55import javascript
66
77module GlobalAccessPath {
8+ /**
9+ * A source node that can be the root of an access path.
10+ */
11+ private class Root extends DataFlow:: SourceNode {
12+ Root ( ) {
13+ not this .accessesGlobal ( _) and
14+ not this instanceof DataFlow:: PropRead and
15+ not this instanceof PropertyProjection and
16+ not this instanceof Closure:: ClosureNamespaceAccess and
17+ not this = DataFlow:: parameterNode ( any ( ImmediatelyInvokedFunctionExpr iife ) .getAParameter ( ) )
18+ }
19+
20+ /** Holds if this represents the root of the global access path. */
21+ predicate isGlobal ( ) {
22+ this = DataFlow:: globalAccessPathRootPseudoNode ( )
23+ }
24+ }
25+
826 /**
927 * A local variable with exactly one definition, not counting implicit initialization.
1028 */
@@ -21,52 +39,56 @@ module GlobalAccessPath {
2139 }
2240
2341 /**
24- * Gets the global access path referred to by `node`.
42+ * Gets the access path relative to `root` referred to by `node`.
2543 *
2644 * This holds for direct references as well as for aliases
2745 * established through local data flow.
2846 *
2947 * Examples:
3048 * ```
31- * function f() {
32- * let v = foo.bar ; // reference to 'foo.bar '
33- * v.baz; // reference to 'foo.bar.baz '
49+ * function f(x ) {
50+ * let a = x.f.g ; // access path relative to 'x' is '.f.g '
51+ * let b = a.h; // access path relative to 'x' is '.f.g.h '
3452 * }
35- *
36- * (function(ns) {
37- * ns.x; // reference to 'NS.x'
38- * })(NS = NS || {});
3953 * ```
4054 */
4155 cached
42- string fromReference ( DataFlow:: Node node ) {
43- result = fromReference ( node .getImmediatePredecessor ( ) )
56+ string fromReference ( DataFlow:: Node node , Root root ) {
57+ root = node and
58+ not root .isGlobal ( ) and
59+ result = ""
60+ or
61+ result = fromReference ( node .getImmediatePredecessor ( ) , root )
4462 or
4563 exists ( EffectivelyConstantVariable var |
4664 var .isCaptured ( ) and
4765 node .asExpr ( ) = var .getAnAccess ( ) and
48- result = fromReference ( var .getValue ( ) )
66+ result = fromReference ( var .getValue ( ) , root )
4967 )
5068 or
5169 node .accessesGlobal ( result ) and
52- result != "undefined"
70+ result != "undefined" and
71+ root .isGlobal ( )
5372 or
5473 not node .accessesGlobal ( _) and
5574 exists ( DataFlow:: PropRead prop | node = prop |
56- result = fromReference ( prop .getBase ( ) ) + "." + prop .getPropertyName ( )
75+ result = fromReference ( prop .getBase ( ) , root ) + "." + prop .getPropertyName ( )
5776 )
5877 or
59- exists ( Closure:: ClosureNamespaceAccess acc | node = acc | result = acc .getClosureNamespace ( ) )
78+ exists ( Closure:: ClosureNamespaceAccess acc | node = acc |
79+ result = acc .getClosureNamespace ( ) and
80+ root .isGlobal ( )
81+ )
6082 or
6183 exists ( PropertyProjection proj | node = proj |
6284 proj .isSingletonProjection ( ) and
63- result = fromReference ( proj .getObject ( ) ) + "." + proj .getASelector ( )
85+ result = fromReference ( proj .getObject ( ) , root ) + "." + proj .getASelector ( )
6486 )
6587 or
6688 // Treat 'e || {}' as having the same name as 'e'
6789 exists ( LogOrExpr e | node .asExpr ( ) = e |
6890 e .getRightOperand ( ) .( ObjectExpr ) .getNumProperty ( ) = 0 and
69- result = fromReference ( e .getLeftOperand ( ) .flow ( ) )
91+ result = fromReference ( e .getLeftOperand ( ) .flow ( ) , root )
7092 )
7193 or
7294 // Treat 'e && e.f' as having the same name as 'e.f'
@@ -84,10 +106,33 @@ module GlobalAccessPath {
84106 rhs .getBase ( ) .( PropAccess ) .getQualifiedName ( ) = name
85107 )
86108 ) and
87- result = fromReference ( rhs .flow ( ) )
109+ result = fromReference ( rhs .flow ( ) , root )
88110 )
89111 }
90112
113+ /**
114+ * Gets the global access path referred to by `node`.
115+ *
116+ * This holds for direct references as well as for aliases
117+ * established through local data flow.
118+ *
119+ * Examples:
120+ * ```
121+ * function f() {
122+ * let v = foo.bar; // reference to 'foo.bar'
123+ * v.baz; // reference to 'foo.bar.baz'
124+ * }
125+ *
126+ * (function(ns) {
127+ * ns.x; // reference to 'NS.x'
128+ * })(NS = NS || {});
129+ * ```
130+ */
131+ cached
132+ string fromReference ( DataFlow:: Node node ) {
133+ result = fromReference ( node , DataFlow:: globalAccessPathRootPseudoNode ( ) )
134+ }
135+
91136 /**
92137 * Holds if `rhs` is the right-hand side of a self-assignment.
93138 *
@@ -96,90 +141,143 @@ module GlobalAccessPath {
96141 * foo = foo || {};
97142 * ```
98143 */
99- private predicate isSelfAssignment ( DataFlow:: Node rhs ) { fromRhs ( rhs ) = fromReference ( rhs ) }
144+ private predicate isSelfAssignment ( DataFlow:: Node rhs ) {
145+ fromRhs ( rhs , DataFlow:: globalAccessPathRootPseudoNode ( ) ) = fromReference ( rhs , DataFlow:: globalAccessPathRootPseudoNode ( ) )
146+ }
100147
101148 /**
102149 * Holds if there is an assignment to `accessPath` in `file`, not counting
103150 * self-assignments.
104151 */
105152 private predicate isAssignedInFile ( string accessPath , File file ) {
106153 exists ( DataFlow:: Node rhs |
107- fromRhs ( rhs ) = accessPath and
154+ fromRhs ( rhs , DataFlow :: globalAccessPathRootPseudoNode ( ) ) = accessPath and
108155 not isSelfAssignment ( rhs ) and
109156 // Note: Avoid unneeded materialization of DataFlow::Node.getFile()
110157 rhs .getAstNode ( ) .getFile ( ) = file
111158 )
112159 }
113160
114161 /**
115- * Holds if `accessPath` is only assigned to from one file, not counting
162+ * Holds if the global `accessPath` is only assigned to from one file, not counting
116163 * self-assignments.
117164 */
118165 predicate isAssignedInUniqueFile ( string accessPath ) {
119166 strictcount ( File f | isAssignedInFile ( accessPath , f ) ) = 1
120167 }
121168
122169 /**
123- * Gets the global access path `node` is being assigned to, if any.
170+ * Gets the access path relative to `root`, which `node` is being assigned to, if any.
124171 *
125172 * Only holds for the immediate right-hand side of an assignment or property, not
126173 * for nodes that transitively flow there.
127174 *
128- * For example, the class nodes below all map to `foo.bar`:
175+ * For example, the class nodes below all map to `. foo.bar` relative to `x `:
129176 * ```
130- * foo.bar = class {};
131- *
132- * foo = { bar: class {} };
133- *
134- * (function(f) {
135- * f.bar = class {}
136- * })(foo = foo || {});
177+ * function f(x) {
178+ * x.foo.bar = class {};
179+ * x.foo = { bar: class() };
180+ * let alias = x;
181+ * alias.foo.bar = class {};
182+ * }
137183 * ```
138184 */
139185 cached
140- string fromRhs ( DataFlow:: Node node ) {
186+ string fromRhs ( DataFlow:: Node node , Root root ) {
141187 exists ( DataFlow:: SourceNode base , string baseName , string name |
142188 node = base .getAPropertyWrite ( name ) .getRhs ( ) and
143189 result = baseName + "." + name
144190 |
145- baseName = fromReference ( base )
191+ baseName = fromReference ( base , root )
146192 or
147- baseName = fromRhs ( base )
193+ baseName = fromRhs ( base , root )
148194 )
149195 or
150196 exists ( GlobalVariable var |
151197 node = var .getAnAssignedExpr ( ) .flow ( ) and
152- result = var .getName ( )
198+ result = var .getName ( ) and
199+ root .isGlobal ( )
153200 )
154201 or
155202 exists ( FunctionDeclStmt fun |
156203 node = DataFlow:: valueNode ( fun ) and
157- result = fun .getId ( ) .( GlobalVarDecl ) .getName ( )
204+ result = fun .getId ( ) .( GlobalVarDecl ) .getName ( ) and
205+ root .isGlobal ( )
158206 )
159207 or
160208 exists ( ClassDeclStmt cls |
161209 node = DataFlow:: valueNode ( cls ) and
162- result = cls .getIdentifier ( ) .( GlobalVarDecl ) .getName ( )
210+ result = cls .getIdentifier ( ) .( GlobalVarDecl ) .getName ( ) and
211+ root .isGlobal ( )
163212 )
164213 or
165214 exists ( EnumDeclaration decl |
166215 node = DataFlow:: valueNode ( decl ) and
167- result = decl .getIdentifier ( ) .( GlobalVarDecl ) .getName ( )
216+ result = decl .getIdentifier ( ) .( GlobalVarDecl ) .getName ( ) and
217+ root .isGlobal ( )
168218 )
169219 or
170220 exists ( NamespaceDeclaration decl |
171221 node = DataFlow:: valueNode ( decl ) and
172- result = decl .getId ( ) .( GlobalVarDecl ) .getName ( )
222+ result = decl .getId ( ) .( GlobalVarDecl ) .getName ( ) and
223+ root .isGlobal ( )
173224 )
174225 }
175226
227+ /**
228+ * Gets the global access path `node` is being assigned to, if any.
229+ *
230+ * Only holds for the immediate right-hand side of an assignment or property, not
231+ * for nodes that transitively flow there.
232+ *
233+ * For example, the class nodes below all map to `foo.bar`:
234+ * ```
235+ * foo.bar = class {};
236+ *
237+ * foo = { bar: class {} };
238+ *
239+ * (function(f) {
240+ * f.bar = class {}
241+ * })(foo = foo || {});
242+ * ```
243+ */
244+ cached
245+ string fromRhs ( DataFlow:: Node node ) {
246+ result = fromRhs ( node , DataFlow:: globalAccessPathRootPseudoNode ( ) )
247+ }
248+
249+ /**
250+ * Gets the access path relative to `root` referenced by or assigned to `node`.
251+ */
252+ string getAccessPath ( DataFlow:: Node node , Root root ) {
253+ result = fromReference ( node , root )
254+ or
255+ not exists ( fromReference ( node , root ) ) and
256+ result = fromRhs ( node , root )
257+ }
258+
176259 /**
177260 * Gets the global access path referenced by or assigned to `node`.
178261 */
179262 string getAccessPath ( DataFlow:: Node node ) {
180- result = fromReference ( node )
263+ result = getAccessPath ( node , DataFlow:: globalAccessPathRootPseudoNode ( ) )
264+ }
265+
266+ /**
267+ * Holds if there is a step from `pred` to `succ` through an assignment to an access path.
268+ */
269+ pragma [ inline]
270+ predicate step ( DataFlow:: Node pred , DataFlow:: Node succ ) {
271+ exists ( string name , Root root |
272+ name = fromRhs ( pred , root ) and
273+ name = fromReference ( succ , root ) and
274+ not root .isGlobal ( )
275+ )
181276 or
182- not exists ( fromReference ( node ) ) and
183- result = fromRhs ( node )
277+ exists ( string name |
278+ name = fromRhs ( pred ) and
279+ name = fromReference ( succ ) and
280+ isAssignedInUniqueFile ( name )
281+ )
184282 }
185283}
0 commit comments