Skip to content

Commit bea86e5

Browse files
authored
Merge pull request #275 from xiemaisi/js/workaround-for-nested-imports
Approved by asger-semmle
2 parents 604ff23 + 220fcb5 commit bea86e5

File tree

8 files changed

+74
-5
lines changed

8 files changed

+74
-5
lines changed

change-notes/1.19/analysis-javascript.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
* Support for popular libraries has been improved. Consequently, queries may produce more results on code bases that use the following features:
1010
- file system access, for example through [fs-extra](https://github.com/jprichardson/node-fs-extra) or [globby](https://www.npmjs.com/package/globby)
1111

12+
* The type inference now handles nested imports (that is, imports not appearing at the toplevel). This may yield fewer false-positive results on projects that use this non-standard language feature.
1213

1314
## New queries
1415

javascript/ql/src/semmle/javascript/CFG.qll

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -281,7 +281,7 @@ import javascript
281281
*/
282282
class ControlFlowNode extends @cfg_node, Locatable {
283283
/** Gets a node succeeding this node in the CFG. */
284-
ControlFlowNode getASuccessor() {
284+
cached ControlFlowNode getASuccessor() {
285285
successor(this, result)
286286
}
287287

@@ -457,3 +457,47 @@ class ConcreteControlFlowNode extends ControlFlowNode {
457457
not this instanceof SyntheticControlFlowNode
458458
}
459459
}
460+
461+
/**
462+
* A CFG node corresponding to a nested (that is, non-toplevel) import declaration.
463+
*
464+
* This is a non-standard language feature that is not currently supported very well
465+
* by the extractor, in particular such imports do not appear in the control flow graph
466+
* generated by the extractor. We patch them in by overriding `getASuccessor`; once an
467+
* extractor fix becomes available, this class can be removed.
468+
*/
469+
private class NestedImportDeclaration extends ImportDeclaration {
470+
NestedImportDeclaration() {
471+
exists (ASTNode p | p = getParent() |
472+
not p instanceof TopLevel
473+
) and
474+
// if there are no specifiers, the default control flow graph is fine
475+
exists(getASpecifier())
476+
}
477+
478+
override ControlFlowNode getASuccessor() {
479+
result = getSpecifier(0).getFirstControlFlowNode()
480+
}
481+
}
482+
483+
/**
484+
* A CFG node corresponding to an import specifier in a nested import declaration.
485+
*
486+
* As for `NestedImportDeclaration` above, this is a temporary workaround that will be
487+
* removed once extractor support for this non-standard language feature becomes available.
488+
*/
489+
private class NestedImportSpecifier extends ImportSpecifier {
490+
NestedImportDeclaration decl;
491+
int i;
492+
493+
NestedImportSpecifier() {
494+
this = decl.getSpecifier(i)
495+
}
496+
497+
override ControlFlowNode getASuccessor() {
498+
result = decl.getSpecifier(i+1).getFirstControlFlowNode()
499+
or
500+
not exists(decl.getSpecifier(i+1)) and
501+
successor(decl, result)
502+
}
503+
}

javascript/ql/src/semmle/javascript/ES2015Modules.qll

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ class ES2015Module extends Module {
2727

2828
/** Gets an export declaration in this module. */
2929
ExportDeclaration getAnExport() {
30-
result.getContainer() = this
30+
result.getTopLevel() = this
3131
}
3232

3333
override Module getAnImportedModule() {
@@ -55,7 +55,7 @@ class ES2015Module extends Module {
5555
/** An import declaration. */
5656
class ImportDeclaration extends Stmt, Import, @importdeclaration {
5757
override ES2015Module getEnclosingModule() {
58-
result = getContainer()
58+
result = getTopLevel()
5959
}
6060

6161
override PathExprInModule getImportedPath() {
@@ -254,7 +254,7 @@ class BulkReExportDeclaration extends ReExportDeclaration, @exportalldeclaration
254254
* but we ignore this subtlety.
255255
*/
256256
private predicate isShadowedFromBulkExport(BulkReExportDeclaration reExport, string name) {
257-
exists (ExportNamedDeclaration other | other.getContainer() = reExport.getEnclosingModule() |
257+
exists (ExportNamedDeclaration other | other.getTopLevel() = reExport.getEnclosingModule() |
258258
other.getAnExportedDecl().getName() = name
259259
or
260260
other.getASpecifier().getExportedName() = name)

javascript/ql/src/semmle/javascript/dataflow/internal/InterModuleTypeInference.qll

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -301,7 +301,7 @@ private class AnalyzedExportAssign extends AnalyzedPropertyWrite, DataFlow::Valu
301301
}
302302

303303
override predicate writes(AbstractValue baseVal, string propName, DataFlow::AnalyzedNode source) {
304-
baseVal = TAbstractModuleObject(exportAssign.getContainer()) and
304+
baseVal = TAbstractModuleObject(exportAssign.getTopLevel()) and
305305
propName = "exports" and
306306
source = this
307307
}

javascript/ql/test/library-tests/Flow/AbstractValues.expected

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,10 @@
180180
| n.js:3:16:3:23 | object literal |
181181
| namespace-reexport.js:1:1:4:0 | exports object of module namespace-reexport |
182182
| namespace-reexport.js:1:1:4:0 | module object of module namespace-reexport |
183+
| nestedImport.js:1:1:13:0 | exports object of module nestedImport |
184+
| nestedImport.js:1:1:13:0 | module object of module nestedImport |
185+
| nestedImport.js:9:1:12:1 | function tst |
186+
| nestedImport.js:9:1:12:1 | instance of function tst |
183187
| nodeJsClient.js:1:1:6:0 | exports object of module nodeJsClient |
184188
| nodeJsClient.js:1:1:6:0 | module object of module nodeJsClient |
185189
| nodeJsLib.js:1:1:4:0 | exports object of module nodeJsLib |

javascript/ql/test/library-tests/Flow/abseval.expected

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@
5454
| b.js:42:5:42:7 | z11 | b.js:42:11:42:18 | toString | file://:0:0:0:0 | indefinite value (import) |
5555
| b.js:45:5:45:7 | z12 | b.js:45:11:45:12 | f2 | ts2.ts:1:1:6:0 | exports object of module ts2 |
5656
| b.js:45:5:45:7 | z12 | b.js:45:11:45:12 | f2 | ts2.ts:1:10:1:22 | anonymous function |
57+
| b.js:45:5:45:7 | z12 | b.js:45:11:45:12 | f2 | ts2.ts:4:12:4:13 | object literal |
5758
| b.js:48:5:48:7 | z13 | b.js:48:11:48:11 | w | file://:0:0:0:0 | non-empty, non-numeric string |
5859
| b.js:51:5:51:7 | z14 | b.js:51:11:51:24 | foo_reexported | file://:0:0:0:0 | indefinite value (import) |
5960
| b.js:54:5:54:7 | z15 | b.js:54:11:54:19 | something | file://:0:0:0:0 | indefinite value (import) |
@@ -171,6 +172,10 @@
171172
| mixed.js:9:5:9:6 | fn | mixed.js:9:10:9:19 | __filename | file://:0:0:0:0 | numeric string |
172173
| mixed.js:10:5:10:6 | dn | mixed.js:10:10:10:18 | __dirname | file://:0:0:0:0 | non-empty, non-numeric string |
173174
| mixed.js:10:5:10:6 | dn | mixed.js:10:10:10:18 | __dirname | file://:0:0:0:0 | numeric string |
175+
| nestedImport.js:2:5:2:6 | x1 | nestedImport.js:2:10:2:12 | foo | esLib.js:3:8:3:24 | function foo |
176+
| nestedImport.js:6:7:6:8 | x2 | nestedImport.js:6:12:6:14 | foo | file://:0:0:0:0 | indefinite value (import) |
177+
| nestedImport.js:6:7:6:8 | x2 | nestedImport.js:6:12:6:14 | foo | nodeJsLib.js:3:15:3:37 | function nodeJsFoo |
178+
| nestedImport.js:11:7:11:8 | x3 | nestedImport.js:11:12:11:14 | foo | esLib.js:3:8:3:24 | function foo |
174179
| nodeJsClient.js:1:5:1:6 | nj | nodeJsClient.js:1:10:1:31 | require ... JsLib') | file://:0:0:0:0 | indefinite value (call) |
175180
| nodeJsClient.js:1:5:1:6 | nj | nodeJsClient.js:1:10:1:31 | require ... JsLib') | nodeJsLib.js:1:1:4:0 | exports object of module nodeJsLib |
176181
| nodeJsClient.js:1:5:1:6 | nj | nodeJsClient.js:1:10:1:31 | require ... JsLib') | nodeJsLib.js:1:18:1:43 | function nodeJsModule |
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { foo } from './esLib';
2+
let x1 = foo;
3+
4+
if (!foo) {
5+
import { foo } from './nodeJsLib';
6+
let x2 = foo;
7+
}
8+
9+
function tst() {
10+
import { foo } from './esLib';
11+
let x3 = foo;
12+
}

javascript/ql/test/library-tests/Flow/types.expected

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,9 @@
9898
| mixed.js:8:5:8:7 | exp | mixed.js:8:11:8:17 | exports | object |
9999
| mixed.js:9:5:9:6 | fn | mixed.js:9:10:9:19 | __filename | string |
100100
| mixed.js:10:5:10:6 | dn | mixed.js:10:10:10:18 | __dirname | string |
101+
| nestedImport.js:2:5:2:6 | x1 | nestedImport.js:2:10:2:12 | foo | function |
102+
| nestedImport.js:6:7:6:8 | x2 | nestedImport.js:6:12:6:14 | foo | boolean, class, date, function, null, number, object, regular expression,string or undefined |
103+
| nestedImport.js:11:7:11:8 | x3 | nestedImport.js:11:12:11:14 | foo | function |
101104
| nodeJsClient.js:1:5:1:6 | nj | nodeJsClient.js:1:10:1:31 | require ... JsLib') | boolean, class, date, function, null, number, object, regular expression,string or undefined |
102105
| nodeJsClient.js:2:5:2:6 | es | nodeJsClient.js:2:10:2:27 | require('./esLib') | boolean, class, date, function, null, number, object, regular expression,string or undefined |
103106
| nodeJsClient.js:4:5:4:6 | x1 | nodeJsClient.js:4:10:4:15 | nj.foo | boolean, class, date, function, null, number, object, regular expression,string or undefined |

0 commit comments

Comments
 (0)