Skip to content

Commit

Permalink
Merge pull request #16241 from asgerf/js/re-export
Browse files Browse the repository at this point in the history
JS: Improve support for `export * as ...` declarations
  • Loading branch information
asgerf authored Apr 19, 2024
2 parents 18acad5 + da33c22 commit ac34b92
Show file tree
Hide file tree
Showing 13 changed files with 67 additions and 3 deletions.
20 changes: 18 additions & 2 deletions javascript/ql/lib/semmle/javascript/ApiGraphs.qll
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

import javascript
private import semmle.javascript.dataflow.internal.FlowSteps as FlowSteps
private import semmle.javascript.dataflow.internal.PreCallGraphStep
private import internal.CachedStages

/**
Expand Down Expand Up @@ -782,6 +783,13 @@ module API {
rhs = m.getAnExportedValue(prop)
)
or
// In general, turn store steps into member steps for def-nodes
exists(string prop |
PreCallGraphStep::storeStep(rhs, pred, prop) and
lbl = Label::member(prop) and
not DataFlow::PseudoProperties::isPseudoProperty(prop)
)
or
exists(DataFlow::FunctionNode fn |
fn = pred and
lbl = Label::return()
Expand Down Expand Up @@ -947,7 +955,6 @@ module API {
(base instanceof TNonModuleDef or base instanceof TUse)
)
or
// invocations
exists(DataFlow::SourceNode src, DataFlow::SourceNode pred |
use(base, src) and pred = trackUseNode(src)
|
Expand All @@ -968,6 +975,13 @@ module API {
or
ref = cls.getAClassReference().getAnInstantiation()
)
or
exists(string prop |
PreCallGraphStep::loadStep(pred.getALocalUse(), ref, prop) and
lbl = Label::member(prop) and
// avoid generating member edges like "$arrayElement$"
not DataFlow::PseudoProperties::isPseudoProperty(prop)
)
)
or
exists(DataFlow::Node def, DataFlow::FunctionNode fn |
Expand Down Expand Up @@ -1535,7 +1549,9 @@ module API {
prop = any(CanonicalName c).getName() or
prop = any(DataFlow::PropRef p).getPropertyName() or
exists(Impl::MkTypeUse(_, prop)) or
exists(any(Module m).getAnExportedValue(prop))
exists(any(Module m).getAnExportedValue(prop)) or
PreCallGraphStep::loadStep(_, _, prop) or
PreCallGraphStep::storeStep(_, _, prop)
} or
MkLabelUnknownMember() or
MkLabelParameter(int i) {
Expand Down
16 changes: 16 additions & 0 deletions javascript/ql/lib/semmle/javascript/ES2015Modules.qll
Original file line number Diff line number Diff line change
Expand Up @@ -510,6 +510,9 @@ class ExportNamedDeclaration extends ExportDeclaration, @export_named_declaratio
or
exists(ReExportDeclaration red | red = this |
result = red.getReExportedES2015Module().getAnExport().getSourceNode(spec.getLocalName())
or
spec instanceof ExportNamespaceSpecifier and
result = DataFlow::valueNode(spec)
)
)
}
Expand All @@ -524,6 +527,19 @@ class ExportNamedDeclaration extends ExportDeclaration, @export_named_declaratio
ExportSpecifier getASpecifier() { result = this.getSpecifier(_) }
}

private import semmle.javascript.dataflow.internal.PreCallGraphStep

private class ExportNamespaceStep extends PreCallGraphStep {
override predicate storeStep(DataFlow::Node pred, DataFlow::SourceNode succ, string prop) {
exists(ExportNamedDeclaration exprt, ExportNamespaceSpecifier spec |
spec = exprt.getASpecifier() and
pred =
exprt.(ReExportDeclaration).getReExportedES2015Module().getAnExport().getSourceNode(prop) and
succ = DataFlow::valueNode(spec)
)
}
}

/**
* An export declaration with the `type` modifier.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -704,6 +704,10 @@ module SharedFlowStep {
* For use with load/store steps in `DataFlow::SharedFlowStep` and TypeTracking.
*/
module PseudoProperties {
/** Holds if `s` is a pseudo-property. */
bindingset[s]
predicate isPseudoProperty(string s) { s.matches("$%$") }

bindingset[s]
private string pseudoProperty(string s) { result = "$" + s + "$" }

Expand Down
1 change: 1 addition & 0 deletions javascript/ql/lib/semmle/javascript/dataflow/Sources.qll
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,7 @@ module SourceNode {
astNode instanceof FunctionBindExpr or
astNode instanceof DynamicImportExpr or
astNode instanceof ImportSpecifier or
astNode instanceof ExportNamespaceSpecifier or
astNode instanceof ImportMetaExpr or
astNode instanceof TaggedTemplateExpr or
astNode instanceof Templating::PipeRefExpr or
Expand Down
Empty file.
13 changes: 13 additions & 0 deletions javascript/ql/test/ApiGraphs/custom-use-steps/VerifyAssertions.ql
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import ApiGraphs.VerifyAssertions
private import semmle.javascript.dataflow.internal.PreCallGraphStep

class CustomUseStep extends PreCallGraphStep {
override predicate loadStep(DataFlow::Node pred, DataFlow::Node succ, string prop) {
exists(DataFlow::CallNode call |
call.getCalleeName() = "customLoad" and
pred = call.getArgument(0) and
succ = call and
prop = call.getArgument(1).getStringValue()
)
}
}
4 changes: 4 additions & 0 deletions javascript/ql/test/ApiGraphs/custom-use-steps/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
const foo = require("foo");

foo.bar; // use=moduleImport("foo").getMember("exports").getMember("bar")
customLoad(foo, "baz") // use=moduleImport("foo").getMember("exports").getMember("baz")
3 changes: 3 additions & 0 deletions javascript/ql/test/ApiGraphs/custom-use-steps/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"name": "custom-use-steps"
}
3 changes: 2 additions & 1 deletion javascript/ql/test/ApiGraphs/reexport/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@ module.exports = {
impl,
util: require("./lib/utils"),
other: require("./lib/stuff"),
util2: require("./lib/utils2")
util2: require("./lib/utils2"),
esmodule: require("./lib/esmodule-reexport"),
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from "./esmodule-reexported1";
export * as lib2 from "./esmodule-reexported2";
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export function one() {} /* def=moduleImport("reexport").getMember("exports").getMember("esmodule").getMember("one") */
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export function two() {} /* def=moduleImport("reexport").getMember("exports").getMember("esmodule").getMember("lib2").getMember("two") */
2 changes: 2 additions & 0 deletions javascript/ql/test/library-tests/Modules/tests.expected
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ test_Module_exports
| 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 |
| reExportNamespace.js:1:1:2:0 | <toplevel> | ns | reExportNamespace.js:1:8:1:14 | * as ns |
| 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 |
Expand Down Expand Up @@ -149,4 +150,5 @@ test_getSourceNode
| export-in-mjs.mjs:1:1:1:34 | export ... s = 42; | exported_from_mjs | export-in-mjs.mjs:1:32:1:33 | 42 |
| f.ts:5:1:5:24 | export ... oo() {} | foo | f.ts:5:8:5:24 | function foo() {} |
| m/c.js:5:1:5:30 | export ... '../b'; | h | b.js:5:10:5:10 | f |
| reExportNamespace.js:1:1:1:26 | export ... "./a"; | ns | reExportNamespace.js:1:8:1:14 | * as ns |
| tst.html:7:3:7:22 | export const y = 42; | y | tst.html:7:20:7:21 | 42 |

0 comments on commit ac34b92

Please sign in to comment.