From 7ae28ceee0ab4362d84df2b70a7aeb41e9b308ac Mon Sep 17 00:00:00 2001 From: Asger F Date: Mon, 4 Mar 2024 15:46:55 +0100 Subject: [PATCH] More Module interop code --- .../dataflow/internal/ModuleInterop.qll | 58 +++++++++++++++---- 1 file changed, 47 insertions(+), 11 deletions(-) diff --git a/javascript/ql/lib/semmle/javascript/dataflow/internal/ModuleInterop.qll b/javascript/ql/lib/semmle/javascript/dataflow/internal/ModuleInterop.qll index 47a26350f97..933fe817a8b 100644 --- a/javascript/ql/lib/semmle/javascript/dataflow/internal/ModuleInterop.qll +++ b/javascript/ql/lib/semmle/javascript/dataflow/internal/ModuleInterop.qll @@ -1,24 +1,32 @@ private import javascript private import PreCallGraphStep as PCG -class ES2015InteropCall extends DataFlow::CallNode { - ES2015InteropCall() { - this.getCalleeName() = ["_interopRequireDefault", "_interopRequireWildcard"] - } - - private Import getAnImport() { +/** + * Base class for calls whose first argument is an imported module. + */ +abstract private class ModuleInteropCall extends DataFlow::CallNode { + private Import getImport() { this.getArgument(0).getALocalSource() = result.getImportedModuleNode() } - Module getImportedModule() { result = this.getAnImport().getImportedModule() } + /** Gets the module that is passed as the first argument. */ + Module getImportedModule() { result = this.getImport().getImportedModule() } + /** Holds if the imported module is an ES2015 module or a compiled version thereof. */ predicate isImportingESModule() { this.getImportedModule() instanceof ES2015Module or exists(this.getImportedModule().getAnExportedValue("__esModule")) } +} - DataFlow::SourceNode getImportedObject() { +/** A call that wraps the imported object in a `default` property, unless it is from an ES2015 module. */ +private class InteropRequireCall extends ModuleInteropCall { + InteropRequireCall() { + this.getCalleeName() = ["_interopRequireDefault", "_interopRequireWildcard"] + } + + DataFlow::SourceNode getImportedObjectRef() { this.isImportingESModule() and result = this or @@ -27,14 +35,42 @@ class ES2015InteropCall extends DataFlow::CallNode { } } +/** + * A call that read the `default` property if it exists, otherwise returns the object as-is. + */ +private class InteropDefaultCall extends ModuleInteropCall { + InteropDefaultCall() { this.getCalleeName() = "_interopDefault" } + + DataFlow::Node getDefaultExport() { + result = this.getImportedModule().getAnExportedValue("default") + } + + DataFlow::Node getImportedObjectSource() { + result = this.getDefaultExport() + or + not exists(this.getDefaultExport()) and + result = this.getArgument(0) + } +} + predicate interopModuleStep(DataFlow::Node pred, DataFlow::Node succ) { - exists(ES2015InteropCall call | + exists(InteropRequireCall call | pred = call.getArgument(0) and - succ = call.getImportedObject() + succ = call.getImportedObjectRef() or exists(string prop | pred = call.getImportedModule().getAnExportedValue(prop) and - succ = call.getImportedObject().getAPropertyRead(prop) + succ = call.getImportedObjectRef().getAPropertyRead(prop) + ) + ) + or + exists(InteropDefaultCall call | + pred = call.getImportedObjectSource() and + succ = call + or + exists(string prop | + pred = call.getDefaultExport().getALocalSource().getAPropertyWrite(prop).getRhs() and + succ = call.getAPropertyRead(prop) ) ) }