mirror of
https://github.com/github/codeql.git
synced 2026-04-28 02:05:14 +02:00
Merge pull request #1185 from xiemaisi/js/improve-amd-imports
Approved by asger-semmle
This commit is contained in:
@@ -17,6 +17,6 @@ where
|
||||
not f.inExternsFile() and
|
||||
f.getNumParameter() > 7 and
|
||||
// exclude AMD modules
|
||||
not exists(AMDModuleDefinition m | f = m.getFactoryNode().(DataFlow::FunctionNode).getAstNode())
|
||||
not exists(AmdModuleDefinition m | f = m.getFactoryNode().(DataFlow::FunctionNode).getAstNode())
|
||||
select f.(FirstLineOf),
|
||||
capitalize(f.describe()) + " has too many parameters (" + f.getNumParameter() + ")."
|
||||
|
||||
@@ -23,8 +23,8 @@ import javascript
|
||||
* where the first argument is the module name, the second argument an
|
||||
* array of dependencies, and the third argument a factory method or object.
|
||||
*/
|
||||
class AMDModuleDefinition extends CallExpr {
|
||||
AMDModuleDefinition() {
|
||||
class AmdModuleDefinition extends CallExpr {
|
||||
AmdModuleDefinition() {
|
||||
getParent() instanceof ExprStmt and
|
||||
getCallee().(GlobalVarAccess).getName() = "define" and
|
||||
exists(int n | n = getNumArgument() |
|
||||
@@ -153,7 +153,7 @@ class AMDModuleDefinition extends CallExpr {
|
||||
result = getModuleExpr().analyze().getAValue()
|
||||
or
|
||||
// explicit exports: anything assigned to `module.exports`
|
||||
exists(AbstractProperty moduleExports, AMDModule m |
|
||||
exists(AbstractProperty moduleExports, AmdModule m |
|
||||
this = m.getDefine() and
|
||||
moduleExports.getBase().(AbstractModuleObject).getModule() = m and
|
||||
moduleExports.getPropertyName() = "exports"
|
||||
@@ -170,10 +170,15 @@ class AMDModuleDefinition extends CallExpr {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* DEPRECATED: Use `AmdModuleDefinition` instead.
|
||||
*/
|
||||
deprecated class AMDModuleDefinition = AmdModuleDefinition;
|
||||
|
||||
/** An AMD dependency, considered as a path expression. */
|
||||
private class AmdDependencyPath extends PathExprCandidate {
|
||||
AmdDependencyPath() {
|
||||
exists(AMDModuleDefinition amd |
|
||||
exists(AmdModuleDefinition amd |
|
||||
this = amd.getDependencies().getAnElement() or
|
||||
this = amd.getARequireCall().getAnArgument()
|
||||
)
|
||||
@@ -191,21 +196,83 @@ private class ConstantAmdDependencyPathElement extends PathExprInModule, Constan
|
||||
* Holds if `def` is an AMD module definition in `tl` which is not
|
||||
* nested inside another module definition.
|
||||
*/
|
||||
private predicate amdModuleTopLevel(AMDModuleDefinition def, TopLevel tl) {
|
||||
private predicate amdModuleTopLevel(AmdModuleDefinition def, TopLevel tl) {
|
||||
def.getTopLevel() = tl and
|
||||
not def.getParent+() instanceof AMDModuleDefinition
|
||||
not def.getParent+() instanceof AmdModuleDefinition
|
||||
}
|
||||
|
||||
/**
|
||||
* An AMD dependency, viewed as an import.
|
||||
*/
|
||||
private class AmdDependencyImport extends Import {
|
||||
AmdDependencyImport() { this = any(AmdModuleDefinition def).getADependency() }
|
||||
|
||||
override Module getEnclosingModule() { this = result.(AmdModule).getDefine().getADependency() }
|
||||
|
||||
override PathExpr getImportedPath() { result = this }
|
||||
|
||||
/**
|
||||
* Gets a file that looks like it might be the target of this import.
|
||||
*
|
||||
* Specifically, we look for files whose absolute path ends with the imported path, possibly
|
||||
* adding well-known JavaScript file extensions like `.js`.
|
||||
*/
|
||||
private File guessTarget() {
|
||||
exists(PathString imported, string abspath, string dirname, string basename |
|
||||
targetCandidate(result, abspath, imported, dirname, basename)
|
||||
|
|
||||
abspath.regexpMatch(".*/\\Q" + imported + "\\E")
|
||||
or
|
||||
exists(Folder dir |
|
||||
// `dir` ends with the dirname of the imported path
|
||||
dir.getAbsolutePath().regexpMatch(".*/\\Q" + dirname + "\\E") or
|
||||
dirname = ""
|
||||
|
|
||||
result = dir.getJavaScriptFile(basename)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `f` is a file whose stem (that is, basename without extension) matches the imported path.
|
||||
*
|
||||
* Additionally, `abspath` is bound to the absolute path of `f`, `imported` to the imported path, and
|
||||
* `dirname` and `basename` to the dirname and basename (respectively) of `imported`.
|
||||
*/
|
||||
private predicate targetCandidate(
|
||||
File f, string abspath, PathString imported, string dirname, string basename
|
||||
) {
|
||||
imported = getImportedPath().getValue() and
|
||||
f.getStem() = imported.getStem() and
|
||||
f.getAbsolutePath() = abspath and
|
||||
dirname = imported.getDirName() and
|
||||
basename = imported.getBaseName()
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the module whose absolute path matches this import, if there is only a single such module.
|
||||
*/
|
||||
private Module resolveByAbsolutePath() {
|
||||
count(guessTarget()) = 1 and
|
||||
result.getFile() = guessTarget()
|
||||
}
|
||||
|
||||
override Module getImportedModule() {
|
||||
result = super.getImportedModule()
|
||||
or
|
||||
not exists(super.getImportedModule()) and
|
||||
result = resolveByAbsolutePath()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An AMD-style module.
|
||||
*/
|
||||
class AMDModule extends Module {
|
||||
AMDModule() { strictcount(AMDModuleDefinition def | amdModuleTopLevel(def, this)) = 1 }
|
||||
class AmdModule extends Module {
|
||||
AmdModule() { strictcount(AmdModuleDefinition def | amdModuleTopLevel(def, this)) = 1 }
|
||||
|
||||
/** Gets the definition of this module. */
|
||||
AMDModuleDefinition getDefine() { amdModuleTopLevel(result, this) }
|
||||
|
||||
override Module getAnImportedModule() { result.getFile() = resolve(getDefine().getADependency()) }
|
||||
AmdModuleDefinition getDefine() { amdModuleTopLevel(result, this) }
|
||||
|
||||
override predicate exports(string name, ASTNode export) {
|
||||
exists(DataFlow::PropWrite pwn | export = pwn.getAstNode() |
|
||||
@@ -214,3 +281,8 @@ class AMDModule extends Module {
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* DEPRECATED: Use `AmdModule` instead.
|
||||
*/
|
||||
deprecated class AMDModule = AmdModule;
|
||||
|
||||
@@ -19,8 +19,6 @@ class ES2015Module extends Module {
|
||||
/** Gets an export declaration in this module. */
|
||||
ExportDeclaration getAnExport() { result.getTopLevel() = this }
|
||||
|
||||
override Module getAnImportedModule() { result = getAnImport().getImportedModule() }
|
||||
|
||||
override predicate exports(string name, ASTNode export) {
|
||||
exists(ExportDeclaration ed | ed = getAnExport() and ed = export | ed.exportsAs(_, name))
|
||||
}
|
||||
|
||||
@@ -75,7 +75,7 @@ abstract class Container extends @container {
|
||||
* </table>
|
||||
*/
|
||||
string getBaseName() {
|
||||
result = getAbsolutePath().regexpCapture(".*/(([^/]*?)(?:\\.([^.]*))?)", 1)
|
||||
result = getAbsolutePath().regexpCapture(".*/(([^/]*?)(\\.([^.]*))?)", 1)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -101,7 +101,7 @@ abstract class Container extends @container {
|
||||
* <tr><td>"/tmp/x.tar.gz"</td><td>"gz"</td></tr>
|
||||
* </table>
|
||||
*/
|
||||
string getExtension() { result = getAbsolutePath().regexpCapture(".*/([^/]*?)(\\.([^.]*))?", 3) }
|
||||
string getExtension() { result = getAbsolutePath().regexpCapture(".*/(([^/]*?)(\\.([^.]*))?)", 4) }
|
||||
|
||||
/**
|
||||
* Gets the stem of this container, that is, the prefix of its base name up to
|
||||
@@ -120,7 +120,7 @@ abstract class Container extends @container {
|
||||
* <tr><td>"/tmp/x.tar.gz"</td><td>"x.tar"</td></tr>
|
||||
* </table>
|
||||
*/
|
||||
string getStem() { result = getAbsolutePath().regexpCapture(".*/([^/]*?)(?:\\.([^.]*))?", 1) }
|
||||
string getStem() { result = getAbsolutePath().regexpCapture(".*/(([^/]*?)(\\.([^.]*))?)", 2) }
|
||||
|
||||
/** Gets the parent container of this file or folder, if any. */
|
||||
Container getParentContainer() { containerparent(result, this) }
|
||||
|
||||
@@ -21,7 +21,7 @@ abstract class Module extends TopLevel {
|
||||
Import getAnImport() { result.getTopLevel() = this }
|
||||
|
||||
/** Gets a module from which this module imports. */
|
||||
abstract Module getAnImportedModule();
|
||||
Module getAnImportedModule() { result = getAnImport().getImportedModule() }
|
||||
|
||||
/** Gets a symbol exported by this module. */
|
||||
string getAnExportedSymbol() { exports(result, _) }
|
||||
@@ -92,8 +92,8 @@ abstract class Module extends TopLevel {
|
||||
}
|
||||
|
||||
/**
|
||||
* An import in a module, which may either be an ECMAScript 2015-style
|
||||
* `import` statement or a CommonJS-style `require` import.
|
||||
* An import in a module, which may be an ECMAScript 2015-style
|
||||
* `import` statement, a CommonJS-style `require` import, or an AMD dependency.
|
||||
*/
|
||||
abstract class Import extends ASTNode {
|
||||
/** Gets the module in which this import appears. */
|
||||
|
||||
@@ -21,9 +21,6 @@ class NodeModule extends Module {
|
||||
/** Gets the scope induced by this module. */
|
||||
override ModuleScope getScope() { result.getScopeElement() = this }
|
||||
|
||||
/** Gets a module imported by this module. */
|
||||
override Module getAnImportedModule() { result = getAnImport().getImportedModule() }
|
||||
|
||||
/**
|
||||
* Gets an abstract value representing one or more values that may flow
|
||||
* into this module's `module.exports` property.
|
||||
|
||||
@@ -76,6 +76,26 @@ private class ConsPath extends Path, TConsPath {
|
||||
override string toString() { result = pp(this) }
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a regular expression that can be used to parse slash-separated paths.
|
||||
*
|
||||
* The first capture group captures the dirname of the path, that is, everything
|
||||
* before the last slash, or the empty string if there isn't a slash.
|
||||
*
|
||||
* The second capture group captures the basename of the path, that is, everything
|
||||
* after the last slash, or the entire path if there isn't a slash.
|
||||
*
|
||||
* The third capture group captures the stem of the basename, that is, everything
|
||||
* before the last dot, or the entire basename if there isn't a dot.
|
||||
*
|
||||
* Finally, the fourth and fifth capture groups capture the extension of the basename,
|
||||
* that is, everything after the last dot. The fourth group includes the dot, the
|
||||
* fifth does not.
|
||||
*/
|
||||
private string pathRegex() {
|
||||
result = "(.*)(?:/|^)(([^/]*?)(\\.([^.]*))?)"
|
||||
}
|
||||
|
||||
/**
|
||||
* A string value that represents a (relative or absolute) file system path.
|
||||
*
|
||||
@@ -98,7 +118,17 @@ abstract class PathString extends string {
|
||||
int getNumComponent() { result = count(int i | exists(getComponent(i))) }
|
||||
|
||||
/** Gets the base name of the folder or file this path refers to. */
|
||||
string getBaseName() { result = this.regexpCapture("(.*/|^)([^/]+)", 2) }
|
||||
string getBaseName() { result = this.regexpCapture(pathRegex(), 2) }
|
||||
|
||||
/**
|
||||
* Gets stem of the folder or file this path refers to, that is, the prefix of its base name
|
||||
* up to (but not including) the last dot character if there is one, or the entire
|
||||
* base name if there is not
|
||||
*/
|
||||
string getStem() { result = this.regexpCapture(pathRegex(), 3) }
|
||||
|
||||
/** Gets the path of the parent folder of the folder or file this path refers to. */
|
||||
string getDirName() { result = this.regexpCapture(pathRegex(), 1) }
|
||||
|
||||
/**
|
||||
* Gets the absolute path that the sub-path consisting of the first `n`
|
||||
|
||||
@@ -472,12 +472,12 @@ module ModuleImportNode {
|
||||
)
|
||||
or
|
||||
// declared AMD dependency
|
||||
exists(AMDModuleDefinition amd |
|
||||
exists(AmdModuleDefinition amd |
|
||||
this = DataFlow::parameterNode(amd.getDependencyParameter(path))
|
||||
)
|
||||
or
|
||||
// AMD require
|
||||
exists(AMDModuleDefinition amd, CallExpr req |
|
||||
exists(AmdModuleDefinition amd, CallExpr req |
|
||||
req = amd.getARequireCall() and
|
||||
this = DataFlow::valueNode(req) and
|
||||
path = req.getArgument(0).(ConstantString).getStringValue()
|
||||
|
||||
@@ -61,7 +61,7 @@ private predicate mayDynamicallyComputeExports(Module m) {
|
||||
or
|
||||
// AMD modules can export arbitrary objects, so an import is essentially a property read
|
||||
// and hence must be considered indefinite
|
||||
m instanceof AMDModule
|
||||
m instanceof AmdModule
|
||||
or
|
||||
// `m` re-exports all exports of some other module that dynamically computes its exports
|
||||
exists(BulkReExportDeclaration rexp | rexp = m.(ES2015Module).getAnExport() |
|
||||
@@ -229,7 +229,7 @@ class AnalyzedExternalModuleReference extends AnalyzedPropertyRead, DataFlow::Va
|
||||
* Flow analysis for AMD exports.
|
||||
*/
|
||||
private class AnalyzedAmdExport extends AnalyzedPropertyWrite, DataFlow::ValueNode {
|
||||
AMDModule amd;
|
||||
AmdModule amd;
|
||||
|
||||
AnalyzedAmdExport() { astNode = amd.getDefine().getModuleExpr() }
|
||||
|
||||
@@ -248,7 +248,7 @@ private class AnalyzedAmdImport extends AnalyzedPropertyRead, DataFlow::Node {
|
||||
Module required;
|
||||
|
||||
AnalyzedAmdImport() {
|
||||
exists(AMDModule amd, PathExpr dep, Parameter p |
|
||||
exists(AmdModule amd, PathExpr dep, Parameter p |
|
||||
amd.getDefine().dependencyParameter(dep, p) and
|
||||
this = DataFlow::parameterNode(p) and
|
||||
required.getFile() = amd.resolve(dep)
|
||||
|
||||
@@ -180,7 +180,7 @@ private class AnalyzedAmdParameter extends AnalyzedVarDef {
|
||||
AbstractValue implicitInitVal;
|
||||
|
||||
AnalyzedAmdParameter() {
|
||||
exists(AMDModule m, AMDModuleDefinition mdef | mdef = m.getDefine() |
|
||||
exists(AmdModule m, AmdModuleDefinition mdef | mdef = m.getDefine() |
|
||||
this = mdef.getModuleParameter() and
|
||||
implicitInitVal = TAbstractModuleObject(m)
|
||||
or
|
||||
|
||||
@@ -57,7 +57,7 @@ module TaintedPath {
|
||||
ModulePathSink() {
|
||||
astNode = any(Require rq).getArgument(0) or
|
||||
astNode = any(ExternalModuleReference rq).getExpression() or
|
||||
astNode = any(AMDModuleDefinition amd).getDependencies()
|
||||
astNode = any(AmdModuleDefinition amd).getDependencies()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user