Skip to content

Commit 4249276

Browse files
legendecasruyadorno
authored andcommitted
src,lib: print source map error source on demand
The source context is not prepended to the value of the `stack` property when the source map is not enabled. Rather than prepending the error source context to the value of the `stack` property unconditionally, this patch aligns the behavior and only prints the source context when the error is not handled by userland (e.g. fatal errors). Also, this patch fixes that when source-map support is enabled, the error source context is not pointing to where the error was thrown. PR-URL: #43875Fixes: #43186Fixes: #41541 Reviewed-By: Ben Coe <bencoe@gmail.com> Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
1 parent 374b776 commit 4249276

18 files changed

Lines changed: 221 additions & 87 deletions

‎.eslintignore‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ test/message/esm_display_syntax_error.mjs
66
tools/icu
77
tools/lint-md/lint-md.mjs
88
benchmark/tmp
9+
benchmark/fixtures
910
doc/**/*.js
1011
!doc/api_assets/*.js
1112
!.eslintrc.js

‎benchmark/es/error-stack.js‎

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
'use strict';
2+
3+
constcommon=require('../common.js');
4+
constmodPath=require.resolve('../fixtures/simple-error-stack.js');
5+
6+
constbench=common.createBenchmark(main,{
7+
method: ['without-sourcemap','sourcemap'],
8+
n: [1e5],
9+
});
10+
11+
functionrunN(n){
12+
deleterequire.cache[modPath];
13+
constmod=require(modPath);
14+
bench.start();
15+
for(leti=0;i<n;i++){
16+
mod.simpleErrorStack();
17+
}
18+
bench.end(n);
19+
}
20+
21+
functionmain({ n, method }){
22+
switch(method){
23+
case'without-sourcemap':
24+
process.setSourceMapsEnabled(false);
25+
runN(n);
26+
break;
27+
case'sourcemap':
28+
process.setSourceMapsEnabled(true);
29+
runN(n);
30+
break;
31+
default:
32+
thrownewError(`Unexpected method "${method}"`);
33+
}
34+
}

‎benchmark/fixtures/simple-error-stack.js‎

Lines changed: 15 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
'use strict';
2+
3+
// Compile with `tsc --inlineSourceMap benchmark/fixtures/simple-error-stack.ts`.
4+
5+
constlorem='Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.';
6+
7+
functionsimpleErrorStack(){
8+
try{
9+
(loremasany).BANG();
10+
}catch(e){
11+
returne.stack;
12+
}
13+
}
14+
15+
export{
16+
simpleErrorStack,
17+
};

‎lib/internal/source_map/prepare_stack_trace.js‎

Lines changed: 26 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ const {
2525
kIsNodeError,
2626
}=require('internal/errors');
2727
const{ fileURLToPath }=require('internal/url');
28+
const{ setGetSourceMapErrorSource }=internalBinding('errors');
2829

2930
// Create a prettified stacktrace, inserting context from source maps
3031
// if possible.
@@ -53,7 +54,6 @@ const prepareStackTrace = (globalThis, error, trace) => {
5354
returnerrorString;
5455
}
5556

56-
leterrorSource='';
5757
letlastSourceMap;
5858
letlastFileName;
5959
constpreparedTrace=ArrayPrototypeJoin(ArrayPrototypeMap(trace,(t,i)=>{
@@ -62,14 +62,12 @@ const prepareStackTrace = (globalThis, error, trace) => {
6262
// A stack trace will often have several call sites in a row within the
6363
// same file, cache the source map and file content accordingly:
6464
letfileName=t.getFileName();
65-
letgenerated=false;
6665
if(fileName===undefined){
6766
fileName=t.getEvalOrigin();
68-
generated=true;
6967
}
7068
constsm=fileName===lastFileName ?
7169
lastSourceMap :
72-
findSourceMap(fileName,generated);
70+
findSourceMap(fileName);
7371
lastSourceMap=sm;
7472
lastFileName=fileName;
7573
if(sm){
@@ -83,14 +81,6 @@ const prepareStackTrace = (globalThis, error, trace) => {
8381
if(originalSource&&originalLine!==undefined&&
8482
originalColumn!==undefined){
8583
constname=getOriginalSymbolName(sm,trace,i);
86-
if(i===0){
87-
errorSource=getErrorSource(
88-
sm,
89-
originalSource,
90-
originalLine,
91-
originalColumn
92-
);
93-
}
9484
// Construct call site name based on: v8.dev/docs/stack-trace-api:
9585
constfnName=t.getFunctionName()??t.getMethodName();
9686
consttypeName=t.getTypeName();
@@ -116,7 +106,7 @@ const prepareStackTrace = (globalThis, error, trace) => {
116106
}
117107
return`${str}${t}`;
118108
}),'');
119-
return`${errorSource}${errorString}\n at ${preparedTrace}`;
109+
return`${errorString}\n at ${preparedTrace}`;
120110
};
121111

122112
// Transpilers may have removed the original symbol name used in the stack
@@ -155,7 +145,7 @@ function getErrorSource(
155145
fileURLToPath(originalSourcePath) : originalSourcePath;
156146
constsource=getOriginalSource(
157147
sourceMap.payload,
158-
originalSourcePathNoScheme
148+
originalSourcePath
159149
);
160150
constlines=RegExpPrototypeSymbolSplit(/\r?\n/,source,originalLine+1);
161151
constline=lines[originalLine];
@@ -178,28 +168,46 @@ function getErrorSource(
178168

179169
functiongetOriginalSource(payload,originalSourcePath){
180170
letsource;
181-
constoriginalSourcePathNoScheme=
182-
StringPrototypeStartsWith(originalSourcePath,'file://') ?
183-
fileURLToPath(originalSourcePath) : originalSourcePath;
171+
// payload.sources has been normalized to be an array of absolute urls.
184172
constsourceContentIndex=
185173
ArrayPrototypeIndexOf(payload.sources,originalSourcePath);
186174
if(payload.sourcesContent?.[sourceContentIndex]){
187175
// First we check if the original source content was provided in the
188176
// source map itself:
189177
source=payload.sourcesContent[sourceContentIndex];
190-
}else{
178+
}elseif(StringPrototypeStartsWith(originalSourcePath,'file://')){
191179
// If no sourcesContent was found, attempt to load the original source
192180
// from disk:
181+
debug(`read source of ${originalSourcePath} from filesystem`);
182+
constoriginalSourcePathNoScheme=fileURLToPath(originalSourcePath);
193183
try{
194184
source=readFileSync(originalSourcePathNoScheme,'utf8');
195185
}catch(err){
196186
debug(err);
197187
source='';
198188
}
189+
}else{
190+
source='';
199191
}
200192
returnsource;
201193
}
202194

195+
functiongetSourceMapErrorSource(fileName,lineNumber,columnNumber){
196+
constsm=findSourceMap(fileName);
197+
if(sm===null){
198+
return;
199+
}
200+
const{
201+
originalLine,
202+
originalColumn,
203+
originalSource,
204+
}=sm.findEntry(lineNumber-1,columnNumber);
205+
consterrorSource=getErrorSource(sm,originalSource,originalLine,originalColumn);
206+
returnerrorSource;
207+
}
208+
209+
setGetSourceMapErrorSource(getSourceMapErrorSource);
210+
203211
module.exports={
204212
prepareStackTrace,
205213
};

‎lib/internal/source_map/source_map_cache.js‎

Lines changed: 54 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ const esmSourceMapCache = new SafeMap();
4141
// The generated sources is not mutable, so we can use a Map without memory concerns:
4242
constgeneratedSourceMapCache=newSafeMap();
4343
constkLeadingProtocol=/^\w+:\/\//;
44+
constkSourceMappingURLMagicComment=/\/[*/]#\s+sourceMappingURL=(?<sourceMappingURL>[^\s]+)/;
45+
constkSourceURLMagicComment=/\/[*/]#\s+sourceURL=(?<sourceURL>[^\s]+)/;
4446

4547
const{ fileURLToPath, pathToFileURL,URL}=require('internal/url');
4648
letSourceMap;
@@ -77,7 +79,22 @@ function setSourceMapsEnabled(val) {
7779
sourceMapsEnabled=val;
7880
}
7981

80-
functionmaybeCacheSourceMap(filename,content,cjsModuleInstance,isGeneratedSource){
82+
functionextractSourceURLMagicComment(content){
83+
constmatchSourceURL=RegExpPrototypeExec(
84+
kSourceURLMagicComment,
85+
content
86+
);
87+
if(matchSourceURL===null){
88+
returnnull;
89+
}
90+
letsourceURL=matchSourceURL.groups.sourceURL;
91+
if(sourceURL!=null&&RegExpPrototypeExec(kLeadingProtocol,sourceURL)===null){
92+
sourceURL=pathToFileURL(sourceURL).href;
93+
}
94+
returnsourceURL;
95+
}
96+
97+
functionmaybeCacheSourceMap(filename,content,cjsModuleInstance,isGeneratedSource,sourceURL){
8198
constsourceMapsEnabled=getSourceMapsEnabled();
8299
if(!(process.env.NODE_V8_COVERAGE||sourceMapsEnabled))return;
83100
try{
@@ -87,10 +104,10 @@ function maybeCacheSourceMap(filename, content, cjsModuleInstance, isGeneratedSo
87104
debug(err);
88105
return;
89106
}
90-
constmatch=RegExpPrototypeExec(
91-
/\/[*/]#\s+sourceMappingURL=(?<sourceMappingURL>[^\s]+)/,
92-
content,
93-
);
107+
constmatch=RegExpPrototypeExec(kSourceMappingURLMagicComment,content);
108+
if(sourceURL===undefined){
109+
sourceURL=extractSourceURLMagicComment(content);
110+
}
94111
if(match){
95112
constdata=dataFromUrl(filename,match.groups.sourceMappingURL);
96113
consturl=data ? null : match.groups.sourceMappingURL;
@@ -99,22 +116,33 @@ function maybeCacheSourceMap(filename, content, cjsModuleInstance, isGeneratedSo
99116
filename,
100117
lineLengths: lineLengths(content),
101118
data,
102-
url
119+
url,
120+
sourceURL,
103121
});
104122
}elseif(isGeneratedSource){
105-
generatedSourceMapCache.set(filename,{
123+
constentry={
106124
lineLengths: lineLengths(content),
107125
data,
108-
url
109-
});
126+
url,
127+
sourceURL
128+
};
129+
generatedSourceMapCache.set(filename,entry);
130+
if(sourceURL){
131+
generatedSourceMapCache.set(sourceURL,entry);
132+
}
110133
}else{
111134
// If there is no cjsModuleInstance and is not generated source assume we are in a
112135
// "modules/esm" context.
113-
esmSourceMapCache.set(filename,{
136+
constentry={
114137
lineLengths: lineLengths(content),
115138
data,
116-
url
117-
});
139+
url,
140+
sourceURL,
141+
};
142+
esmSourceMapCache.set(filename,entry);
143+
if(sourceURL){
144+
esmSourceMapCache.set(sourceURL,entry);
145+
}
118146
}
119147
}
120148
}
@@ -123,19 +151,12 @@ function maybeCacheGeneratedSourceMap(content) {
123151
constsourceMapsEnabled=getSourceMapsEnabled();
124152
if(!(process.env.NODE_V8_COVERAGE||sourceMapsEnabled))return;
125153

126-
constmatchSourceURL=RegExpPrototypeExec(
127-
/\/[*/]#\s+sourceURL=(?<sourceURL>[^\s]+)/,
128-
content
129-
);
130-
if(matchSourceURL==null){
154+
constsourceURL=extractSourceURLMagicComment(content);
155+
if(sourceURL===null){
131156
return;
132157
}
133-
letsourceURL=matchSourceURL.groups.sourceURL;
134-
if(RegExpPrototypeExec(kLeadingProtocol,sourceURL)===null){
135-
sourceURL=pathToFileURL(sourceURL).href;
136-
}
137158
try{
138-
maybeCacheSourceMap(sourceURL,content,null,true);
159+
maybeCacheSourceMap(sourceURL,content,null,true,sourceURL);
139160
}catch(err){
140161
// This can happen if the filename is not a valid URL.
141162
// If we fail to cache the source map, we should not fail the whole process.
@@ -254,33 +275,29 @@ function appendCJSCache(obj) {
254275
}
255276
}
256277

257-
functionfindSourceMap(sourceURL,isGenerated){
278+
functionfindSourceMap(sourceURL){
258279
if(RegExpPrototypeExec(kLeadingProtocol,sourceURL)===null){
259280
sourceURL=pathToFileURL(sourceURL).href;
260281
}
261282
if(!SourceMap){
262283
SourceMap=require('internal/source_map/source_map').SourceMap;
263284
}
264-
letsourceMap;
265-
if(isGenerated){
266-
sourceMap=generatedSourceMapCache.get(sourceURL);
267-
}else{
268-
sourceMap=esmSourceMapCache.get(sourceURL);
269-
if(sourceMap===undefined){
270-
for(constvalueofcjsSourceMapCache){
271-
constfilename=ObjectGetValueSafe(value,'filename');
272-
if(sourceURL===filename){
273-
sourceMap={
274-
data: ObjectGetValueSafe(value,'data')
275-
};
276-
}
285+
letsourceMap=esmSourceMapCache.get(sourceURL)??generatedSourceMapCache.get(sourceURL);
286+
if(sourceMap===undefined){
287+
for(constvalueofcjsSourceMapCache){
288+
constfilename=ObjectGetValueSafe(value,'filename');
289+
constcachedSourceURL=ObjectGetValueSafe(value,'sourceURL');
290+
if(sourceURL===filename||sourceURL===cachedSourceURL){
291+
sourceMap={
292+
data: ObjectGetValueSafe(value,'data')
293+
};
277294
}
278295
}
279296
}
280297
if(sourceMap&&sourceMap.data){
281298
returnnewSourceMap(sourceMap.data);
282299
}
283-
returnundefined;
300+
returnnull;
284301
}
285302

286303
module.exports={

‎src/env.h‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -525,6 +525,7 @@ class NoArrayBufferZeroFillScope {
525525
V(enhance_fatal_stack_after_inspector, v8::Function) \
526526
V(enhance_fatal_stack_before_inspector, v8::Function) \
527527
V(fs_use_promises_symbol, v8::Symbol) \
528+
V(get_source_map_error_source, v8::Function) \
528529
V(host_import_module_dynamically_callback, v8::Function) \
529530
V(host_initialize_import_meta_object_callback, v8::Function) \
530531
V(http2session_on_altsvc_function, v8::Function) \

0 commit comments

Comments
 (0)