deprecate exports and replace uses with the new getAnExportedValue

This commit is contained in:
Erik Krogh Kristensen
2020-09-30 13:24:29 +02:00
parent c5b5a4fd55
commit d316cb512e
18 changed files with 157 additions and 109 deletions

View File

@@ -20,9 +20,9 @@ predicate definedInModule(GlobalVariable v, NodeModule m) {
)
}
from NodeModule m, GlobalVariable f, InvokeExpr invk, ASTNode export, GlobalVarAccess acc
from NodeModule m, GlobalVariable f, InvokeExpr invk, DataFlow::Node export, GlobalVarAccess acc
where
m.exports(f.getName(), export) and
export = m.getAnExportedValue(f.getName()) and
acc = f.getAnAccess() and
invk.getCallee() = acc and
invk.getTopLevel() = m and

View File

@@ -295,8 +295,8 @@ class AmdModule extends Module {
/** Gets the definition of this module. */
AmdModuleDefinition getDefine() { amdModuleTopLevel(result, this) }
override predicate exports(string name, ASTNode export) {
exists(DataFlow::PropWrite pwn | export = pwn.getAstNode() |
override DataFlow::Node getAnExportedValue(string name) {
exists(DataFlow::PropWrite pwn | result = pwn.getRhs() |
pwn.getBase().analyze().getAValue() = getDefine().getAModuleExportsValue() and
name = pwn.getPropertyName()
)

View File

@@ -165,9 +165,9 @@ module Closure {
result = getScope().getVariable("exports")
}
override predicate exports(string name, ASTNode export) {
override DataFlow::Node getAnExportedValue(string name) {
exists(DataFlow::PropWrite write, Expr base |
write.getAstNode() = export and
result = write.getRhs() and
write.writes(base.flow(), name, _) and
(
base = getExportsVariable().getAReference()

View File

@@ -27,8 +27,8 @@ class ES2015Module extends Module {
/** Gets an export declaration in this module. */
ExportDeclaration getAnExport() { result.getTopLevel() = this }
override predicate exports(string name, ASTNode export) {
exists(ExportDeclaration ed | ed = getAnExport() and ed = export | ed.exportsAs(_, name))
override DataFlow::Node getAnExportedValue(string name) {
exists(ExportDeclaration ed | ed = getAnExport() and result = ed.getSourceNode(name))
}
/** Holds if this module exports variable `v` under the name `name`. */
@@ -235,7 +235,7 @@ abstract class ExportDeclaration extends Stmt, @export_declaration {
ES2015Module getEnclosingModule() { this = result.getAnExport() }
/** Holds if this export declaration exports variable `v` under the name `name`. */
abstract predicate exportsAs(LexicalName v, string name);
abstract predicate exportsAs(LexicalName v, string name); // TODO: Can I deprecate this?
/**
* Gets the data flow node corresponding to the value this declaration exports

View File

@@ -24,9 +24,11 @@ abstract class Module extends TopLevel {
Module getAnImportedModule() { result = getAnImport().getImportedModule() }
/** Gets a symbol exported by this module. */
string getAnExportedSymbol() { exports(result, _) }
string getAnExportedSymbol() { exists(getAnExportedValue(result)) }
/**
* DEPRECATED. Use `getAnExportedValue` instead.
*
* Holds if this module explicitly exports symbol `name` at the
* program element `export`.
*
@@ -36,9 +38,77 @@ abstract class Module extends TopLevel {
* that are explicitly defined on the module object.
*
* Symbols defined in another module that are re-exported by
* this module are not considered either.
* this module are only sometimes considered.
*/
abstract predicate exports(string name, ASTNode export);
deprecated predicate exports(string name, ASTNode export) {
this instanceof AmdModule and
exists(DataFlow::PropWrite pwn | export = pwn.getAstNode() |
pwn.getBase().analyze().getAValue() = this.(AmdModule).getDefine().getAModuleExportsValue() and
name = pwn.getPropertyName()
)
or
this instanceof Closure::ClosureModule and
exists(DataFlow::PropWrite write, Expr base |
write.getAstNode() = export and
write.writes(base.flow(), name, _) and
(
base = this.(Closure::ClosureModule).getExportsVariable().getAReference()
or
base = this.(Closure::ClosureModule).getExportsVariable().getAnAssignedExpr()
)
)
or
this instanceof NodeModule and
(
// a property write whose base is `exports` or `module.exports`
exists(DataFlow::PropWrite pwn | export = pwn.getAstNode() |
pwn.getBase() = this.(NodeModule).getAModuleExportsNode() and
name = pwn.getPropertyName()
)
or
// a re-export using spread-operator. E.g. `const foo = require("./foo"); module.exports = {bar: bar, ...foo};`
exists(ObjectExpr obj | obj = this.(NodeModule).getAModuleExportsNode().asExpr() |
obj
.getAProperty()
.(SpreadProperty)
.getInit()
.(SpreadElement)
.getOperand()
.flow()
.getALocalSource()
.asExpr()
.(Import)
.getImportedModule()
.exports(name, export)
)
or
// an externs definition (where appropriate)
exists(PropAccess pacc | export = pacc |
pacc.getBase() = this.(NodeModule).getAModuleExportsNode().asExpr() and
name = pacc.getPropertyName() and
isExterns() and
exists(pacc.getDocumentation())
)
)
or
this instanceof ES2015Module and
exists(ExportDeclaration ed | ed = this.(ES2015Module).getAnExport() and ed = export |
ed.exportsAs(_, name)
)
}
/**
* Get a value that is explicitly exported from this module with under `name`.
*
* Note that in some module systems (notably CommonJS and AMD)
* modules are arbitrary objects that export all their
* properties. This predicate only considers properties
* that are explicitly defined on the module object.
*
* Symbols defined in another module that are re-exported by
* this module are only sometimes considered.
*/
abstract DataFlow::Node getAnExportedValue(string name);
/**
* Gets the root folder relative to which the given import path (which must

View File

@@ -48,7 +48,7 @@ class NodeModule extends Module {
* So if using this predicate - consider expanding the list of relevant expressions.
*/
pragma[noinline]
private DataFlow::Node getAModuleExportsNode() {
DataFlow::Node getAModuleExportsNode() {
(
// A bit of manual magic
result = any(DataFlow::PropWrite w | exists(w.getPropertyName())).getBase()
@@ -62,35 +62,43 @@ class NodeModule extends Module {
/** Gets a symbol exported by this module. */
override string getAnExportedSymbol() {
result = super.getAnExportedSymbol() or
result = super.getAnExportedSymbol()
or
result = getAnImplicitlyExportedSymbol()
or
// getters and the like.
exists(DataFlow::PropWrite pwn |
pwn.getBase() = this.getAModuleExportsNode() and
result = pwn.getPropertyName()
)
}
override predicate exports(string name, ASTNode export) {
override DataFlow::Node getAnExportedValue(string name) {
// a property write whose base is `exports` or `module.exports`
exists(DataFlow::PropWrite pwn | export = pwn.getAstNode() |
exists(DataFlow::PropWrite pwn | result = pwn.getRhs() |
pwn.getBase() = getAModuleExportsNode() and
name = pwn.getPropertyName()
)
or
// a re-export using spread-operator. E.g. `const foo = require("./foo"); module.exports = {bar: bar, ...foo};`
exists(ObjectExpr obj | obj = getAModuleExportsNode().asExpr() |
obj
.getAProperty()
.(SpreadProperty)
.getInit()
.(SpreadElement)
.getOperand()
.flow()
.getALocalSource()
.asExpr()
.(Import)
.getImportedModule()
.exports(name, export)
result =
obj
.getAProperty()
.(SpreadProperty)
.getInit()
.(SpreadElement)
.getOperand()
.flow()
.getALocalSource()
.asExpr()
.(Import)
.getImportedModule()
.getAnExportedValue(name)
)
or
// an externs definition (where appropriate)
exists(PropAccess pacc | export = pacc |
exists(PropAccess pacc | result = DataFlow::valueNode(pacc) |
pacc.getBase() = getAModuleExportsNode().asExpr() and
name = pacc.getPropertyName() and
isExterns() and

View File

@@ -68,22 +68,5 @@ DataFlow::Node getAValueExportedBy(PackageJSON packageJSON) {
private DataFlow::Node getAnExportFromModule(Module mod) {
result.analyze().getAValue() = mod.(NodeModule).getAModuleExportsValue()
or
result = getAnExportedValue(mod, _)
}
/**
* Gets a value exported from `mod` under `name`.
*/
DataFlow::Node getAnExportedValue(Module mod, string name) {
exists(Property export | result.asExpr() = export.getInit() | mod.exports(name, export))
or
result =
DataFlow::valueNode(any(ASTNode export | mod.exports(name, export)))
.(DataFlow::PropWrite)
.getRhs()
or
exists(ExportDeclaration export |
result = export.getSourceNode(name) and
mod = export.getEnclosingModule()
)
result = mod.getAnExportedValue(_)
}

View File

@@ -233,7 +233,9 @@ private module NpmPackagePortal {
apw.writes(m.(AnalyzedModule).getModuleObject(), "exports", exp)
)
or
m.(ES2015Module).exports("default", exp.(DataFlow::ValueNode).getAstNode())
exists(DataFlow::PropWrite export | exp = export |
export.getRhs() = m.(ES2015Module).getAnExportedValue("default")
)
)
}
}

View File

@@ -647,7 +647,7 @@ module NodeJSLib {
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
exists(Import imp, string name |
succ = DataFlow::valueNode(imp).(DataFlow::SourceNode).getAPropertyRead(name) and
pred = Exports::getAnExportedValue(imp.getImportedModule(), name)
pred = imp.getImportedModule().getAnExportedValue(name)
)
}
}

View File

@@ -1,13 +1,13 @@
| a.js:1:1:3:3 | <toplevel> | foo | a.js:2:14:2:20 | foo: 42 |
| dir/b.js:1:1:3:3 | <toplevel> | bar | dir/b.js:2:5:2:11 | bar: 42 |
| lib/a.js:1:1:3:3 | <toplevel> | foo | lib/a.js:2:14:2:20 | foo: 42 |
| lib/foo.js:1:1:4:0 | <toplevel> | foo | lib/foo.js:2:5:2:11 | foo: 23 |
| lib/nested/a.js:1:1:3:3 | <toplevel> | foo | lib/nested/a.js:2:14:2:20 | foo: 42 |
| tst2.js:1:1:3:3 | <toplevel> | foo | tst2.js:2:5:2:15 | exports.foo |
| tst3.js:1:1:3:3 | <toplevel> | foo | tst3.js:2:29:2:39 | exports.foo |
| tst4.js:1:1:11:3 | <toplevel> | bar | tst4.js:9:9:9:18 | bar: b.bar |
| tst4.js:1:1:11:3 | <toplevel> | foo | tst4.js:8:9:8:18 | foo: a.foo |
| tst.js:1:1:6:3 | <toplevel> | bar | tst.js:4:9:4:18 | bar: b.bar |
| tst.js:1:1:6:3 | <toplevel> | foo | tst.js:3:9:3:18 | foo: a.foo |
| umd.js:1:1:14:4 | <toplevel> | bar | umd.js:11:9:11:18 | bar: a.foo |
| umd.js:1:1:14:4 | <toplevel> | foo | umd.js:12:9:12:18 | foo: b.bar |
| a.js:1:1:3:3 | <toplevel> | foo | a.js:2:19:2:20 | 42 |
| dir/b.js:1:1:3:3 | <toplevel> | bar | dir/b.js:2:10:2:11 | 42 |
| lib/a.js:1:1:3:3 | <toplevel> | foo | lib/a.js:2:19:2:20 | 42 |
| lib/foo.js:1:1:4:0 | <toplevel> | foo | lib/foo.js:2:10:2:11 | 23 |
| lib/nested/a.js:1:1:3:3 | <toplevel> | foo | lib/nested/a.js:2:19:2:20 | 42 |
| tst2.js:1:1:3:3 | <toplevel> | foo | tst2.js:2:19:2:20 | 42 |
| tst3.js:1:1:3:3 | <toplevel> | foo | tst3.js:2:43:2:44 | 42 |
| tst4.js:1:1:11:3 | <toplevel> | bar | tst4.js:9:14:9:18 | b.bar |
| tst4.js:1:1:11:3 | <toplevel> | foo | tst4.js:8:14:8:18 | a.foo |
| tst.js:1:1:6:3 | <toplevel> | bar | tst.js:4:14:4:18 | b.bar |
| tst.js:1:1:6:3 | <toplevel> | foo | tst.js:3:14:3:18 | a.foo |
| umd.js:1:1:14:4 | <toplevel> | bar | umd.js:11:14:11:18 | a.foo |
| umd.js:1:1:14:4 | <toplevel> | foo | umd.js:12:14:12:18 | b.bar |

View File

@@ -1,5 +1,5 @@
import javascript
from Module m, string name, ASTNode export
where m.exports(name, export)
select m, name, export
from Module m, string name, DataFlow::Node exportValue
where exportValue = m.getAnExportedValue(name)
select m, name, exportValue

View File

@@ -69,19 +69,18 @@ test_Imports
| tst.html:5:3:5:20 | import f from 'a'; | tst.html:5:17:5:19 | 'a' | 1 |
| unresolved.js:1:1:1:18 | import f from 'a'; | unresolved.js:1:15:1:17 | 'a' | 1 |
test_Module_exports
| a.js:1:1:5:32 | <toplevel> | default | a.js:1:1:3:1 | export ... n 23;\\n} |
| a.js:1:1:5:32 | <toplevel> | x | a.js:5:1:5:32 | export ... } = o; |
| a.js:1:1:5:32 | <toplevel> | y | a.js:5:1:5:32 | export ... } = o; |
| b.js:1:1:8:0 | <toplevel> | f2 | b.js:7:1:7:21 | export ... './a'; |
| b.js:1:1:8:0 | <toplevel> | g | b.js:5:1:5:18 | export { f as g }; |
| e.js:1:1:4:0 | <toplevel> | g | e.js:3:1:3:35 | export ... './a'; |
| e.js:1:1:4:0 | <toplevel> | x | e.js:2:1:2:16 | export { x, y }; |
| e.js:1:1:4:0 | <toplevel> | y | e.js:2:1:2:16 | export { x, y }; |
| es2015_require.js:1:1:3:25 | <toplevel> | default | es2015_require.js:3:1:3:25 | export ... ss C {} |
| export-in-mjs.mjs:1:1:1:34 | <toplevel> | exported_from_mjs | export-in-mjs.mjs:1:1:1:34 | export ... s = 42; |
| f.ts:1:1:6:0 | <toplevel> | foo | f.ts:5:1:5:24 | export ... oo() {} |
| m/c.js:1:1:6:0 | <toplevel> | h | m/c.js:5:1:5:30 | export ... '../b'; |
| tst.html:4:23:8:0 | <toplevel> | y | tst.html:7:3:7:22 | export const y = 42; |
| a.js:1:1:5:32 | <toplevel> | default | a.js:1:16:3:1 | functio ... n 23;\\n} |
| a.js:1:1:5:32 | <toplevel> | x | a.js:5:18:5:20 | f() |
| b.js:1:1:8:0 | <toplevel> | f2 | a.js:1:16:3:1 | functio ... n 23;\\n} |
| b.js:1:1:8:0 | <toplevel> | g | b.js:5:10:5:10 | f |
| e.js:1:1:4:0 | <toplevel> | g | a.js:1:16:3:1 | functio ... n 23;\\n} |
| e.js:1:1:4:0 | <toplevel> | x | e.js:2:10:2:10 | x |
| e.js:1:1:4:0 | <toplevel> | y | e.js:2:13:2:13 | y |
| es2015_require.js:1:1:3:25 | <toplevel> | default | es2015_require.js:3:16:3:25 | class C {} |
| export-in-mjs.mjs:1:1:1:34 | <toplevel> | exported_from_mjs | export-in-mjs.mjs:1:32:1:33 | 42 |
| f.ts:1:1:6:0 | <toplevel> | foo | f.ts:5:8:5:24 | function foo() {} |
| m/c.js:1:1:6:0 | <toplevel> | h | b.js:5:10:5:10 | f |
| tst.html:4:23:8:0 | <toplevel> | y | tst.html:7:20:7:21 | 42 |
test_NamedImportSpecifier
| d.js:1:10:1:21 | default as g |
| d.js:1:24:1:29 | x as y |

View File

@@ -26,8 +26,8 @@ query predicate test_Imports(ImportDeclaration id, PathExpr res0, int res1) {
res0 = id.getImportedPath() and res1 = count(id.getASpecifier())
}
query predicate test_Module_exports(Module m, string name, ASTNode export) {
m.exports(name, export)
query predicate test_Module_exports(Module m, string name, DataFlow::Node exportValue) {
exportValue = m.getAnExportedValue(name)
}
query predicate test_NamedImportSpecifier(NamedImportSpecifier nis) { any() }

View File

@@ -1,9 +1,9 @@
| b.js:1:1:8:0 | <toplevel> | sneaky | b.js:7:1:7:48 | (functi ... .sneaky |
| d.js:1:1:7:15 | <toplevel> | baz | d.js:4:2:4:8 | baz: 42 |
| mjs-files/depend-on-me.js:1:1:8:0 | <toplevel> | add | mjs-files/depend-on-me.js:5:1:7:1 | export ... + y;\\n} |
| mjs-files/depend-on-me.mjs:1:1:7:1 | <toplevel> | add | mjs-files/depend-on-me.mjs:5:1:7:1 | export ... + y;\\n} |
| reexport/a.js:1:1:3:1 | <toplevel> | foo | reexport/a.js:2:5:2:26 | foo: fu ... oo() {} |
| reexport/b.js:1:1:6:1 | <toplevel> | bar | reexport/b.js:4:5:4:26 | bar: fu ... ar() {} |
| reexport/b.js:1:1:6:1 | <toplevel> | foo | reexport/a.js:2:5:2:26 | foo: fu ... oo() {} |
| sub/c.js:1:1:4:0 | <toplevel> | foo | sub/c.js:3:1:3:11 | exports.foo |
| sub/f.js:1:1:4:17 | <toplevel> | bar | sub/f.js:4:1:4:11 | exports.bar |
| b.js:1:1:8:0 | <toplevel> | sneaky | b.js:7:52:7:53 | 56 |
| d.js:1:1:7:15 | <toplevel> | baz | d.js:4:7:4:8 | 42 |
| mjs-files/depend-on-me.js:1:1:8:0 | <toplevel> | add | mjs-files/depend-on-me.js:5:8:7:1 | functio ... + y;\\n} |
| mjs-files/depend-on-me.mjs:1:1:7:1 | <toplevel> | add | mjs-files/depend-on-me.mjs:5:8:7:1 | functio ... + y;\\n} |
| reexport/a.js:1:1:3:1 | <toplevel> | foo | reexport/a.js:2:10:2:26 | function foo() {} |
| reexport/b.js:1:1:6:1 | <toplevel> | bar | reexport/b.js:4:10:4:26 | function bar() {} |
| reexport/b.js:1:1:6:1 | <toplevel> | foo | reexport/a.js:2:10:2:26 | function foo() {} |
| sub/c.js:1:1:4:0 | <toplevel> | foo | sub/c.js:3:15:3:16 | 23 |
| sub/f.js:1:1:4:17 | <toplevel> | bar | sub/f.js:4:15:4:16 | 42 |

View File

@@ -1,5 +1,5 @@
import javascript
from Module m, string name, ASTNode export
where m.exports(name, export)
select m, name, export
from Module m, string name, DataFlow::Node exportValue
where exportValue = m.getAnExportedValue(name)
select m, name, exportValue

View File

@@ -45,16 +45,4 @@ getAnExportedValue
| lib1/main.js:1:1:17:30 | <toplevel> | foo | lib1/main.js:3:22:3:40 | require("./foo.js") |
| lib1/reexport/a.js:1:1:3:1 | <toplevel> | reexported | lib1/reexport/a.js:2:17:2:40 | functio ... ed() {} |
| lib1/reexport/b.js:1:1:6:1 | <toplevel> | base | lib1/reexport/b.js:4:11:4:28 | function base() {} |
| lib1/reexport/b.js:1:1:6:1 | <toplevel> | reexported | lib1/reexport/a.js:2:17:2:40 | functio ... ed() {} |
exports
| Baz | lib1/main.js:17:1:17:18 | module.exports.Baz | lib1/main.js:1:1:17:30 | <toplevel> |
| bar | lib1/baz.js:4:5:4:26 | bar: fu ... ar() {} | lib1/baz.js:1:1:5:1 | <toplevel> |
| bar | lib1/main.js:5:1:5:18 | module.exports.bar | lib1/main.js:1:1:17:30 | <toplevel> |
| base | lib1/reexport/b.js:4:5:4:28 | base: f ... se() {} | lib1/reexport/b.js:1:1:6:1 | <toplevel> |
| exported | esmodules/main.js:1:1:1:29 | export ... ed() {} | esmodules/main.js:1:1:4:0 | <toplevel> |
| foo | absent_main/index.js:1:1:1:18 | module.exports.foo | absent_main/index.js:1:1:2:0 | <toplevel> |
| foo | lib1/baz.js:3:5:3:26 | foo: fu ... oo() {} | lib1/baz.js:1:1:5:1 | <toplevel> |
| foo | lib1/foo.js:3:1:3:18 | module.exports.foo | lib1/foo.js:1:1:3:47 | <toplevel> |
| foo | lib1/main.js:3:1:3:18 | module.exports.foo | lib1/main.js:1:1:17:30 | <toplevel> |
| reexported | lib1/reexport/a.js:2:5:2:40 | reexpor ... ed() {} | lib1/reexport/a.js:1:1:3:1 | <toplevel> |
| reexported | lib1/reexport/a.js:2:5:2:40 | reexpor ... ed() {} | lib1/reexport/b.js:1:1:6:1 | <toplevel> |
| lib1/reexport/b.js:1:1:6:1 | <toplevel> | reexported | lib1/reexport/a.js:2:17:2:40 | functio ... ed() {} |

View File

@@ -8,7 +8,5 @@ query DataFlow::Node getAValueExportedBy(PackageJSON json) {
}
query DataFlow::Node getAnExportedValue(Module mod, string name) {
result = Exports::getAnExportedValue(mod, name)
result = mod.getAnExportedValue(name)
}
query Module exports(string name, ASTNode export) { result.exports(name, export) }

View File

@@ -1 +1 @@
| MissingExports.js:7:5:7:19 | checkOne(xs[i]) | 'checkOne' references an undeclared global variable, not the variable exported $@. | MissingExports.js:1:1:1:16 | exports.checkOne | here |
| MissingExports.js:7:5:7:19 | checkOne(xs[i]) | 'checkOne' references an undeclared global variable, not the variable exported $@. | MissingExports.js:1:20:3:1 | functio ... or();\\n} | here |