summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorBenjamin Coe <bencoe@google.com>2019-09-20 11:43:02 -0700
committerAnna Henningsen <anna@addaleax.net>2019-09-22 00:39:41 +0200
commit8f06773a8cd73aef37ddc8f68d2c8202f1a8c45a (patch)
tree9fc55258099209d89e0eebdd2b6a45128da0f928 /lib
parente74f30894c46c94aa1329e8462f811b8d5e54a91 (diff)
downloadandroid-node-v8-8f06773a8cd73aef37ddc8f68d2c8202f1a8c45a.tar.gz
android-node-v8-8f06773a8cd73aef37ddc8f68d2c8202f1a8c45a.tar.bz2
android-node-v8-8f06773a8cd73aef37ddc8f68d2c8202f1a8c45a.zip
process: initial SourceMap support via NODE_V8_COVERAGE
PR-URL: https://github.com/nodejs/node/pull/28960 Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com> Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: David Carlier <devnexen@gmail.com>
Diffstat (limited to 'lib')
-rw-r--r--lib/internal/bootstrap/pre_execution.js2
-rw-r--r--lib/internal/modules/cjs/loader.js7
-rw-r--r--lib/internal/modules/esm/translators.js2
-rw-r--r--lib/internal/source_map.js152
4 files changed, 161 insertions, 2 deletions
diff --git a/lib/internal/bootstrap/pre_execution.js b/lib/internal/bootstrap/pre_execution.js
index 9e8897bb00..d18d709928 100644
--- a/lib/internal/bootstrap/pre_execution.js
+++ b/lib/internal/bootstrap/pre_execution.js
@@ -119,7 +119,9 @@ function setupCoverageHooks(dir) {
const cwd = require('internal/process/execution').tryGetCwd();
const { resolve } = require('path');
const coverageDirectory = resolve(cwd, dir);
+ const { sourceMapCacheToObject } = require('internal/source_map');
internalBinding('profiler').setCoverageDirectory(coverageDirectory);
+ internalBinding('profiler').setSourceMapCacheGetter(sourceMapCacheToObject);
return coverageDirectory;
}
diff --git a/lib/internal/modules/cjs/loader.js b/lib/internal/modules/cjs/loader.js
index 51adba6086..8ef01b4499 100644
--- a/lib/internal/modules/cjs/loader.js
+++ b/lib/internal/modules/cjs/loader.js
@@ -31,6 +31,7 @@ const {
} = primordials;
const { NativeModule } = require('internal/bootstrap/loaders');
+const { maybeCacheSourceMap } = require('internal/source_map');
const { pathToFileURL, fileURLToPath, URL } = require('internal/url');
const { deprecate } = require('internal/util');
const vm = require('vm');
@@ -845,7 +846,9 @@ Module.prototype.require = function(id) {
var resolvedArgv;
let hasPausedEntry = false;
-function wrapSafe(filename, content) {
+function wrapSafe(filename, content, cjsModuleInstance) {
+ maybeCacheSourceMap(filename, content, cjsModuleInstance);
+
if (patched) {
const wrapper = Module.wrap(content);
return vm.runInThisContext(wrapper, {
@@ -910,7 +913,7 @@ Module.prototype._compile = function(content, filename) {
manifest.assertIntegrity(moduleURL, content);
}
- const compiledWrapper = wrapSafe(filename, content);
+ const compiledWrapper = wrapSafe(filename, content, this);
var inspectorWrapper = null;
if (getOptionValue('--inspect-brk') && process._eval == null) {
diff --git a/lib/internal/modules/esm/translators.js b/lib/internal/modules/esm/translators.js
index 26508e744e..352b937766 100644
--- a/lib/internal/modules/esm/translators.js
+++ b/lib/internal/modules/esm/translators.js
@@ -31,6 +31,7 @@ const {
} = require('internal/errors').codes;
const readFileAsync = promisify(fs.readFile);
const JsonParse = JSON.parse;
+const { maybeCacheSourceMap } = require('internal/source_map');
const debug = debuglog('esm');
@@ -74,6 +75,7 @@ async function importModuleDynamically(specifier, { url }) {
// Strategy for loading a standard JavaScript module
translators.set('module', async function moduleStrategy(url) {
const source = `${await getSource(url)}`;
+ maybeCacheSourceMap(url, source);
debug(`Translating StandardModule ${url}`);
const { ModuleWrap, callbackMap } = internalBinding('module_wrap');
const module = new ModuleWrap(source, url);
diff --git a/lib/internal/source_map.js b/lib/internal/source_map.js
new file mode 100644
index 0000000000..4b198ff598
--- /dev/null
+++ b/lib/internal/source_map.js
@@ -0,0 +1,152 @@
+'use strict';
+
+// See https://sourcemaps.info/spec.html for SourceMap V3 specification.
+const { Buffer } = require('buffer');
+const debug = require('internal/util/debuglog').debuglog('source_map');
+const { dirname, resolve } = require('path');
+const fs = require('fs');
+const {
+ normalizeReferrerURL,
+} = require('internal/modules/cjs/helpers');
+const { JSON, Object } = primordials;
+// For cjs, since Module._cache is exposed to users, we use a WeakMap
+// keyed on module, facilitating garbage collection.
+const cjsSourceMapCache = new WeakMap();
+// The esm cache is not exposed to users, so we can use a Map keyed
+// on filenames.
+const esmSourceMapCache = new Map();
+const { fileURLToPath, URL } = require('url');
+
+function maybeCacheSourceMap(filename, content, cjsModuleInstance) {
+ if (!process.env.NODE_V8_COVERAGE) return;
+
+ let basePath;
+ try {
+ filename = normalizeReferrerURL(filename);
+ basePath = dirname(fileURLToPath(filename));
+ } catch (err) {
+ // This is most likely an [eval]-wrapper, which is currently not
+ // supported.
+ debug(err.stack);
+ return;
+ }
+
+ const match = content.match(/\/[*/]#\s+sourceMappingURL=(?<sourceMappingURL>[^\s]+)/);
+ if (match) {
+ if (cjsModuleInstance) {
+ cjsSourceMapCache.set(cjsModuleInstance, {
+ url: match.groups.sourceMappingURL,
+ data: dataFromUrl(basePath, match.groups.sourceMappingURL)
+ });
+ } else {
+ // If there is no cjsModuleInstance assume we are in a
+ // "modules/esm" context.
+ esmSourceMapCache.set(filename, {
+ url: match.groups.sourceMappingURL,
+ data: dataFromUrl(basePath, match.groups.sourceMappingURL)
+ });
+ }
+ }
+}
+
+function dataFromUrl(basePath, sourceMappingURL) {
+ try {
+ const url = new URL(sourceMappingURL);
+ switch (url.protocol) {
+ case 'data:':
+ return sourceMapFromDataUrl(basePath, url.pathname);
+ default:
+ debug(`unknown protocol ${url.protocol}`);
+ return null;
+ }
+ } catch (err) {
+ debug(err.stack);
+ // If no scheme is present, we assume we are dealing with a file path.
+ const sourceMapFile = resolve(basePath, sourceMappingURL);
+ return sourceMapFromFile(sourceMapFile);
+ }
+}
+
+function sourceMapFromFile(sourceMapFile) {
+ try {
+ const content = fs.readFileSync(sourceMapFile, 'utf8');
+ const data = JSON.parse(content);
+ return sourcesToAbsolute(dirname(sourceMapFile), data);
+ } catch (err) {
+ debug(err.stack);
+ return null;
+ }
+}
+
+// data:[<mediatype>][;base64],<data> see:
+// https://tools.ietf.org/html/rfc2397#section-2
+function sourceMapFromDataUrl(basePath, url) {
+ const [format, data] = url.split(',');
+ const splitFormat = format.split(';');
+ const contentType = splitFormat[0];
+ const base64 = splitFormat[splitFormat.length - 1] === 'base64';
+ if (contentType === 'application/json') {
+ const decodedData = base64 ?
+ Buffer.from(data, 'base64').toString('utf8') : data;
+ try {
+ const parsedData = JSON.parse(decodedData);
+ return sourcesToAbsolute(basePath, parsedData);
+ } catch (err) {
+ debug(err.stack);
+ return null;
+ }
+ } else {
+ debug(`unknown content-type ${contentType}`);
+ return null;
+ }
+}
+
+// If the sources are not absolute URLs after prepending of the "sourceRoot",
+// the sources are resolved relative to the SourceMap (like resolving script
+// src in a html document).
+function sourcesToAbsolute(base, data) {
+ data.sources = data.sources.map((source) => {
+ source = (data.sourceRoot || '') + source;
+ if (!/^[\\/]/.test(source[0])) {
+ source = resolve(base, source);
+ }
+ if (!source.startsWith('file://')) source = `file://${source}`;
+ return source;
+ });
+ // The sources array is now resolved to absolute URLs, sourceRoot should
+ // be updated to noop.
+ data.sourceRoot = '';
+ return data;
+}
+
+function sourceMapCacheToObject() {
+ const obj = Object.create(null);
+
+ for (const [k, v] of esmSourceMapCache) {
+ obj[k] = v;
+ }
+ appendCJSCache(obj);
+
+ if (Object.keys(obj).length === 0) {
+ return undefined;
+ } else {
+ return obj;
+ }
+}
+
+// Since WeakMap can't be iterated over, we use Module._cache's
+// keys to facilitate Source Map serialization.
+function appendCJSCache(obj) {
+ const { Module } = require('internal/modules/cjs/loader');
+ Object.keys(Module._cache).forEach((key) => {
+ const value = cjsSourceMapCache.get(Module._cache[key]);
+ if (value) {
+ obj[`file://${key}`] = value;
+ }
+ });
+}
+
+module.exports = {
+ sourceMapCacheToObject,
+ maybeCacheSourceMap
+};