33const  { 
44  ArrayPrototypePush, 
55  JSONParse, 
6-   ObjectKeys, 
76  RegExpPrototypeExec, 
7+   SafeFinalizationRegistry, 
88  SafeMap, 
9+   SafeWeakRef, 
910  StringPrototypeCodePointAt, 
1011  StringPrototypeSplit, 
1112}  =  primordials ; 
@@ -23,19 +24,26 @@ const {
2324const  { 
2425  setInternalPrepareStackTrace, 
2526}  =  require ( 'internal/errors' ) ; 
26- const  {  getLazy }  =  require ( 'internal/util' ) ; 
27- 
28- // Since the CJS module cache is mutable, which leads to memory leaks when 
29- // modules are deleted, we use a WeakMap so that the source map cache will 
30- // be purged automatically: 
31- const  getCjsSourceMapCache  =  getLazy ( ( )  =>  { 
32-   const  {  IterableWeakMap }  =  require ( 'internal/util/iterable_weak_map' ) ; 
33-   return  new  IterableWeakMap ( ) ; 
34- } ) ; 
3527
36- // The esm cache is not mutable, so we can use a Map without memory concerns: 
37- const  esmSourceMapCache  =  new  SafeMap ( ) ; 
38- // The generated sources is not mutable, so we can use a Map without memory concerns: 
28+ // The cached module instance can be removed from the global module registry 
29+ // with approaches like mutating `require.cache`. 
30+ // The `moduleSourceMapCache` exposes entries by `filename` and `sourceURL`. 
31+ // In the case of mutated module registry, obsolete entries are removed from 
32+ // the cache by the `moduleFinalizationRegistry`. 
33+ const  moduleSourceMapCache  =  new  SafeMap ( ) ; 
34+ const  moduleFinalizationRegistry  =  new  SafeFinalizationRegistry ( ( key )  =>  { 
35+   const  instanceRef  =  moduleSourceMapCache . get ( key ) . moduleInstanceRef ; 
36+   // Delete the entry if the instanceRef has been reclaimed. 
37+   // If the instanceRef is not reclaimed, the entry was overridden by a new 
38+   // module instance. 
39+   if  ( instanceRef  &&  instanceRef . deref ( )  ===  undefined )  { 
40+     moduleSourceMapCache . delete ( key ) ; 
41+   } 
42+ } ) ; 
43+ // The generated source module/script instance is not accessible, so we can use 
44+ // a Map without memory concerns. Separate generated source entries with the module 
45+ // source entries to avoid overriding the module source entries with arbitrary 
46+ // source url magic comments. 
3947const  generatedSourceMapCache  =  new  SafeMap ( ) ; 
4048const  kLeadingProtocol  =  / ^ \w + : \/ \/ / ; 
4149const  kSourceMappingURLMagicComment  =  / \/ [ * / ] # \s + s o u r c e M a p p i n g U R L = (?< sourceMappingURL > [ ^ \s ] + ) / g; 
@@ -52,6 +60,10 @@ function getSourceMapsEnabled() {
5260  return  sourceMapsEnabled ; 
5361} 
5462
63+ /** 
64+  * Enables or disables source maps programmatically. 
65+  * @param  {boolean } val 
66+  */ 
5567function  setSourceMapsEnabled ( val )  { 
5668  validateBoolean ( val ,  'val' ) ; 
5769
@@ -72,6 +84,14 @@ function setSourceMapsEnabled(val) {
7284  sourceMapsEnabled  =  val ; 
7385} 
7486
87+ /** 
88+  * Extracts the source url from the content if present. For example 
89+  * //# sourceURL=file:///path/to/file 
90+  * 
91+  * Read more at: https://tc39.es/source-map-spec/#linking-evald-code-to-named-generated-code 
92+  * @param  {string } content - source content 
93+  * @returns  {string | null } source url or null if not present 
94+  */ 
7595function  extractSourceURLMagicComment ( content )  { 
7696  let  match ; 
7797  let  matchSourceURL ; 
@@ -90,6 +110,14 @@ function extractSourceURLMagicComment(content) {
90110  return  sourceURL ; 
91111} 
92112
113+ /** 
114+  * Extracts the source map url from the content if present. For example 
115+  * //# sourceMappingURL=file:///path/to/file 
116+  * 
117+  * Read more at: https://tc39.es/source-map-spec/#linking-generated-code 
118+  * @param  {string } content - source content 
119+  * @returns  {string | null } source map url or null if not present 
120+  */ 
93121function  extractSourceMapURLMagicComment ( content )  { 
94122  let  match ; 
95123  let  lastMatch ; 
@@ -104,7 +132,17 @@ function extractSourceMapURLMagicComment(content) {
104132  return  lastMatch . groups . sourceMappingURL ; 
105133} 
106134
107- function  maybeCacheSourceMap ( filename ,  content ,  cjsModuleInstance ,  isGeneratedSource ,  sourceURL ,  sourceMapURL )  { 
135+ /** 
136+  * Caches the source map if it is present in the content, with the given filename, moduleInstance, and sourceURL. 
137+  * @param  {string } filename - the actual filename 
138+  * @param  {string } content - the actual source content 
139+  * @param  {import('internal/modules/cjs/loader').Module | undefined } moduleInstance - a CJS module instance that 
140+  * associated with the source, once this is reclaimed, the source map entry will be removed from the cache 
141+  * @param  {boolean } isGeneratedSource - if the source was generated and evaluated with the global eval 
142+  * @param  {string | undefined } sourceURL - the source url 
143+  * @param  {string | undefined } sourceMapURL - the source map url 
144+  */ 
145+ function  maybeCacheSourceMap ( filename ,  content ,  moduleInstance ,  isGeneratedSource ,  sourceURL ,  sourceMapURL )  { 
108146  const  sourceMapsEnabled  =  getSourceMapsEnabled ( ) ; 
109147  if  ( ! ( process . env . NODE_V8_COVERAGE  ||  sourceMapsEnabled ) )  return ; 
110148  const  {  normalizeReferrerURL }  =  require ( 'internal/modules/helpers' ) ; 
@@ -129,16 +167,7 @@ function maybeCacheSourceMap(filename, content, cjsModuleInstance, isGeneratedSo
129167
130168  const  data  =  dataFromUrl ( filename ,  sourceMapURL ) ; 
131169  const  url  =  data  ? null  : sourceMapURL ; 
132-   if  ( cjsModuleInstance )  { 
133-     getCjsSourceMapCache ( ) . set ( cjsModuleInstance ,  { 
134-       __proto__ : null , 
135-       filename, 
136-       lineLengths : lineLengths ( content ) , 
137-       data, 
138-       url, 
139-       sourceURL, 
140-     } ) ; 
141-   }  else  if  ( isGeneratedSource )  { 
170+   if  ( isGeneratedSource )  { 
142171    const  entry  =  { 
143172      __proto__ : null , 
144173      lineLengths : lineLengths ( content ) , 
@@ -150,23 +179,49 @@ function maybeCacheSourceMap(filename, content, cjsModuleInstance, isGeneratedSo
150179    if  ( sourceURL )  { 
151180      generatedSourceMapCache . set ( sourceURL ,  entry ) ; 
152181    } 
153-   }  else  { 
154-     // If there is no cjsModuleInstance and is not generated source assume we are in a 
155-     // "modules/esm" context. 
156-     const  entry  =  { 
157-       __proto__ : null , 
158-       lineLengths : lineLengths ( content ) , 
159-       data, 
160-       url, 
161-       sourceURL, 
162-     } ; 
163-     esmSourceMapCache . set ( filename ,  entry ) ; 
164-     if  ( sourceURL )  { 
165-       esmSourceMapCache . set ( sourceURL ,  entry ) ; 
166-     } 
182+     return ; 
183+   } 
184+ 
185+   // If it is not a generated source, we assume we are in a "cjs/esm" 
186+   // context. 
187+   const  entry  =  { 
188+     __proto__ : null , 
189+     lineLengths : lineLengths ( content ) , 
190+     data, 
191+     url, 
192+     sourceURL, 
193+     moduleInstanceRef : moduleInstance  ? new  SafeWeakRef ( moduleInstance )  : undefined , 
194+   } ; 
195+   setModuleSourceMapCache ( filename ,  sourceURL ,  moduleInstance ,  entry ) ; 
196+ } 
197+ 
198+ /** 
199+  * Registers the module entry in the `moduleSourceMapCache` with the filename and sourceURL. 
200+  * @param  {string } filename - the actual filename, in the form of a file URL 
201+  * @param  {string | undefined } sourceURL - the source url 
202+  * @param  {import('internal/modules/cjs/loader').Module | undefined } moduleInstance - a CJS module instance 
203+  * @param  {object } entry - the source map cache entry 
204+  */ 
205+ function  setModuleSourceMapCache ( filename ,  sourceURL ,  moduleInstance ,  entry )  { 
206+   moduleSourceMapCache . set ( filename ,  entry ) ; 
207+   if  ( sourceURL )  { 
208+     moduleSourceMapCache . set ( sourceURL ,  entry ) ; 
209+   } 
210+   // Skip if the module instance is not present. 
211+   if  ( moduleInstance  ==  null )  return ; 
212+ 
213+   // Register the module instance with the finalization registry to clear the 
214+   // entry once the module instance is been reclaimed. 
215+   moduleFinalizationRegistry . register ( moduleInstance ,  filename ) ; 
216+   if  ( sourceURL )  { 
217+     moduleFinalizationRegistry . register ( moduleInstance ,  sourceURL ) ; 
167218  } 
168219} 
169220
221+ /** 
222+  * Caches the source map if it is present in the eval'd source. 
223+  * @param  {string } content - the eval'd source code 
224+  */ 
170225function  maybeCacheGeneratedSourceMap ( content )  { 
171226  const  sourceMapsEnabled  =  getSourceMapsEnabled ( ) ; 
172227  if  ( ! ( process . env . NODE_V8_COVERAGE  ||  sourceMapsEnabled ) )  return ; 
@@ -184,6 +239,14 @@ function maybeCacheGeneratedSourceMap(content) {
184239  } 
185240} 
186241
242+ /** 
243+  * Resolves source map payload data from the source url and source map url. 
244+  * If the source map url is a data url, the data is returned. 
245+  * Otherwise the source map url is resolved to a file path and the file is read. 
246+  * @param  {string } sourceURL - url of the source file 
247+  * @param  {string } sourceMappingURL - url of the source map 
248+  * @returns  {object } deserialized source map JSON object 
249+  */ 
187250function  dataFromUrl ( sourceURL ,  sourceMappingURL )  { 
188251  try  { 
189252    const  url  =  new  URL ( sourceMappingURL ) ; 
@@ -225,7 +288,11 @@ function lineLengths(content) {
225288  return  output ; 
226289} 
227290
228- 
291+ /** 
292+  * Read source map from file. 
293+  * @param  {string } mapURL - file url of the source map 
294+  * @returns  {object } deserialized source map JSON object 
295+  */ 
229296function  sourceMapFromFile ( mapURL )  { 
230297  try  { 
231298    const  fs  =  require ( 'fs' ) ; 
@@ -279,56 +346,43 @@ function sourcesToAbsolute(baseURL, data) {
279346  return  data ; 
280347} 
281348
282- // WARNING: The `sourceMapCacheToObject` and `appendCJSCache`  run during 
283- // shutdown. In particular,  they also run when Workers are terminated, making 
284- // it important that they do  not call out to any user-provided code, including 
285- // built-in prototypes that  might have been tampered with. 
349+ // WARNING: The `sourceMapCacheToObject` run during shutdown. In particular,  
350+ // they also run when Workers are terminated, making it important that they do  
351+ // not call out to any user-provided code, including built-in prototypes that  
352+ // might have been tampered with. 
286353
287354// Get serialized representation of source-map cache, this is used 
288355// to persist a cache of source-maps to disk when NODE_V8_COVERAGE is enabled. 
289356function  sourceMapCacheToObject ( )  { 
290-   const  obj  =  {  __proto__ : null  } ; 
291- 
292-   for  ( const  {  0 : k ,  1 : v  }  of  esmSourceMapCache )  { 
293-     obj [ k ]  =  v ; 
294-   } 
295- 
296-   appendCJSCache ( obj ) ; 
297- 
298-   if  ( ObjectKeys ( obj ) . length  ===  0 )  { 
357+   if  ( moduleSourceMapCache . size  ===  0 )  { 
299358    return  undefined ; 
300359  } 
301-   return  obj ; 
302- } 
303360
304- function   appendCJSCache ( obj )   { 
305-   for  ( const  value   of  getCjsSourceMapCache ( ) )  { 
306-     obj [ value . filename ]  =  { 
361+    const   obj   =   {   __proto__ :  null   } ; 
362+   for  ( const  {   0 :  k ,   1 :  v   }   of  moduleSourceMapCache )  { 
363+     obj [ k ]  =  { 
307364      __proto__ : null , 
308-       lineLengths : value . lineLengths , 
309-       data : value . data , 
310-       url : value . url , 
365+       lineLengths : v . lineLengths , 
366+       data : v . data , 
367+       url : v . url , 
311368    } ; 
312369  } 
370+   return  obj ; 
313371} 
314372
373+ /** 
374+  * Find a source map for a given actual source URL or path. 
375+  * @param  {string } sourceURL - actual source URL or path 
376+  * @returns  {import('internal/source_map/source_map').SourceMap | undefined } a source map or undefined if not found 
377+  */ 
315378function  findSourceMap ( sourceURL )  { 
316379  if  ( RegExpPrototypeExec ( kLeadingProtocol ,  sourceURL )  ===  null )  { 
317380    sourceURL  =  pathToFileURL ( sourceURL ) . href ; 
318381  } 
319382  if  ( ! SourceMap )  { 
320383    SourceMap  =  require ( 'internal/source_map/source_map' ) . SourceMap ; 
321384  } 
322-   let  entry  =  esmSourceMapCache . get ( sourceURL )  ??  generatedSourceMapCache . get ( sourceURL ) ; 
323-   if  ( entry  ===  undefined )  { 
324-     for  ( const  value  of  getCjsSourceMapCache ( ) )  { 
325-       const  filename  =  value . filename ; 
326-       const  cachedSourceURL  =  value . sourceURL ; 
327-       if  ( sourceURL  ===  filename  ||  sourceURL  ===  cachedSourceURL )  { 
328-         entry  =  value ; 
329-       } 
330-     } 
331-   } 
385+   const  entry  =  moduleSourceMapCache . get ( sourceURL )  ??  generatedSourceMapCache . get ( sourceURL ) ; 
332386  if  ( entry  ===  undefined )  { 
333387    return  undefined ; 
334388  } 
0 commit comments