@@ -38,18 +38,91 @@ private string join(string x, string y) {
3838
3939private predicate isPackageExport ( API:: Node node ) { node = API:: moduleExport ( _) }
4040
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. */
4154private predicate relevantEdge ( API:: Node pred , API:: Node succ ) {
4255 succ = pred .getMember ( _) and
4356 not isPrivateLike ( succ )
4457 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+ )
46110}
47111
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+ }
51123
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 ) ) }
53126
54127/**
55128 * 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) {
76149
77150private predicate isPrivateLike ( API:: Node node ) { isPrivateAssignment ( node .asSink ( ) ) }
78151
152+ bindingset [ name]
153+ private int getNameBadness ( string name ) {
154+ if name = [ "constructor" , "default" ] then result = 10 else result = 0
155+ }
156+
79157private 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
82160 (
83161 exists ( string member |
84162 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 )
95169 )
96170 )
97171 or
98- result = node . getInstance ( ) and
172+ instanceEdge ( node , result ) and
99173 name = "prototype" and
100174 badness = 0
101175 )
@@ -118,15 +192,17 @@ private API::Node getPreferredPredecessor(API::Node node, string name, int badne
118192 min ( API:: Node pred , int b |
119193 pred = getAPredecessor ( node , _, b ) and
120194 // 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 )
122196 |
123197 b
124198 ) and
125199 result =
126200 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 )
128204 |
129- pred order by distanceFromPackageExport ( pred ) , name1
205+ pred order by distanceFromRoot ( pred ) , name1
130206 ) and
131207 name = min ( string n | result = getAPredecessor ( node , n , badness ) | n )
132208}
@@ -141,6 +217,12 @@ private predicate sinkHasNameCandidate(API::Node sink, string package, string na
141217 name = "" and
142218 badness = 0
143219 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
144226 exists ( API:: Node baseNode , string baseName , int baseBadness , string step , int stepBadness |
145227 sinkHasNameCandidate ( baseNode , package , baseName , baseBadness ) and
146228 baseNode = getPreferredPredecessor ( sink , step , stepBadness ) and
@@ -192,51 +274,7 @@ private API::Node getASinkNode(DataFlow::SourceNode node) { node = nodeReachingS
192274private predicate nameFromGlobal ( DataFlow:: Node node , string package , string name , int badness ) {
193275 package = "global" and
194276 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 )
240278}
241279
242280/** Holds if an instance of `cls` can be exposed to client code. */
@@ -250,8 +288,6 @@ private predicate sourceNodeHasNameCandidate(
250288 sinkHasPrimaryName ( getASinkNode ( node ) , package , name , badness )
251289 or
252290 nameFromGlobal ( node , package , name , badness )
253- or
254- classObjectHasNameCandidate ( node , package , name , badness )
255291}
256292
257293private predicate sourceNodeHasPrimaryName (
@@ -273,6 +309,11 @@ private DataFlow::SourceNode functionValue(DataFlow::TypeTracker t) {
273309 result instanceof DataFlow:: ClassNode
274310 or
275311 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 ( )
276317 )
277318 or
278319 exists ( DataFlow:: TypeTracker t2 | result = functionValue ( t2 ) .track ( t2 , t ) )
@@ -299,7 +340,6 @@ private predicate isFunctionSource(DataFlow::SourceNode node) {
299340 or
300341 node = functionValue ( ) and
301342 node instanceof DataFlow:: InvokeNode and
302- exists ( node .getABoundFunctionValue ( _) ) and
303343 // `getASinkNode` steps through imports (but not other calls) so exclude calls that are imports (i.e. require calls)
304344 // as we want to get as close to the source as possible.
305345 not node instanceof DataFlow:: ModuleImportNode
@@ -323,25 +363,17 @@ private predicate sinkHasSourceName(API::Node sink, string package, string name,
323363 )
324364}
325365
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-
332366private 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 , _)
334369}
335370
336371private predicate aliasCandidate (
337372 string package , string name , string targetPackage , string targetName , API:: Node aliasDef
338373) {
339374 sinkHasPrimaryName ( aliasDef , package , name ) and
340375 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
345377}
346378
347379private predicate nonAlias ( string package , string name ) {
@@ -354,7 +386,7 @@ private predicate nonAlias(string package, string name) {
354386 exists ( API:: Node sink , string targetPackage , string targetName |
355387 aliasCandidate ( package , name , targetPackage , targetName , _) and
356388 sinkHasPrimaryName ( sink , package , name ) and
357- not sinkHasPrimarySourceName ( sink , targetPackage , targetName , _ )
389+ not sinkHasPrimarySourceName ( sink , targetPackage , targetName )
358390 )
359391}
360392
@@ -424,8 +456,6 @@ private module InternalModuleNaming {
424456
425457 /** Holds if `(package, name)` should be used to refer to code inside `mod`. */
426458 predicate fallbackModuleName ( Module mod , string package , string name , int badness ) {
427- sinkHasPrimaryName ( getASinkNode ( mod .getDefaultOrBulkExport ( ) ) , package , name , badness )
428- or
429459 badness = 50 and
430460 package = getPackageRelativePath ( mod ) and
431461 name = ""
0 commit comments