@@ -38,18 +38,91 @@ private string join(string x, string y) {
38
38
39
39
private predicate isPackageExport ( API:: Node node ) { node = API:: moduleExport ( _) }
40
40
41
+ /**
42
+ * A version of `getInstance()` only from sink nodes to the special `ClassInstance` node.
43
+ *
44
+ * This ensures we see instance methods, but not side effects on `this` or on instantiations of the class.
45
+ */
46
+ private predicate instanceEdge ( API:: Node pred , API:: Node succ ) {
47
+ exists ( DataFlow:: ClassNode cls |
48
+ pred .getAValueReachingSink ( ) = cls and
49
+ succ = API:: Internal:: getClassInstance ( cls )
50
+ )
51
+ }
52
+
53
+ /** Holds if `pred -> succ` is an edge we can use for naming. */
41
54
private predicate relevantEdge ( API:: Node pred , API:: Node succ ) {
42
55
succ = pred .getMember ( _) and
43
56
not isPrivateLike ( succ )
44
57
or
45
- succ = pred .getInstance ( )
58
+ instanceEdge ( pred , succ )
59
+ }
60
+
61
+ private signature predicate isRootNodeSig ( API:: Node node ) ;
62
+
63
+ private signature predicate edgeSig ( API:: Node pred , API:: Node succ ) ;
64
+
65
+ /** Builds `shortestDistances` using the API graph root node as the only origin node, to ensure unique results. */
66
+ private module ApiGraphDistance< isRootNodeSig / 1 isRootNode, edgeSig / 2 edges> {
67
+ private predicate edgesWithEntry ( API:: Node pred , API:: Node succ ) {
68
+ edges ( pred , succ )
69
+ or
70
+ pred = API:: root ( ) and
71
+ isRootNode ( succ )
72
+ }
73
+
74
+ int distanceTo ( API:: Node node ) = shortestDistances( API:: root / 0 , edgesWithEntry / 2 ) ( _, node , result )
75
+ }
76
+
77
+ /** Gets the shortest distance from a package export to `nd` in the API graph. */
78
+ private predicate distanceFromPackageExport =
79
+ ApiGraphDistance< isPackageExport / 1 , relevantEdge / 2 > :: distanceTo / 1 ;
80
+
81
+ /**
82
+ * Holds if `(package, name)` is the fallback name for `cls`, to be used as a last resort
83
+ * in order to name its instance methods.
84
+ *
85
+ * This happens when the class is not accessible via an access path, but instances of the
86
+ * class can still escape via more complex access patterns, for example:
87
+ *
88
+ * class InternalClass {}
89
+ * function foo() {
90
+ * return new InternalClass();
91
+ * }
92
+ */
93
+ private predicate classHasFallbackName (
94
+ DataFlow:: ClassNode cls , string package , string name , int badness
95
+ ) {
96
+ hasEscapingInstance ( cls ) and
97
+ not exists ( distanceFromPackageExport ( any ( API:: Node node | node .getAValueReachingSink ( ) = cls ) ) ) and
98
+ exists ( string baseName |
99
+ InternalModuleNaming:: fallbackModuleName ( cls .getTopLevel ( ) , package , baseName , badness - 100 ) and
100
+ name = join ( baseName , cls .getName ( ) )
101
+ )
102
+ }
103
+
104
+ /** Holds if `node` describes instances of a class that has a fallback name. */
105
+ private predicate isClassInstanceWithFallbackName ( API:: Node node ) {
106
+ exists ( DataFlow:: ClassNode cls |
107
+ classHasFallbackName ( cls , _, _, _) and
108
+ node = API:: Internal:: getClassInstance ( cls )
109
+ )
46
110
}
47
111
48
- /** Gets the shortest distance from a packaeg export to `nd` in the API graph. */
49
- private int distanceFromPackageExport ( API:: Node nd ) =
50
- shortestDistances( isPackageExport / 1 , relevantEdge / 2 ) ( _, nd , result )
112
+ /** Gets the shortest distance from a node with a fallback name, to `nd` in the API graph. */
113
+ private predicate distanceFromFallbackName =
114
+ ApiGraphDistance< isClassInstanceWithFallbackName / 1 , relevantEdge / 2 > :: distanceTo / 1 ;
115
+
116
+ /** Gets the shortest distance from a name-root (package export or fallback name) to `nd` */
117
+ private int distanceFromRoot ( API:: Node nd ) {
118
+ result = distanceFromPackageExport ( nd )
119
+ or
120
+ not exists ( distanceFromPackageExport ( nd ) ) and
121
+ result = 100 + distanceFromFallbackName ( nd )
122
+ }
51
123
52
- private predicate isExported ( API:: Node node ) { exists ( distanceFromPackageExport ( node ) ) }
124
+ /** Holds if `nd` can be given a name. */
125
+ private predicate isRelevant ( API:: Node node ) { exists ( distanceFromRoot ( node ) ) }
53
126
54
127
/**
55
128
* Holds if `node` is a default export that can be reinterpreted as a namespace export,
@@ -76,26 +149,27 @@ private predicate isPrivateAssignment(DataFlow::Node node) {
76
149
77
150
private predicate isPrivateLike ( API:: Node node ) { isPrivateAssignment ( node .asSink ( ) ) }
78
151
152
+ bindingset [ name]
153
+ private int getNameBadness ( string name ) {
154
+ if name = [ "constructor" , "default" ] then result = 10 else result = 0
155
+ }
156
+
79
157
private API:: Node getASuccessor ( API:: Node node , string name , int badness ) {
80
- isExported ( node ) and
81
- isExported ( result ) and
158
+ isRelevant ( node ) and
159
+ isRelevant ( result ) and
82
160
(
83
161
exists ( string member |
84
162
result = node .getMember ( member ) and
85
- if member = "default"
86
- then
87
- if defaultExportCanBeInterpretedAsNamespaceExport ( node )
88
- then (
89
- badness = 5 and name = ""
90
- ) else (
91
- badness = 10 and name = "default"
92
- )
93
- else (
94
- name = member and badness = 0
163
+ if member = "default" and defaultExportCanBeInterpretedAsNamespaceExport ( node )
164
+ then (
165
+ badness = 5 and name = ""
166
+ ) else (
167
+ name = member and
168
+ badness = getNameBadness ( name )
95
169
)
96
170
)
97
171
or
98
- result = node . getInstance ( ) and
172
+ instanceEdge ( node , result ) and
99
173
name = "prototype" and
100
174
badness = 0
101
175
)
@@ -118,15 +192,17 @@ private API::Node getPreferredPredecessor(API::Node node, string name, int badne
118
192
min ( API:: Node pred , int b |
119
193
pred = getAPredecessor ( node , _, b ) and
120
194
// ensure the preferred predecessor is strictly closer to a root export, even if it means accepting more badness
121
- distanceFromPackageExport ( pred ) < distanceFromPackageExport ( node )
195
+ distanceFromRoot ( pred ) < distanceFromRoot ( node )
122
196
|
123
197
b
124
198
) and
125
199
result =
126
200
min ( API:: Node pred , string name1 |
127
- pred = getAPredecessor ( node , name1 , badness )
201
+ pred = getAPredecessor ( node , name1 , badness ) and
202
+ // ensure the preferred predecessor is strictly closer to a root export, even if it means accepting more badness
203
+ distanceFromRoot ( pred ) < distanceFromRoot ( node )
128
204
|
129
- pred order by distanceFromPackageExport ( pred ) , name1
205
+ pred order by distanceFromRoot ( pred ) , name1
130
206
) and
131
207
name = min ( string n | result = getAPredecessor ( node , n , badness ) | n )
132
208
}
@@ -141,6 +217,12 @@ private predicate sinkHasNameCandidate(API::Node sink, string package, string na
141
217
name = "" and
142
218
badness = 0
143
219
or
220
+ exists ( DataFlow:: ClassNode cls , string className |
221
+ sink = API:: Internal:: getClassInstance ( cls ) and
222
+ classHasFallbackName ( cls , package , className , badness ) and
223
+ name = join ( className , "prototype" )
224
+ )
225
+ or
144
226
exists ( API:: Node baseNode , string baseName , int baseBadness , string step , int stepBadness |
145
227
sinkHasNameCandidate ( baseNode , package , baseName , baseBadness ) and
146
228
baseNode = getPreferredPredecessor ( sink , step , stepBadness ) and
@@ -192,51 +274,7 @@ private API::Node getASinkNode(DataFlow::SourceNode node) { node = nodeReachingS
192
274
private predicate nameFromGlobal ( DataFlow:: Node node , string package , string name , int badness ) {
193
275
package = "global" and
194
276
node = AccessPath:: getAnAssignmentTo ( name ) and
195
- badness = - 10
196
- }
197
-
198
- bindingset [ qualifiedName]
199
- private int getBadnessOfClassName ( string qualifiedName ) {
200
- if qualifiedName .matches ( "%.constructor" )
201
- then result = 10
202
- else
203
- if qualifiedName = ""
204
- then result = 5
205
- else result = 0
206
- }
207
-
208
- /** Holds if `(package, name)` is a potential name for `cls`, with the given `badness`. */
209
- private predicate classObjectHasNameCandidate (
210
- DataFlow:: ClassNode cls , string package , string name , int badness
211
- ) {
212
- // There can be multiple API nodes associated with `cls`.
213
- // For example:
214
- ///
215
- // class C {}
216
- // module.exports.A = C; // first sink
217
- // module.exports.B = C; // second sink
218
- //
219
- exists ( int baseBadness |
220
- sinkHasPrimaryName ( getASinkNode ( cls ) , package , name , baseBadness ) and
221
- badness = baseBadness + getBadnessOfClassName ( name )
222
- )
223
- or
224
- nameFromGlobal ( cls , package , name , badness )
225
- or
226
- // If the class is not accessible via an access path, but instances of the
227
- // class can still escape via more complex access patterns, resort to a synthesized name.
228
- // For example:
229
- //
230
- // class InternalClass {}
231
- // function foo() {
232
- // return new InternalClass();
233
- // }
234
- //
235
- hasEscapingInstance ( cls ) and
236
- exists ( string baseName |
237
- InternalModuleNaming:: fallbackModuleName ( cls .getTopLevel ( ) , package , baseName , badness - 100 ) and
238
- name = join ( baseName , cls .getName ( ) )
239
- )
277
+ ( if node .getTopLevel ( ) .isExterns ( ) then badness = - 10 else badness = 10 )
240
278
}
241
279
242
280
/** Holds if an instance of `cls` can be exposed to client code. */
@@ -250,8 +288,6 @@ private predicate sourceNodeHasNameCandidate(
250
288
sinkHasPrimaryName ( getASinkNode ( node ) , package , name , badness )
251
289
or
252
290
nameFromGlobal ( node , package , name , badness )
253
- or
254
- classObjectHasNameCandidate ( node , package , name , badness )
255
291
}
256
292
257
293
private predicate sourceNodeHasPrimaryName (
@@ -273,6 +309,11 @@ private DataFlow::SourceNode functionValue(DataFlow::TypeTracker t) {
273
309
result instanceof DataFlow:: ClassNode
274
310
or
275
311
result instanceof DataFlow:: PartialInvokeNode
312
+ or
313
+ result = DataFlow:: globalVarRef ( [ "Function" , "eval" ] ) .getAnInvocation ( )
314
+ or
315
+ // Assume double-invocation of Function also returns a function
316
+ result = DataFlow:: globalVarRef ( "Function" ) .getAnInvocation ( ) .getAnInvocation ( )
276
317
)
277
318
or
278
319
exists ( DataFlow:: TypeTracker t2 | result = functionValue ( t2 ) .track ( t2 , t ) )
@@ -299,7 +340,6 @@ private predicate isFunctionSource(DataFlow::SourceNode node) {
299
340
or
300
341
node = functionValue ( ) and
301
342
node instanceof DataFlow:: InvokeNode and
302
- exists ( node .getABoundFunctionValue ( _) ) and
303
343
// `getASinkNode` steps through imports (but not other calls) so exclude calls that are imports (i.e. require calls)
304
344
// as we want to get as close to the source as possible.
305
345
not node instanceof DataFlow:: ModuleImportNode
@@ -323,25 +363,17 @@ private predicate sinkHasSourceName(API::Node sink, string package, string name,
323
363
)
324
364
}
325
365
326
- private predicate sinkHasPrimarySourceName ( API:: Node sink , string package , string name , int badness ) {
327
- badness = min ( int b | sinkHasSourceName ( sink , _, _, b ) | b ) and
328
- package = min ( string p | sinkHasSourceName ( sink , p , _, badness ) | p order by p .length ( ) , p ) and
329
- name = min ( string n | sinkHasSourceName ( sink , package , n , badness ) | n order by n .length ( ) , n )
330
- }
331
-
332
366
private predicate sinkHasPrimarySourceName ( API:: Node sink , string package , string name ) {
333
- sinkHasPrimarySourceName ( sink , package , name , _)
367
+ strictcount ( string p , string n | sinkHasSourceName ( sink , p , n , _) ) = 1 and
368
+ sinkHasSourceName ( sink , package , name , _)
334
369
}
335
370
336
371
private predicate aliasCandidate (
337
372
string package , string name , string targetPackage , string targetName , API:: Node aliasDef
338
373
) {
339
374
sinkHasPrimaryName ( aliasDef , package , name ) and
340
375
sinkHasPrimarySourceName ( aliasDef , targetPackage , targetName ) and
341
- not (
342
- package = targetPackage and
343
- name = targetName
344
- )
376
+ not sinkHasSourceName ( _, package , name , _) // (package, name) cannot be an alias if a source has it as its primary name
345
377
}
346
378
347
379
private predicate nonAlias ( string package , string name ) {
@@ -354,7 +386,7 @@ private predicate nonAlias(string package, string name) {
354
386
exists ( API:: Node sink , string targetPackage , string targetName |
355
387
aliasCandidate ( package , name , targetPackage , targetName , _) and
356
388
sinkHasPrimaryName ( sink , package , name ) and
357
- not sinkHasPrimarySourceName ( sink , targetPackage , targetName , _ )
389
+ not sinkHasPrimarySourceName ( sink , targetPackage , targetName )
358
390
)
359
391
}
360
392
@@ -424,8 +456,6 @@ private module InternalModuleNaming {
424
456
425
457
/** Holds if `(package, name)` should be used to refer to code inside `mod`. */
426
458
predicate fallbackModuleName ( Module mod , string package , string name , int badness ) {
427
- sinkHasPrimaryName ( getASinkNode ( mod .getDefaultOrBulkExport ( ) ) , package , name , badness )
428
- or
429
459
badness = 50 and
430
460
package = getPackageRelativePath ( mod ) and
431
461
name = ""
0 commit comments