mirror of
https://github.com/github/codeql.git
synced 2026-04-26 01:05:15 +02:00
Merge branch 'main' into js/quality/stream_pipe
This commit is contained in:
@@ -404,7 +404,7 @@ public class AutoBuild {
|
||||
patterns.add("**/*.view.json"); // SAP UI5
|
||||
patterns.add("**/manifest.json");
|
||||
patterns.add("**/package.json");
|
||||
patterns.add("**/tsconfig*.json");
|
||||
patterns.add("**/*tsconfig*.json");
|
||||
patterns.add("**/codeql-javascript-*.json");
|
||||
|
||||
// include any explicitly specified extensions
|
||||
|
||||
@@ -29,7 +29,7 @@ public class JSONExtractor implements IExtractor {
|
||||
private final boolean tolerateParseErrors;
|
||||
|
||||
public JSONExtractor(ExtractorConfig config) {
|
||||
this.tolerateParseErrors = config.isTolerateParseErrors();
|
||||
this.tolerateParseErrors = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -301,9 +301,14 @@ public class Main {
|
||||
// only extract HTML and JS by default
|
||||
addIncludesFor(includes, FileType.HTML);
|
||||
addIncludesFor(includes, FileType.JS);
|
||||
includes.add("**/.babelrc*.json");
|
||||
|
||||
|
||||
// extract TypeScript if `--typescript` or `--typescript-full` was specified
|
||||
if (getTypeScriptMode(ap) != TypeScriptMode.NONE) addIncludesFor(includes, FileType.TYPESCRIPT);
|
||||
if (getTypeScriptMode(ap) != TypeScriptMode.NONE) {
|
||||
addIncludesFor(includes, FileType.TYPESCRIPT);
|
||||
includes.add("**/*tsconfig*.json");
|
||||
}
|
||||
|
||||
// add explicit include patterns
|
||||
for (String pattern : ap.getZeroOrMore(P_INCLUDE))
|
||||
|
||||
@@ -11,5 +11,5 @@
|
||||
import javascript
|
||||
|
||||
from ImportDeclaration id
|
||||
where id.getImportedPath().getValue() = "react"
|
||||
where id.getImportedPathString() = "react"
|
||||
select id
|
||||
|
||||
@@ -75,7 +75,6 @@ ql/javascript/ql/src/Security/CWE-754/UnvalidatedDynamicMethodCall.ql
|
||||
ql/javascript/ql/src/Security/CWE-770/MissingRateLimiting.ql
|
||||
ql/javascript/ql/src/Security/CWE-770/ResourceExhaustion.ql
|
||||
ql/javascript/ql/src/Security/CWE-776/XmlBomb.ql
|
||||
ql/javascript/ql/src/Security/CWE-798/HardcodedCredentials.ql
|
||||
ql/javascript/ql/src/Security/CWE-829/InsecureDownload.ql
|
||||
ql/javascript/ql/src/Security/CWE-830/FunctionalityFromUntrustedDomain.ql
|
||||
ql/javascript/ql/src/Security/CWE-830/FunctionalityFromUntrustedSource.ql
|
||||
|
||||
@@ -145,7 +145,6 @@ ql/javascript/ql/src/Security/CWE-312/ActionsArtifactLeak.ql
|
||||
ql/javascript/ql/src/Security/CWE-312/BuildArtifactLeak.ql
|
||||
ql/javascript/ql/src/Security/CWE-312/CleartextLogging.ql
|
||||
ql/javascript/ql/src/Security/CWE-312/CleartextStorage.ql
|
||||
ql/javascript/ql/src/Security/CWE-313/PasswordInConfigurationFile.ql
|
||||
ql/javascript/ql/src/Security/CWE-326/InsufficientKeySize.ql
|
||||
ql/javascript/ql/src/Security/CWE-327/BadRandomness.ql
|
||||
ql/javascript/ql/src/Security/CWE-327/BrokenCryptoAlgorithm.ql
|
||||
@@ -174,7 +173,6 @@ ql/javascript/ql/src/Security/CWE-754/UnvalidatedDynamicMethodCall.ql
|
||||
ql/javascript/ql/src/Security/CWE-770/MissingRateLimiting.ql
|
||||
ql/javascript/ql/src/Security/CWE-770/ResourceExhaustion.ql
|
||||
ql/javascript/ql/src/Security/CWE-776/XmlBomb.ql
|
||||
ql/javascript/ql/src/Security/CWE-798/HardcodedCredentials.ql
|
||||
ql/javascript/ql/src/Security/CWE-807/ConditionalBypass.ql
|
||||
ql/javascript/ql/src/Security/CWE-829/InsecureDownload.ql
|
||||
ql/javascript/ql/src/Security/CWE-830/FunctionalityFromUntrustedDomain.ql
|
||||
|
||||
@@ -59,7 +59,6 @@ ql/javascript/ql/src/Security/CWE-312/ActionsArtifactLeak.ql
|
||||
ql/javascript/ql/src/Security/CWE-312/BuildArtifactLeak.ql
|
||||
ql/javascript/ql/src/Security/CWE-312/CleartextLogging.ql
|
||||
ql/javascript/ql/src/Security/CWE-312/CleartextStorage.ql
|
||||
ql/javascript/ql/src/Security/CWE-313/PasswordInConfigurationFile.ql
|
||||
ql/javascript/ql/src/Security/CWE-326/InsufficientKeySize.ql
|
||||
ql/javascript/ql/src/Security/CWE-327/BadRandomness.ql
|
||||
ql/javascript/ql/src/Security/CWE-327/BrokenCryptoAlgorithm.ql
|
||||
@@ -88,7 +87,6 @@ ql/javascript/ql/src/Security/CWE-754/UnvalidatedDynamicMethodCall.ql
|
||||
ql/javascript/ql/src/Security/CWE-770/MissingRateLimiting.ql
|
||||
ql/javascript/ql/src/Security/CWE-770/ResourceExhaustion.ql
|
||||
ql/javascript/ql/src/Security/CWE-776/XmlBomb.ql
|
||||
ql/javascript/ql/src/Security/CWE-798/HardcodedCredentials.ql
|
||||
ql/javascript/ql/src/Security/CWE-807/ConditionalBypass.ql
|
||||
ql/javascript/ql/src/Security/CWE-829/InsecureDownload.ql
|
||||
ql/javascript/ql/src/Security/CWE-830/FunctionalityFromUntrustedDomain.ql
|
||||
|
||||
@@ -53,7 +53,9 @@ ql/javascript/ql/src/RegExp/BackspaceEscape.ql
|
||||
ql/javascript/ql/src/RegExp/MalformedRegExp.ql
|
||||
ql/javascript/ql/src/Security/CWE-020/ExternalAPIsUsedWithUntrustedData.ql
|
||||
ql/javascript/ql/src/Security/CWE-020/UntrustedDataToExternalAPI.ql
|
||||
ql/javascript/ql/src/Security/CWE-313/PasswordInConfigurationFile.ql
|
||||
ql/javascript/ql/src/Security/CWE-451/MissingXFrameOptions.ql
|
||||
ql/javascript/ql/src/Security/CWE-798/HardcodedCredentials.ql
|
||||
ql/javascript/ql/src/Security/CWE-807/DifferentKindsComparisonBypass.ql
|
||||
ql/javascript/ql/src/Security/trest/test.ql
|
||||
ql/javascript/ql/src/Statements/EphemeralLoop.ql
|
||||
|
||||
@@ -1,3 +1,11 @@
|
||||
## 2.6.3
|
||||
|
||||
### Minor Analysis Improvements
|
||||
|
||||
* Enhanced modeling of the [fastify](https://www.npmjs.com/package/fastify) framework to support the `all` route handler method.
|
||||
* Improved modeling of the [`shelljs`](https://www.npmjs.com/package/shelljs) and [`async-shelljs`](https://www.npmjs.com/package/async-shelljs) libraries by adding support for the `which`, `cmd`, `asyncExec` and `env`.
|
||||
* Added support for the `fastify` `addHook` method.
|
||||
|
||||
## 2.6.2
|
||||
|
||||
No user-facing changes.
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
---
|
||||
category: minorAnalysis
|
||||
---
|
||||
* Added support for the `fastify` `addHook` method.
|
||||
@@ -0,0 +1,4 @@
|
||||
---
|
||||
category: minorAnalysis
|
||||
---
|
||||
* Improved analysis for `ES6 classes` mixed with `function prototypes`, leading to more accurate call graph resolution.
|
||||
@@ -1,4 +0,0 @@
|
||||
---
|
||||
category: minorAnalysis
|
||||
---
|
||||
* Enhanced modeling of the [fastify](https://www.npmjs.com/package/fastify) framework to support the `all` route handler method.
|
||||
@@ -1,4 +0,0 @@
|
||||
---
|
||||
category: minorAnalysis
|
||||
---
|
||||
* Improved modeling of the [`shelljs`](https://www.npmjs.com/package/shelljs) and [`async-shelljs`](https://www.npmjs.com/package/async-shelljs) libraries by adding support for the `which`, `cmd`, `asyncExec` and `env`.
|
||||
@@ -0,0 +1,4 @@
|
||||
---
|
||||
category: minorAnalysis
|
||||
---
|
||||
* Added taint flow through the `URL` constructor from the `url` package, improving the identification of SSRF vulnerabilities.
|
||||
7
javascript/ql/lib/change-notes/released/2.6.3.md
Normal file
7
javascript/ql/lib/change-notes/released/2.6.3.md
Normal file
@@ -0,0 +1,7 @@
|
||||
## 2.6.3
|
||||
|
||||
### Minor Analysis Improvements
|
||||
|
||||
* Enhanced modeling of the [fastify](https://www.npmjs.com/package/fastify) framework to support the `all` route handler method.
|
||||
* Improved modeling of the [`shelljs`](https://www.npmjs.com/package/shelljs) and [`async-shelljs`](https://www.npmjs.com/package/async-shelljs) libraries by adding support for the `which`, `cmd`, `asyncExec` and `env`.
|
||||
* Added support for the `fastify` `addHook` method.
|
||||
@@ -1,2 +1,2 @@
|
||||
---
|
||||
lastReleaseVersion: 2.6.2
|
||||
lastReleaseVersion: 2.6.3
|
||||
|
||||
@@ -70,7 +70,7 @@ private predicate importLookup(AstNode path, Module target, string kind) {
|
||||
kind = "I" and
|
||||
(
|
||||
exists(Import i |
|
||||
path = i.getImportedPath() and
|
||||
path = i.getImportedPathExpr() and
|
||||
target = i.getImportedModule()
|
||||
)
|
||||
or
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
name: codeql/javascript-all
|
||||
version: 2.6.3-dev
|
||||
version: 2.6.4-dev
|
||||
groups: javascript
|
||||
dbscheme: semmlecode.javascript.dbscheme
|
||||
extractor: javascript
|
||||
|
||||
@@ -61,8 +61,14 @@ class AmdModuleDefinition extends CallExpr instanceof AmdModuleDefinition::Range
|
||||
result = this.getArgument(1)
|
||||
}
|
||||
|
||||
/** DEPRECATED. Use `getDependencyExpr` instead. */
|
||||
deprecated PathExpr getDependency(int i) { result = this.getDependencyExpr(i) }
|
||||
|
||||
/** DEPRECATED. Use `getADependencyExpr` instead. */
|
||||
deprecated PathExpr getADependency() { result = this.getADependencyExpr() }
|
||||
|
||||
/** Gets the `i`th dependency of this module definition. */
|
||||
PathExpr getDependency(int i) {
|
||||
Expr getDependencyExpr(int i) {
|
||||
exists(Expr expr |
|
||||
expr = this.getDependencies().getElement(i) and
|
||||
not isPseudoDependency(expr.getStringValue()) and
|
||||
@@ -71,8 +77,8 @@ class AmdModuleDefinition extends CallExpr instanceof AmdModuleDefinition::Range
|
||||
}
|
||||
|
||||
/** Gets a dependency of this module definition. */
|
||||
PathExpr getADependency() {
|
||||
result = this.getDependency(_) or
|
||||
Expr getADependencyExpr() {
|
||||
result = this.getDependencyExpr(_) or
|
||||
result = this.getARequireCall().getAnArgument()
|
||||
}
|
||||
|
||||
@@ -233,7 +239,7 @@ private class AmdDependencyPath extends PathExprCandidate {
|
||||
}
|
||||
|
||||
/** A constant path element appearing in an AMD dependency expression. */
|
||||
private class ConstantAmdDependencyPathElement extends PathExpr, ConstantString {
|
||||
deprecated private class ConstantAmdDependencyPathElement extends PathExpr, ConstantString {
|
||||
ConstantAmdDependencyPathElement() { this = any(AmdDependencyPath amd).getAPart() }
|
||||
|
||||
override string getValue() { result = this.getStringValue() }
|
||||
@@ -261,11 +267,13 @@ private predicate amdModuleTopLevel(AmdModuleDefinition def, TopLevel tl) {
|
||||
* An AMD dependency, viewed as an import.
|
||||
*/
|
||||
private class AmdDependencyImport extends Import {
|
||||
AmdDependencyImport() { this = any(AmdModuleDefinition def).getADependency() }
|
||||
AmdDependencyImport() { this = any(AmdModuleDefinition def).getADependencyExpr() }
|
||||
|
||||
override Module getEnclosingModule() { this = result.(AmdModule).getDefine().getADependency() }
|
||||
override Module getEnclosingModule() {
|
||||
this = result.(AmdModule).getDefine().getADependencyExpr()
|
||||
}
|
||||
|
||||
override PathExpr getImportedPath() { result = this }
|
||||
override Expr getImportedPathExpr() { result = this }
|
||||
|
||||
/**
|
||||
* Gets a file that looks like it might be the target of this import.
|
||||
@@ -274,7 +282,7 @@ private class AmdDependencyImport extends Import {
|
||||
* adding well-known JavaScript file extensions like `.js`.
|
||||
*/
|
||||
private File guessTarget() {
|
||||
exists(PathString imported, string abspath, string dirname, string basename |
|
||||
exists(FilePath imported, string abspath, string dirname, string basename |
|
||||
this.targetCandidate(result, abspath, imported, dirname, basename)
|
||||
|
|
||||
abspath.regexpMatch(".*/\\Q" + imported + "\\E")
|
||||
@@ -296,9 +304,9 @@ private class AmdDependencyImport extends Import {
|
||||
* `dirname` and `basename` to the dirname and basename (respectively) of `imported`.
|
||||
*/
|
||||
private predicate targetCandidate(
|
||||
File f, string abspath, PathString imported, string dirname, string basename
|
||||
File f, string abspath, FilePath imported, string dirname, string basename
|
||||
) {
|
||||
imported = this.getImportedPath().getValue() and
|
||||
imported = this.getImportedPathString() and
|
||||
f.getStem() = imported.getStem() and
|
||||
f.getAbsolutePath() = abspath and
|
||||
dirname = imported.getDirName() and
|
||||
|
||||
@@ -1236,7 +1236,7 @@ module API {
|
||||
exists(DataFlow::ClassNode cls | nd = MkClassInstance(cls) |
|
||||
ref = cls.getAReceiverNode()
|
||||
or
|
||||
ref = cls.(DataFlow::ClassNode::FunctionStyleClass).getAPrototypeReference()
|
||||
ref = cls.(DataFlow::ClassNode).getAPrototypeReference()
|
||||
)
|
||||
or
|
||||
nd = MkUse(ref)
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
import javascript
|
||||
private import semmle.javascript.internal.CachedStages
|
||||
private import semmle.javascript.internal.paths.PathExprResolver
|
||||
|
||||
/**
|
||||
* An ECMAScript 2015 module.
|
||||
@@ -91,7 +92,12 @@ private predicate hasDefaultExport(ES2015Module mod) {
|
||||
class ImportDeclaration extends Stmt, Import, @import_declaration {
|
||||
override ES2015Module getEnclosingModule() { result = this.getTopLevel() }
|
||||
|
||||
override PathExpr getImportedPath() { result = this.getChildExpr(-1) }
|
||||
/**
|
||||
* INTERNAL USE ONLY. DO NOT USE.
|
||||
*/
|
||||
string getRawImportPath() { result = this.getChildExpr(-1).getStringValue() }
|
||||
|
||||
override Expr getImportedPathExpr() { result = this.getChildExpr(-1) }
|
||||
|
||||
/**
|
||||
* Gets the object literal passed as part of the `with` (or `assert`) clause in this import declaration.
|
||||
@@ -149,7 +155,7 @@ class ImportDeclaration extends Stmt, Import, @import_declaration {
|
||||
}
|
||||
|
||||
/** A literal path expression appearing in an `import` declaration. */
|
||||
private class LiteralImportPath extends PathExpr, ConstantString {
|
||||
deprecated private class LiteralImportPath extends PathExpr, ConstantString {
|
||||
LiteralImportPath() { exists(ImportDeclaration req | this = req.getChildExpr(-1)) }
|
||||
|
||||
override string getValue() { result = this.getStringValue() }
|
||||
@@ -725,27 +731,12 @@ abstract class ReExportDeclaration extends ExportDeclaration {
|
||||
cached
|
||||
Module getReExportedModule() {
|
||||
Stages::Imports::ref() and
|
||||
result.getFile() = this.getEnclosingModule().resolve(this.getImportedPath())
|
||||
or
|
||||
result = this.resolveFromTypeRoot()
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a module in a `node_modules/@types/` folder that matches the imported module name.
|
||||
*/
|
||||
private Module resolveFromTypeRoot() {
|
||||
result.getFile() =
|
||||
min(TypeRootFolder typeRoot |
|
||||
|
|
||||
typeRoot.getModuleFile(this.getImportedPath().getStringValue())
|
||||
order by
|
||||
typeRoot.getSearchPriority(this.getFile().getParentContainer())
|
||||
)
|
||||
result.getFile() = ImportPathResolver::resolveExpr(this.getImportedPath())
|
||||
}
|
||||
}
|
||||
|
||||
/** A literal path expression appearing in a re-export declaration. */
|
||||
private class LiteralReExportPath extends PathExpr, ConstantString {
|
||||
deprecated private class LiteralReExportPath extends PathExpr, ConstantString {
|
||||
LiteralReExportPath() { exists(ReExportDeclaration bred | this = bred.getImportedPath()) }
|
||||
|
||||
override string getValue() { result = this.getStringValue() }
|
||||
|
||||
@@ -2821,7 +2821,7 @@ class DynamicImportExpr extends @dynamic_import, Expr, Import {
|
||||
result = this.getSource().getFirstControlFlowNode()
|
||||
}
|
||||
|
||||
override PathExpr getImportedPath() { result = this.getSource() }
|
||||
override Expr getImportedPathExpr() { result = this.getSource() }
|
||||
|
||||
/**
|
||||
* Gets the second "argument" to the import expression, that is, the `Y` in `import(X, Y)`.
|
||||
@@ -2852,7 +2852,7 @@ class DynamicImportExpr extends @dynamic_import, Expr, Import {
|
||||
}
|
||||
|
||||
/** A literal path expression appearing in a dynamic import. */
|
||||
private class LiteralDynamicImportPath extends PathExpr, ConstantString {
|
||||
deprecated private class LiteralDynamicImportPath extends PathExpr, ConstantString {
|
||||
LiteralDynamicImportPath() {
|
||||
exists(DynamicImportExpr di | this.getParentExpr*() = di.getSource())
|
||||
}
|
||||
|
||||
@@ -29,6 +29,8 @@ private module Impl = Make<FsInput>;
|
||||
|
||||
class Container = Impl::Container;
|
||||
|
||||
module Folder = Impl::Folder;
|
||||
|
||||
/** A folder. */
|
||||
class Folder extends Container, Impl::Folder {
|
||||
/** Gets the file or subfolder in this folder that has the given `name`, if any. */
|
||||
@@ -73,6 +75,19 @@ class Folder extends Container, Impl::Folder {
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an implementation file and/or a typings file from this folder that has the given `stem`.
|
||||
* This could be a single `.ts` file or a pair of `.js` and `.d.ts` files.
|
||||
*/
|
||||
File getJavaScriptFileOrTypings(string stem) {
|
||||
exists(File jsFile | jsFile = this.getJavaScriptFile(stem) |
|
||||
result = jsFile
|
||||
or
|
||||
not jsFile.getFileType().isTypeScript() and
|
||||
result = this.getFile(stem + ".d.ts")
|
||||
)
|
||||
}
|
||||
|
||||
/** Gets a subfolder contained in this folder. */
|
||||
Folder getASubFolder() { result = this.getAChildContainer() }
|
||||
}
|
||||
|
||||
@@ -214,7 +214,7 @@ module HTML {
|
||||
result = path.regexpCapture("file://(/.*)", 1)
|
||||
or
|
||||
not path.regexpMatch("(\\w+:)?//.*") and
|
||||
result = this.getSourcePath().(ScriptSrcPath).resolve(this.getSearchRoot()).toString()
|
||||
result = ResolveScriptSrc::resolve(this.getSearchRoot(), this.getSourcePath()).toString()
|
||||
)
|
||||
}
|
||||
|
||||
@@ -274,10 +274,16 @@ module HTML {
|
||||
)
|
||||
}
|
||||
|
||||
private module ResolverConfig implements Folder::ResolveSig {
|
||||
predicate shouldResolve(Container base, string path) { scriptSrc(path, base) }
|
||||
}
|
||||
|
||||
private module ResolveScriptSrc = Folder::Resolve<ResolverConfig>;
|
||||
|
||||
/**
|
||||
* A path string arising from the `src` attribute of a `script` tag.
|
||||
*/
|
||||
private class ScriptSrcPath extends PathString {
|
||||
deprecated private class ScriptSrcPath extends PathString {
|
||||
ScriptSrcPath() { scriptSrc(this, _) }
|
||||
|
||||
override Folder getARootFolder() { scriptSrc(this, result) }
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
import javascript
|
||||
private import semmle.javascript.internal.CachedStages
|
||||
private import semmle.javascript.internal.paths.PathExprResolver
|
||||
|
||||
/**
|
||||
* A module, which may either be an ECMAScript 2015-style module,
|
||||
@@ -68,7 +69,7 @@ abstract class Module extends TopLevel {
|
||||
* This predicate is not part of the public API, it is only exposed to allow
|
||||
* overriding by subclasses.
|
||||
*/
|
||||
predicate searchRoot(PathExpr path, Folder searchRoot, int priority) {
|
||||
deprecated predicate searchRoot(PathExpr path, Folder searchRoot, int priority) {
|
||||
path.getEnclosingModule() = this and
|
||||
priority = 0 and
|
||||
exists(string v | v = path.getValue() |
|
||||
@@ -89,7 +90,7 @@ abstract class Module extends TopLevel {
|
||||
* resolves to a folder containing a main module (such as `index.js`), then
|
||||
* that file is the result.
|
||||
*/
|
||||
File resolve(PathExpr path) {
|
||||
deprecated File resolve(PathExpr path) {
|
||||
path.getEnclosingModule() = this and
|
||||
(
|
||||
// handle the case where the import path is complete
|
||||
@@ -122,8 +123,14 @@ abstract class Import extends AstNode {
|
||||
/** Gets the module in which this import appears. */
|
||||
abstract Module getEnclosingModule();
|
||||
|
||||
/** DEPRECATED. Use `getImportedPathExpr` instead. */
|
||||
deprecated PathExpr getImportedPath() { result = this.getImportedPathExpr() }
|
||||
|
||||
/** Gets the (unresolved) path that this import refers to. */
|
||||
abstract PathExpr getImportedPath();
|
||||
abstract Expr getImportedPathExpr();
|
||||
|
||||
/** Gets the imported path as a string. */
|
||||
final string getImportedPathString() { result = this.getImportedPathExpr().getStringValue() }
|
||||
|
||||
/**
|
||||
* Gets an externs module the path of this import resolves to.
|
||||
@@ -132,45 +139,23 @@ abstract class Import extends AstNode {
|
||||
* path is assumed to be a possible target of the import.
|
||||
*/
|
||||
Module resolveExternsImport() {
|
||||
result.isExterns() and result.getName() = this.getImportedPath().getValue()
|
||||
result.isExterns() and result.getName() = this.getImportedPathString()
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the module the path of this import resolves to.
|
||||
*/
|
||||
Module resolveImportedPath() {
|
||||
result.getFile() = this.getEnclosingModule().resolve(this.getImportedPath())
|
||||
}
|
||||
Module resolveImportedPath() { result.getFile() = this.getImportedFile() }
|
||||
|
||||
/**
|
||||
* Gets a module with a `@providesModule` JSDoc tag that matches
|
||||
* the imported path.
|
||||
* Gets the module the path of this import resolves to.
|
||||
*/
|
||||
private Module resolveAsProvidedModule() {
|
||||
exists(JSDocTag tag |
|
||||
tag.getTitle() = "providesModule" and
|
||||
tag.getParent().getComment().getTopLevel() = result and
|
||||
tag.getDescription().trim() = this.getImportedPath().getValue()
|
||||
)
|
||||
}
|
||||
File getImportedFile() { result = ImportPathResolver::resolveExpr(this.getImportedPathExpr()) }
|
||||
|
||||
/**
|
||||
* Gets a module in a `node_modules/@types/` folder that matches the imported module name.
|
||||
* DEPRECATED. Use `getImportedModule()` instead.
|
||||
*/
|
||||
private Module resolveFromTypeRoot() {
|
||||
result.getFile() =
|
||||
min(TypeRootFolder typeRoot |
|
||||
|
|
||||
typeRoot.getModuleFile(this.getImportedPath().getValue())
|
||||
order by
|
||||
typeRoot.getSearchPriority(this.getFile().getParentContainer())
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the imported module, as determined by the TypeScript compiler, if any.
|
||||
*/
|
||||
private Module resolveFromTypeScriptSymbol() {
|
||||
deprecated Module resolveFromTypeScriptSymbol() {
|
||||
exists(CanonicalName symbol |
|
||||
ast_node_symbol(this, symbol) and
|
||||
ast_node_symbol(result, symbol)
|
||||
@@ -190,13 +175,7 @@ abstract class Import extends AstNode {
|
||||
Stages::Imports::ref() and
|
||||
if exists(this.resolveExternsImport())
|
||||
then result = this.resolveExternsImport()
|
||||
else (
|
||||
result = this.resolveAsProvidedModule() or
|
||||
result = this.resolveImportedPath() or
|
||||
result = this.resolveFromTypeRoot() or
|
||||
result = this.resolveFromTypeScriptSymbol() or
|
||||
result = resolveNeighbourPackage(this.getImportedPath().getValue())
|
||||
)
|
||||
else result = this.resolveImportedPath()
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -204,28 +183,3 @@ abstract class Import extends AstNode {
|
||||
*/
|
||||
abstract DataFlow::Node getImportedModuleNode();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a module imported from another package in the same repository.
|
||||
*
|
||||
* No support for importing from folders inside the other package.
|
||||
*/
|
||||
private Module resolveNeighbourPackage(PathString importPath) {
|
||||
exists(PackageJson json | importPath = json.getPackageName() and result = json.getMainModule())
|
||||
or
|
||||
exists(string package |
|
||||
result.getFile().getParentContainer() = getPackageFolder(package) and
|
||||
importPath = package + "/" + [result.getFile().getBaseName(), result.getFile().getStem()]
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the folder for a package that has name `package` according to a package.json file in the resulting folder.
|
||||
*/
|
||||
pragma[noinline]
|
||||
private Folder getPackageFolder(string package) {
|
||||
exists(PackageJson json |
|
||||
json.getPackageName() = package and
|
||||
result = json.getFile().getParentContainer()
|
||||
)
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
import javascript
|
||||
private import NodeModuleResolutionImpl
|
||||
private import semmle.javascript.internal.paths.PackageJsonEx
|
||||
|
||||
/** A `package.json` configuration object. */
|
||||
class PackageJson extends JsonObject {
|
||||
@@ -12,25 +13,36 @@ class PackageJson extends JsonObject {
|
||||
this.isTopLevel()
|
||||
}
|
||||
|
||||
/** Gets the folder containing this `package.json` file. */
|
||||
Folder getFolder() { result = this.getJsonFile().getParentContainer() }
|
||||
|
||||
/**
|
||||
* Gets the name of this package as it appears in the `name` field.
|
||||
*/
|
||||
pragma[nomagic]
|
||||
string getDeclaredPackageName() { result = this.getPropStringValue("name") }
|
||||
|
||||
/**
|
||||
* Gets the nearest `package.json` file found in the parent directories, if any.
|
||||
*/
|
||||
PackageJson getEnclosingPackage() {
|
||||
result.getFolder() = packageInternalParent*(this.getFolder().getParentContainer())
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the name of this package.
|
||||
* If the package is located under the package `pkg1` and its relative path is `foo/bar`, then the resulting package name will be `pkg1/foo/bar`.
|
||||
*/
|
||||
string getPackageName() {
|
||||
result = this.getPropStringValue("name")
|
||||
result = this.getDeclaredPackageName()
|
||||
or
|
||||
exists(
|
||||
PackageJson parentPkg, Container currentDir, Container parentDir, string parentPkgName,
|
||||
string pkgNameDiff
|
||||
|
|
||||
currentDir = this.getJsonFile().getParentContainer() and
|
||||
parentDir = parentPkg.getJsonFile().getParentContainer() and
|
||||
parentPkgName = parentPkg.getPropStringValue("name") and
|
||||
parentDir.getAChildContainer+() = currentDir and
|
||||
pkgNameDiff = currentDir.getAbsolutePath().suffix(parentDir.getAbsolutePath().length()) and
|
||||
not exists(pkgNameDiff.indexOf("/node_modules/")) and
|
||||
result = parentPkgName + pkgNameDiff and
|
||||
not parentPkg.isPrivate()
|
||||
not exists(this.getDeclaredPackageName()) and
|
||||
exists(PackageJson parent |
|
||||
parent = this.getEnclosingPackage() and
|
||||
not parent.isPrivate() and
|
||||
result =
|
||||
parent.getDeclaredPackageName() +
|
||||
this.getFolder().getRelativePath().suffix(parent.getFolder().getRelativePath().length())
|
||||
)
|
||||
}
|
||||
|
||||
@@ -84,7 +96,10 @@ class PackageJson extends JsonObject {
|
||||
* `module` paths to be exported under the relative path `"."`.
|
||||
*/
|
||||
string getExportedPath(string relativePath) {
|
||||
result = MainModulePath::of(this, relativePath).getValue()
|
||||
this.(PackageJsonEx).hasExactPathMapping(relativePath, result)
|
||||
or
|
||||
relativePath = "." and
|
||||
result = this.(PackageJsonEx).getMainPath()
|
||||
}
|
||||
|
||||
/** Gets the path of a command defined for this package. */
|
||||
@@ -211,7 +226,7 @@ class PackageJson extends JsonObject {
|
||||
/**
|
||||
* Gets the main module of this package.
|
||||
*/
|
||||
Module getMainModule() { result = this.getExportedModule(".") }
|
||||
Module getMainModule() { result.getFile() = this.(PackageJsonEx).getMainFileOrBestGuess() }
|
||||
|
||||
/**
|
||||
* Gets the module exported under the given relative path.
|
||||
@@ -219,12 +234,10 @@ class PackageJson extends JsonObject {
|
||||
* The main module is considered exported under the path `"."`.
|
||||
*/
|
||||
Module getExportedModule(string relativePath) {
|
||||
result =
|
||||
min(Module m, int prio |
|
||||
m.getFile() = resolveMainModule(this, prio, relativePath)
|
||||
|
|
||||
m order by prio
|
||||
)
|
||||
this.(PackageJsonEx).hasExactPathMappingTo(relativePath, result.getFile())
|
||||
or
|
||||
relativePath = "." and
|
||||
result = this.getMainModule()
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -236,19 +249,7 @@ class PackageJson extends JsonObject {
|
||||
* Gets the file containing the typings of this package, which can either be from the `types` or
|
||||
* `typings` field, or derived from the `main` or `module` fields.
|
||||
*/
|
||||
File getTypingsFile() {
|
||||
result =
|
||||
TypingsModulePathString::of(this).resolve(this.getFile().getParentContainer()).getContainer()
|
||||
or
|
||||
not exists(TypingsModulePathString::of(this)) and
|
||||
exists(File mainFile |
|
||||
mainFile = this.getMainModule().getFile() and
|
||||
result =
|
||||
mainFile
|
||||
.getParentContainer()
|
||||
.getFile(mainFile.getStem().regexpReplaceAll("\\.d$", "") + ".d.ts")
|
||||
)
|
||||
}
|
||||
File getTypingsFile() { none() } // implemented in PackageJsonEx
|
||||
|
||||
/**
|
||||
* Gets the module containing the typings of this package, which can either be from the `types` or
|
||||
@@ -406,5 +407,6 @@ class NpmPackage extends @folder {
|
||||
*/
|
||||
private Folder packageInternalParent(Container c) {
|
||||
result = c.getParentContainer() and
|
||||
not c.(Folder).getBaseName() = "node_modules"
|
||||
not c.(Folder).getBaseName() = "node_modules" and
|
||||
not c = any(PackageJson pkg).getFolder()
|
||||
}
|
||||
|
||||
@@ -146,7 +146,7 @@ class NodeModule extends Module {
|
||||
)
|
||||
}
|
||||
|
||||
override predicate searchRoot(PathExpr path, Folder searchRoot, int priority) {
|
||||
deprecated override predicate searchRoot(PathExpr path, Folder searchRoot, int priority) {
|
||||
path.getEnclosingModule() = this and
|
||||
exists(string pathval | pathval = path.getValue() |
|
||||
// paths starting with `./` or `../` are resolved relative to the importing
|
||||
@@ -236,13 +236,8 @@ private class RequireVariable extends Variable {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if module `m` is in file `f`.
|
||||
*/
|
||||
private predicate moduleInFile(Module m, File f) { m.getFile() = f }
|
||||
|
||||
private predicate isModuleModule(EarlyStageNode nd) {
|
||||
exists(ImportDeclaration imp | imp.getImportedPath().getValue() = "module" |
|
||||
exists(ImportDeclaration imp | imp.getRawImportPath() = "module" |
|
||||
nd = TDestructuredModuleImportNode(imp)
|
||||
or
|
||||
nd = TValueNode(imp.getASpecifier().(ImportNamespaceSpecifier))
|
||||
@@ -268,7 +263,7 @@ private predicate isCreateRequire(EarlyStageNode nd) {
|
||||
)
|
||||
or
|
||||
exists(ImportDeclaration decl, NamedImportSpecifier spec |
|
||||
decl.getImportedPath().getValue() = "module" and
|
||||
decl.getRawImportPath() = "module" and
|
||||
spec = decl.getASpecifier() and
|
||||
spec.getImportedName() = "createRequire" and
|
||||
nd = TValueNode(spec)
|
||||
@@ -328,94 +323,15 @@ private predicate isRequire(EarlyStageNode nd) {
|
||||
class Require extends CallExpr, Import {
|
||||
Require() { isRequire(TValueNode(this.getCallee())) }
|
||||
|
||||
override PathExpr getImportedPath() { result = this.getArgument(0) }
|
||||
override Expr getImportedPathExpr() { result = this.getArgument(0) }
|
||||
|
||||
override Module getEnclosingModule() { this = result.getAnImport() }
|
||||
|
||||
override Module resolveImportedPath() {
|
||||
moduleInFile(result, this.load(min(int prio | moduleInFile(_, this.load(prio)))))
|
||||
or
|
||||
not moduleInFile(_, this.load(_)) and
|
||||
result = Import.super.resolveImportedPath()
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the file that is imported by this `require`.
|
||||
*
|
||||
* The result can be a JavaScript file, a JSON file or a `.node` file.
|
||||
* Externs files are not treated differently from other files by this predicate.
|
||||
*/
|
||||
File getImportedFile() { result = this.load(min(int prio | exists(this.load(prio)))) }
|
||||
|
||||
/**
|
||||
* Gets the file that this `require` refers to (which may not be a JavaScript file),
|
||||
* using the root folder of priority `priority`.
|
||||
*
|
||||
* This predicate implements the specification of
|
||||
* [`require.resolve`](https://nodejs.org/api/modules.html#modules_all_together),
|
||||
* modified to allow additional JavaScript file extensions, such as `ts` and `jsx`.
|
||||
*
|
||||
* Module resolution order is modeled using the `priority` parameter as follows.
|
||||
*
|
||||
* Each candidate folder in which the path may be resolved is assigned
|
||||
* a priority (this is actually done by `Module.searchRoot`, but we explain it
|
||||
* here for completeness):
|
||||
*
|
||||
* - if the path starts with `'./'`, `'../'`, or `/`, it has a single candidate
|
||||
* folder (the enclosing folder of the module for the former two, the file
|
||||
* system root for the latter) of priority 0
|
||||
* - otherwise, candidate folders are folders of the form `<prefix>/node_modules`
|
||||
* such that `<prefix>` is a (not necessarily proper) ancestor of the enclosing
|
||||
* folder of the module which is not itself named `node_modules`; the priority
|
||||
* of a candidate folder is the number of steps from the enclosing folder of
|
||||
* the module to `<prefix>`.
|
||||
*
|
||||
* To resolve an import of a path `p`, we consider each candidate folder `c` with
|
||||
* priority `r` and resolve the import to the following files if they exist
|
||||
* (in order of priority):
|
||||
*
|
||||
* <ul>
|
||||
* <li> the file `c/p`;
|
||||
* <li> the file `c/p.{tsx,ts,jsx,es6,es,mjs,cjs}`;
|
||||
* <li> the file `c/p.js`;
|
||||
* <li> the file `c/p.json`;
|
||||
* <li> the file `c/p.node`;
|
||||
* <li> if `c/p` is a folder:
|
||||
* <ul>
|
||||
* <li> if `c/p/package.json` exists and specifies a `main` module `m`:
|
||||
* <ul>
|
||||
* <li> the file `c/p/m`;
|
||||
* <li> the file `c/p/m.{tsx,ts,jsx,es6,es,mjs,cjs}`;
|
||||
* <li> the file `c/p/m.js`;
|
||||
* <li> the file `c/p/m.json`;
|
||||
* <li> the file `c/p/m.node`;
|
||||
* </ul>
|
||||
* <li> the file `c/p/index.{tsx,ts,jsx,es6,es,mjs,cjs}`;
|
||||
* <li> the file `c/p/index.js`;
|
||||
* <li> the file `c/p/index.json`;
|
||||
* <li> the file `c/p/index.node`.
|
||||
* </ul>
|
||||
* </ul>
|
||||
*
|
||||
* The first four steps are factored out into predicate `loadAsFile`,
|
||||
* the remainder into `loadAsDirectory`; both make use of an auxiliary
|
||||
* predicate `tryExtensions` that handles the repeated distinction between
|
||||
* `.js`, `.json` and `.node`.
|
||||
*/
|
||||
private File load(int priority) {
|
||||
exists(int r | this.getEnclosingModule().searchRoot(this.getImportedPath(), _, r) |
|
||||
result = loadAsFile(this, r, priority - prioritiesPerCandidate() * r) or
|
||||
result =
|
||||
loadAsDirectory(this, r,
|
||||
priority - (prioritiesPerCandidate() * r + numberOfExtensions() + 1))
|
||||
)
|
||||
}
|
||||
|
||||
override DataFlow::Node getImportedModuleNode() { result = DataFlow::valueNode(this) }
|
||||
}
|
||||
|
||||
/** An argument to `require` or `require.resolve`, considered as a path expression. */
|
||||
private class RequirePath extends PathExprCandidate {
|
||||
deprecated private class RequirePath extends PathExprCandidate {
|
||||
RequirePath() {
|
||||
this = any(Require req).getArgument(0)
|
||||
or
|
||||
@@ -428,14 +344,14 @@ private class RequirePath extends PathExprCandidate {
|
||||
}
|
||||
|
||||
/** A constant path element appearing in a call to `require` or `require.resolve`. */
|
||||
private class ConstantRequirePathElement extends PathExpr, ConstantString {
|
||||
deprecated private class ConstantRequirePathElement extends PathExpr, ConstantString {
|
||||
ConstantRequirePathElement() { this = any(RequirePath rp).getAPart() }
|
||||
|
||||
override string getValue() { result = this.getStringValue() }
|
||||
}
|
||||
|
||||
/** A `__dirname` path expression. */
|
||||
private class DirNamePath extends PathExpr, VarAccess {
|
||||
deprecated private class DirNamePath extends PathExpr, VarAccess {
|
||||
DirNamePath() {
|
||||
this.getName() = "__dirname" and
|
||||
this.getVariable().getScope() instanceof ModuleScope
|
||||
@@ -445,7 +361,7 @@ private class DirNamePath extends PathExpr, VarAccess {
|
||||
}
|
||||
|
||||
/** A `__filename` path expression. */
|
||||
private class FileNamePath extends PathExpr, VarAccess {
|
||||
deprecated private class FileNamePath extends PathExpr, VarAccess {
|
||||
FileNamePath() {
|
||||
this.getName() = "__filename" and
|
||||
this.getVariable().getScope() instanceof ModuleScope
|
||||
@@ -458,7 +374,7 @@ private class FileNamePath extends PathExpr, VarAccess {
|
||||
* A path expression of the form `path.join(p, "...")` where
|
||||
* `p` is also a path expression.
|
||||
*/
|
||||
private class JoinedPath extends PathExpr, @call_expr {
|
||||
deprecated private class JoinedPath extends PathExpr, @call_expr {
|
||||
JoinedPath() {
|
||||
exists(MethodCallExpr call | call = this |
|
||||
call.getReceiver().(VarAccess).getName() = "path" and
|
||||
|
||||
@@ -45,7 +45,7 @@ int numberOfExtensions() { result = count(getFileExtensionPriority(_)) }
|
||||
* Gets the resolution target with the given `priority` of `req`
|
||||
* when resolved from the root with priority `rootPriority`.
|
||||
*/
|
||||
File loadAsFile(Require req, int rootPriority, int priority) {
|
||||
deprecated File loadAsFile(Require req, int rootPriority, int priority) {
|
||||
exists(PathExpr path | path = req.getImportedPath() |
|
||||
result = path.resolve(rootPriority) and priority = 0
|
||||
or
|
||||
@@ -60,7 +60,7 @@ File loadAsFile(Require req, int rootPriority, int priority) {
|
||||
* with the given `priority` of `req` when resolved from the root with
|
||||
* priority `rootPriority`.
|
||||
*/
|
||||
File loadAsDirectory(Require req, int rootPriority, int priority) {
|
||||
deprecated File loadAsDirectory(Require req, int rootPriority, int priority) {
|
||||
exists(Folder dir | dir = req.getImportedPath().resolve(rootPriority) |
|
||||
result = resolveMainModule(dir.(NpmPackage).getPackageJson(), priority, ".") or
|
||||
result = tryExtensions(dir, "index", priority - (numberOfExtensions() + 1))
|
||||
@@ -99,7 +99,7 @@ private string getStem(string name) {
|
||||
* Gets a file that a main module from `pkg` exported as `mainPath` with the given `priority`.
|
||||
* `mainPath` is "." if it's the main module of the package.
|
||||
*/
|
||||
private File resolveMainPath(PackageJson pkg, string mainPath, int priority) {
|
||||
deprecated private File resolveMainPath(PackageJson pkg, string mainPath, int priority) {
|
||||
exists(PathExpr main | main = MainModulePath::of(pkg, mainPath) |
|
||||
result = main.resolve() and priority = 0
|
||||
or
|
||||
@@ -132,7 +132,7 @@ private File resolveMainPath(PackageJson pkg, string mainPath, int priority) {
|
||||
/**
|
||||
* Gets the main module described by `pkg` with the given `priority`.
|
||||
*/
|
||||
File resolveMainModule(PackageJson pkg, int priority, string exportPath) {
|
||||
deprecated File resolveMainModule(PackageJson pkg, int priority, string exportPath) {
|
||||
result = resolveMainPath(pkg, exportPath, priority)
|
||||
or
|
||||
exportPath = "." and
|
||||
@@ -178,7 +178,7 @@ private string getASrcFolderName() { result = ["ts", "js", "src", "lib"] }
|
||||
* A JSON string in a `package.json` file specifying the path of one of the exported
|
||||
* modules of the package.
|
||||
*/
|
||||
class MainModulePath extends PathExpr, @json_string {
|
||||
deprecated class MainModulePath extends PathExpr, @json_string {
|
||||
PackageJson pkg;
|
||||
|
||||
MainModulePath() {
|
||||
@@ -228,7 +228,7 @@ private string getExportRelativePath(JsonValue part) {
|
||||
result.matches(".%")
|
||||
}
|
||||
|
||||
module MainModulePath {
|
||||
deprecated module MainModulePath {
|
||||
/** Gets the path to the main entry point of `pkg`. */
|
||||
MainModulePath of(PackageJson pkg) { result = of(pkg, ".") }
|
||||
|
||||
@@ -244,7 +244,7 @@ module MainModulePath {
|
||||
* These files are often imported directly from a client when a "main" module is not specified.
|
||||
* For performance reasons this only exists if there is no "main" field in the `package.json` file.
|
||||
*/
|
||||
private class FilesPath extends PathExpr, @json_string {
|
||||
deprecated private class FilesPath extends PathExpr, @json_string {
|
||||
PackageJson pkg;
|
||||
|
||||
FilesPath() {
|
||||
@@ -263,7 +263,7 @@ private class FilesPath extends PathExpr, @json_string {
|
||||
}
|
||||
}
|
||||
|
||||
private module FilesPath {
|
||||
deprecated private module FilesPath {
|
||||
FilesPath of(PackageJson pkg) { result.getPackageJson() = pkg }
|
||||
}
|
||||
|
||||
@@ -271,7 +271,7 @@ private module FilesPath {
|
||||
* A JSON string in a `package.json` file specifying the path of the
|
||||
* TypeScript typings entry point.
|
||||
*/
|
||||
class TypingsModulePathString extends PathString {
|
||||
deprecated class TypingsModulePathString extends PathString {
|
||||
PackageJson pkg;
|
||||
|
||||
TypingsModulePathString() {
|
||||
@@ -288,7 +288,7 @@ class TypingsModulePathString extends PathString {
|
||||
}
|
||||
|
||||
/** Companion module to the `TypingsModulePathString` class. */
|
||||
module TypingsModulePathString {
|
||||
deprecated module TypingsModulePathString {
|
||||
/** Get the typings path for the given `package.json` file. */
|
||||
TypingsModulePathString of(PackageJson pkg) { result.getPackageJson() = pkg }
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
import javascript
|
||||
private import semmle.javascript.internal.CachedStages
|
||||
private import semmle.javascript.internal.paths.PackageJsonEx
|
||||
|
||||
/**
|
||||
* Gets a parameter that is a library input to a top-level package.
|
||||
@@ -126,19 +127,12 @@ private DataFlow::Node getAValueExportedByPackage() {
|
||||
// ....
|
||||
// }));
|
||||
// ```
|
||||
// Such files are not recognized as modules, so we manually use `NodeModule::resolveMainModule` to resolve the file against a `package.json` file.
|
||||
// Such files are not recognized as modules, so we manually use `PackageJsonEx` to resolve the file against a `package.json` file.
|
||||
exists(ImmediatelyInvokedFunctionExpr func, DataFlow::ParameterNode factory, int i |
|
||||
factory.getName() = "factory" and
|
||||
func.getParameter(i) = factory.getParameter() and
|
||||
DataFlow::globalVarRef("define").getACall().getAnArgument() = factory.getALocalUse() and
|
||||
func.getFile() =
|
||||
min(int j, File f |
|
||||
f =
|
||||
NodeModule::resolveMainModule(any(PackageJson pack | exists(pack.getPackageName())), j,
|
||||
".")
|
||||
|
|
||||
f order by j
|
||||
)
|
||||
func.getFile() = any(PackageJsonEx pack).getMainFileOrBestGuess()
|
||||
|
|
||||
result = func.getInvocation().getArgument(i).flow().getAFunctionValue().getAReturn()
|
||||
or
|
||||
|
||||
@@ -9,13 +9,13 @@ private import semmle.javascript.dataflow.internal.DataFlowNode
|
||||
/**
|
||||
* Internal representation of paths as lists of components.
|
||||
*/
|
||||
private newtype TPath =
|
||||
deprecated private newtype TPath =
|
||||
/** A root path. */
|
||||
TRootPath(string root) {
|
||||
root = any(Folder f | not exists(f.getParentContainer())).getAbsolutePath()
|
||||
} or
|
||||
/** A path of the form `<parent>/<component>`. */
|
||||
TConsPath(Path parent, string component) {
|
||||
deprecated TConsPath(Path parent, string component) {
|
||||
// make sure we can represent paths of files in snapshot
|
||||
exists(Folder f | f = parent.getContainer() | exists(f.getChildContainer(component)))
|
||||
or
|
||||
@@ -32,7 +32,7 @@ private newtype TPath =
|
||||
* Gets a textual representation of path `p` using slashes as delimiters;
|
||||
* the empty path is represented as the empty string `""`.
|
||||
*/
|
||||
private string pp(TPath p) {
|
||||
deprecated private string pp(TPath p) {
|
||||
p = TRootPath(result + "/")
|
||||
or
|
||||
exists(TPath parent, string component | p = TConsPath(parent, component) |
|
||||
@@ -45,7 +45,7 @@ private string pp(TPath p) {
|
||||
* which may (but does not have to) correspond to a file or folder included
|
||||
* in the snapshot.
|
||||
*/
|
||||
class Path extends TPath {
|
||||
deprecated class Path extends TPath {
|
||||
/**
|
||||
* Gets the file or folder referred to by this path, if it exists.
|
||||
*/
|
||||
@@ -60,14 +60,14 @@ class Path extends TPath {
|
||||
/**
|
||||
* The empty path, which refers to the file system root.
|
||||
*/
|
||||
private class RootPath extends Path, TRootPath {
|
||||
deprecated private class RootPath extends Path, TRootPath {
|
||||
override string toString() { this = TRootPath(result) }
|
||||
}
|
||||
|
||||
/**
|
||||
* A non-empty path of the form `<parent>/<component>`.
|
||||
*/
|
||||
private class ConsPath extends Path, TConsPath {
|
||||
deprecated private class ConsPath extends Path, TConsPath {
|
||||
/** Gets the parent path of this path. */
|
||||
Path getParent() { this = TConsPath(result, _) }
|
||||
|
||||
@@ -96,27 +96,22 @@ private class ConsPath extends Path, TConsPath {
|
||||
private string pathRegex() { result = "(.*)(?:/|^)(([^/]*?)(\\.([^.]*))?)" }
|
||||
|
||||
/**
|
||||
* A string value that represents a (relative or absolute) file system path.
|
||||
*
|
||||
* Each path string is associated with one or more root folders relative to
|
||||
* which the path may be resolved. For instance, paths inside a module are
|
||||
* usually resolved relative to the module's folder, with a default
|
||||
* lookup path as the fallback.
|
||||
* A `string` with some additional member predicates for extracting parts of a file path.
|
||||
*/
|
||||
abstract class PathString extends string {
|
||||
class FilePath extends string {
|
||||
bindingset[this]
|
||||
PathString() { any() }
|
||||
|
||||
/** Gets a root folder relative to which this path can be resolved. */
|
||||
abstract Folder getARootFolder();
|
||||
FilePath() { any() }
|
||||
|
||||
/** Gets the `i`th component of this path. */
|
||||
bindingset[this]
|
||||
string getComponent(int i) { result = this.splitAt("/", i) }
|
||||
|
||||
/** Gets the number of components of this path. */
|
||||
bindingset[this]
|
||||
int getNumComponent() { result = count(int i | exists(this.getComponent(i))) }
|
||||
|
||||
/** Gets the base name of the folder or file this path refers to. */
|
||||
bindingset[this]
|
||||
string getBaseName() { result = this.regexpCapture(pathRegex(), 2) }
|
||||
|
||||
/**
|
||||
@@ -124,9 +119,11 @@ abstract class PathString extends string {
|
||||
* up to (but not including) the last dot character if there is one, or the entire
|
||||
* base name if there is not
|
||||
*/
|
||||
bindingset[this]
|
||||
string getStem() { result = this.regexpCapture(pathRegex(), 3) }
|
||||
|
||||
/** Gets the path of the parent folder of the folder or file this path refers to. */
|
||||
bindingset[this]
|
||||
string getDirName() { result = this.regexpCapture(pathRegex(), 1) }
|
||||
|
||||
/**
|
||||
@@ -135,8 +132,51 @@ abstract class PathString extends string {
|
||||
*
|
||||
* Has no result if the base name does not contain a dot.
|
||||
*/
|
||||
bindingset[this]
|
||||
string getExtension() { result = this.regexpCapture(pathRegex(), 4) }
|
||||
|
||||
/**
|
||||
* Holds if this is a relative path starting with an explicit `./` or similar syntax meaning it
|
||||
* must be resolved relative to its enclosing folder.
|
||||
*
|
||||
* Specifically this holds when the string is `.` or `..`, or starts with `./` or `../` or
|
||||
* `.\` or `..\`.
|
||||
*/
|
||||
bindingset[this]
|
||||
pragma[inline_late]
|
||||
predicate isDotRelativePath() { this.regexpMatch("\\.\\.?(?:[/\\\\].*)?") }
|
||||
|
||||
/**
|
||||
* Gets the NPM package name from the beginning of the given import path.
|
||||
*
|
||||
* Has no result for paths starting with a `.` or `/`
|
||||
*
|
||||
* For example:
|
||||
* - `foo/bar` maps to `foo`
|
||||
* - `@example/foo/bar` maps to `@example/foo`
|
||||
* - `./foo` maps to nothing.
|
||||
*/
|
||||
bindingset[this]
|
||||
string getPackagePrefix() {
|
||||
result = this.regexpFind("^(@[^/\\\\]+[/\\\\])?[^@./\\\\][^/\\\\]*", _, _)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A string value that represents a (relative or absolute) file system path.
|
||||
*
|
||||
* Each path string is associated with one or more root folders relative to
|
||||
* which the path may be resolved. For instance, paths inside a module are
|
||||
* usually resolved relative to the module's folder, with a default
|
||||
* lookup path as the fallback.
|
||||
*/
|
||||
abstract deprecated class PathString extends FilePath {
|
||||
bindingset[this]
|
||||
PathString() { any() }
|
||||
|
||||
/** Gets a root folder relative to which this path can be resolved. */
|
||||
abstract Folder getARootFolder();
|
||||
|
||||
/**
|
||||
* Gets the absolute path that the sub-path consisting of the first `n`
|
||||
* components of this path refers to when resolved relative to the
|
||||
@@ -156,7 +196,7 @@ abstract class PathString extends string {
|
||||
* components of this path refers to when resolved relative to the
|
||||
* given `root` folder.
|
||||
*/
|
||||
private Path resolveUpTo(PathString p, int n, Folder root, boolean inTS) {
|
||||
deprecated private Path resolveUpTo(PathString p, int n, Folder root, boolean inTS) {
|
||||
n = 0 and result.getContainer() = root and root = p.getARootFolder() and inTS = false
|
||||
or
|
||||
exists(Path base, string next | next = getComponent(p, n - 1, base, root, inTS) |
|
||||
@@ -185,7 +225,7 @@ private Path resolveUpTo(PathString p, int n, Folder root, boolean inTS) {
|
||||
* Supports that the root directory might be compiled output from TypeScript.
|
||||
* `inTS` is true if the result is TypeScript that is compiled into the path specified by `str`.
|
||||
*/
|
||||
private string getComponent(PathString str, int n, Path base, Folder root, boolean inTS) {
|
||||
deprecated private string getComponent(PathString str, int n, Path base, Folder root, boolean inTS) {
|
||||
exists(boolean prevTS |
|
||||
base = resolveUpTo(str, n, root, prevTS) and
|
||||
(
|
||||
@@ -208,7 +248,7 @@ private string getComponent(PathString str, int n, Path base, Folder root, boole
|
||||
/**
|
||||
* Predicates for resolving imports to compiled TypeScript.
|
||||
*/
|
||||
private module TypeScriptOutDir {
|
||||
deprecated private module TypeScriptOutDir {
|
||||
/**
|
||||
* Gets a folder of TypeScript files that is compiled to JavaScript files in `outdir` relative to a `parent`.
|
||||
*/
|
||||
@@ -300,7 +340,7 @@ private module TypeScriptOutDir {
|
||||
* as their highest-priority root, with default library paths as additional roots
|
||||
* of lower priority.
|
||||
*/
|
||||
abstract class PathExpr extends Locatable {
|
||||
abstract deprecated class PathExpr extends Locatable {
|
||||
/** Gets the (unresolved) path represented by this expression. */
|
||||
abstract string getValue();
|
||||
|
||||
@@ -373,7 +413,7 @@ abstract class PathExpr extends Locatable {
|
||||
}
|
||||
|
||||
/** A path string derived from a path expression. */
|
||||
private class PathExprString extends PathString {
|
||||
deprecated private class PathExprString extends PathString {
|
||||
PathExprString() { this = any(PathExpr pe).getValue() }
|
||||
|
||||
override Folder getARootFolder() {
|
||||
@@ -382,13 +422,13 @@ private class PathExprString extends PathString {
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private EarlyStageNode getAPathExprAlias(PathExpr expr) {
|
||||
deprecated private EarlyStageNode getAPathExprAlias(PathExpr expr) {
|
||||
DataFlow::Impl::earlyStageImmediateFlowStep(TValueNode(expr), result)
|
||||
or
|
||||
DataFlow::Impl::earlyStageImmediateFlowStep(getAPathExprAlias(expr), result)
|
||||
}
|
||||
|
||||
private class PathExprFromAlias extends PathExpr {
|
||||
deprecated private class PathExprFromAlias extends PathExpr {
|
||||
private PathExpr other;
|
||||
|
||||
PathExprFromAlias() { TValueNode(this) = getAPathExprAlias(other) }
|
||||
@@ -404,7 +444,7 @@ private class PathExprFromAlias extends PathExpr {
|
||||
* A path expression of the form `p + q`, where both `p` and `q`
|
||||
* are path expressions.
|
||||
*/
|
||||
private class ConcatPath extends PathExpr {
|
||||
deprecated private class ConcatPath extends PathExpr {
|
||||
ConcatPath() {
|
||||
exists(AddExpr add | this = add |
|
||||
add.getLeftOperand() instanceof PathExpr and
|
||||
|
||||
222
javascript/ql/lib/semmle/javascript/TSConfig.qll
Normal file
222
javascript/ql/lib/semmle/javascript/TSConfig.qll
Normal file
@@ -0,0 +1,222 @@
|
||||
/**
|
||||
* Provides a class for working with `tsconfig.json` files.
|
||||
*/
|
||||
|
||||
private import javascript
|
||||
private import semmle.javascript.internal.paths.PathMapping
|
||||
|
||||
/**
|
||||
* A TypeScript configuration file, usually named `tsconfig.json`.
|
||||
*/
|
||||
class TSConfig extends JsonObject {
|
||||
TSConfig() {
|
||||
this.getJsonFile().getBaseName().matches("%tsconfig%.json") and
|
||||
this.isTopLevel()
|
||||
}
|
||||
|
||||
/** Gets the folder containing this file. */
|
||||
Folder getFolder() { result = this.getJsonFile().getParentContainer() }
|
||||
|
||||
/** Gets the `compilerOptions` object. */
|
||||
JsonObject getCompilerOptions() { result = this.getPropValue("compilerOptions") }
|
||||
|
||||
/** Gets the string value in the `extends` property. */
|
||||
string getExtendsPath() { result = this.getPropStringValue("extends") }
|
||||
|
||||
/** Gets the file referred to by the `extends` property. */
|
||||
File getExtendedFile() { result = Resolver::resolve(this.getFolder(), this.getExtendsPath()) }
|
||||
|
||||
/** Gets the `TSConfig` file referred to by the `extends` property. */
|
||||
TSConfig getExtendedTSConfig() { result.getJsonFile() = this.getExtendedFile() }
|
||||
|
||||
/** Gets the string value in the `baseUrl` property. */
|
||||
string getBaseUrlPath() { result = this.getCompilerOptions().getPropStringValue("baseUrl") }
|
||||
|
||||
/** Gets the folder referred to by the `baseUrl` property in this file, not taking `extends` into account. */
|
||||
Folder getOwnBaseUrlFolder() {
|
||||
result = Resolver::resolve(this.getFolder(), this.getBaseUrlPath())
|
||||
}
|
||||
|
||||
/** Gets the effective baseUrl folder for this tsconfig file. */
|
||||
Folder getBaseUrlFolder() {
|
||||
result = this.getOwnBaseUrlFolder()
|
||||
or
|
||||
not exists(this.getOwnBaseUrlFolder()) and
|
||||
result = this.getExtendedTSConfig().getBaseUrlFolder()
|
||||
}
|
||||
|
||||
/** Gets the effective baseUrl folder for this tsconfig file, or its enclosing folder if there is no baseUrl. */
|
||||
Folder getBaseUrlFolderOrOwnFolder() {
|
||||
result = this.getBaseUrlFolder()
|
||||
or
|
||||
not exists(this.getBaseUrlFolder()) and
|
||||
result = this.getFolder()
|
||||
}
|
||||
|
||||
/** Gets a path mentioned in the `include` property. */
|
||||
string getAnIncludePath() {
|
||||
result = this.getPropStringValue("include")
|
||||
or
|
||||
result = this.getPropValue("include").(JsonArray).getElementStringValue(_)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a file or folder refenced by a path the `include` property, possibly
|
||||
* inherited from an extended tsconfig file.
|
||||
*
|
||||
* Does not include all the files within includes directories, use `getAnIncludedContainer` for that.
|
||||
*/
|
||||
Container getAnIncludePathTarget() {
|
||||
result = Resolver::resolve(this.getFolder(), this.getAnIncludePath())
|
||||
or
|
||||
not exists(this.getPropValue("include")) and
|
||||
result = this.getExtendedTSConfig().getAnIncludePathTarget()
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a file or folder inside the directory tree mentioned in the `include` property.
|
||||
*/
|
||||
Container getAnIncludedContainer() {
|
||||
result = this.getAnIncludePathTarget()
|
||||
or
|
||||
result = this.getAnIncludedContainer().getAChildContainer()
|
||||
}
|
||||
|
||||
/** Gets the path mentioned in the `rootDir` property. */
|
||||
string getRootDirPath() { result = this.getCompilerOptions().getPropStringValue("rootDir") }
|
||||
|
||||
private Container getOwnRootDir() {
|
||||
result = Resolver::resolve(this.getFolder(), this.getRootDirPath())
|
||||
}
|
||||
|
||||
/** Gets the file or folder referenced by the `rootDir` property. */
|
||||
Container getRootDir() {
|
||||
result = this.getOwnRootDir()
|
||||
or
|
||||
not exists(this.getRootDirPath()) and
|
||||
result = this.getExtendedTSConfig().getOwnRootDir()
|
||||
}
|
||||
|
||||
private string getATopLevelIncludePath() {
|
||||
result = this.getAnIncludePath().(FilePath).getComponent(0)
|
||||
}
|
||||
|
||||
private string getUniqueTopLevelIncludePath() {
|
||||
result = unique( | | this.getATopLevelIncludePath())
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the folder referred to by the `rootDir` property, or if absent, an effective root dir
|
||||
* derived from `include` paths.
|
||||
*/
|
||||
Container getEffectiveRootDir() {
|
||||
result = this.getRootDir()
|
||||
or
|
||||
not exists(this.getRootDir()) and
|
||||
(
|
||||
result = this.getFolder().getFolder(this.getUniqueTopLevelIncludePath())
|
||||
or
|
||||
not exists(this.getUniqueTopLevelIncludePath()) and
|
||||
exists(this.getATopLevelIncludePath()) and
|
||||
result = this.getFolder()
|
||||
or
|
||||
not exists(this.getATopLevelIncludePath()) and
|
||||
result = this.getExtendedTSConfig().getEffectiveRootDir()
|
||||
)
|
||||
}
|
||||
|
||||
private JsonObject getPathMappings() { result = this.getCompilerOptions().getPropValue("paths") }
|
||||
|
||||
/**
|
||||
* Holds if this has a path mapping from `pattern` to `newPath`.
|
||||
*
|
||||
* For example, `"paths": { "@/*": "./src/*" }` maps the `@/*` pattern to `./src/*`.
|
||||
*
|
||||
* Does not include path mappings from extended tsconfig files.
|
||||
*/
|
||||
predicate hasPathMapping(string pattern, string newPath) {
|
||||
this.getPathMappings().getPropStringValue(pattern) = newPath
|
||||
or
|
||||
this.getPathMappings().getPropValue(pattern).(JsonArray).getElementStringValue(_) = newPath
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if this has an exact path mapping (i.e. no wildcards) from `pattern` to `newPath`.
|
||||
*
|
||||
* For example, `"paths": { "@": "./src/index.ts" }` maps the `@` path to `./src/index.ts`.
|
||||
*
|
||||
* Does not include path mappings from extended tsconfig files.
|
||||
*/
|
||||
predicate hasExactPathMapping(string pattern, string newPath) {
|
||||
this.hasPathMapping(pattern, newPath) and
|
||||
not pattern.matches("%*%")
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if this has a path mapping from the `pattern` prefix to the `newPath` prefix.
|
||||
* The trailing `*` is not included.
|
||||
*
|
||||
* For example, `"paths": { "@/*": "./src/*" }` maps the `@/` pattern to `./src/`.
|
||||
*
|
||||
* Does not include path mappings from extended tsconfig files.
|
||||
*/
|
||||
predicate hasPrefixPathMapping(string pattern, string newPath) {
|
||||
this.hasPathMapping(pattern + "*", newPath + "*")
|
||||
}
|
||||
}
|
||||
|
||||
/** For resolving paths in a tsconfig file, except `paths` mappings. */
|
||||
private module ResolverConfig implements Folder::ResolveSig {
|
||||
predicate shouldResolve(Container base, string path) {
|
||||
exists(TSConfig cfg |
|
||||
base = cfg.getFolder() and
|
||||
path =
|
||||
[cfg.getExtendsPath(), cfg.getBaseUrlPath(), cfg.getRootDirPath(), cfg.getAnIncludePath()]
|
||||
)
|
||||
}
|
||||
|
||||
predicate allowGlobs() { any() } // "include" can use globs
|
||||
}
|
||||
|
||||
private module Resolver = Folder::Resolve<ResolverConfig>;
|
||||
|
||||
/**
|
||||
* Gets a tsconfig file to use as fallback for handling paths in `c`.
|
||||
*
|
||||
* This holds for files and folders where no tsconfig seems to include it,
|
||||
* but it has one or more tsconfig files in parent directories.
|
||||
*/
|
||||
private TSConfig getFallbackTSConfig(Container c) {
|
||||
not c = any(TSConfig t).getAnIncludedContainer() and
|
||||
(
|
||||
c = result.getFolder()
|
||||
or
|
||||
result = getFallbackTSConfig(c.getParentContainer())
|
||||
)
|
||||
}
|
||||
|
||||
private class TSConfigPathMapping extends PathMapping, TSConfig {
|
||||
override File getAnAffectedFile() {
|
||||
result = this.getAnIncludedContainer()
|
||||
or
|
||||
this = getFallbackTSConfig(result)
|
||||
}
|
||||
|
||||
override predicate hasExactPathMapping(string pattern, Container newContext, string newPath) {
|
||||
exists(TSConfig tsconfig |
|
||||
tsconfig = this.getExtendedTSConfig*() and
|
||||
tsconfig.hasExactPathMapping(pattern, newPath) and
|
||||
newContext = tsconfig.getBaseUrlFolderOrOwnFolder()
|
||||
)
|
||||
}
|
||||
|
||||
override predicate hasPrefixPathMapping(string pattern, Container newContext, string newPath) {
|
||||
exists(TSConfig tsconfig |
|
||||
tsconfig = this.getExtendedTSConfig*() and
|
||||
tsconfig.hasPrefixPathMapping(pattern, newPath) and
|
||||
newContext = tsconfig.getBaseUrlFolderOrOwnFolder()
|
||||
)
|
||||
}
|
||||
|
||||
override predicate hasBaseUrl(Container base) { base = this.getBaseUrlFolder() }
|
||||
}
|
||||
@@ -207,7 +207,7 @@ class ExternalModuleReference extends Expr, Import, @external_module_reference {
|
||||
/** Gets the expression specifying the module. */
|
||||
Expr getExpression() { result = this.getChildExpr(0) }
|
||||
|
||||
override PathExpr getImportedPath() { result = this.getExpression() }
|
||||
override Expr getImportedPathExpr() { result = this.getExpression() }
|
||||
|
||||
override Module getEnclosingModule() { result = this.getTopLevel() }
|
||||
|
||||
@@ -221,7 +221,7 @@ class ExternalModuleReference extends Expr, Import, @external_module_reference {
|
||||
}
|
||||
|
||||
/** A literal path expression appearing in an external module reference. */
|
||||
private class LiteralExternalModulePath extends PathExpr, ConstantString {
|
||||
deprecated private class LiteralExternalModulePath extends PathExpr, ConstantString {
|
||||
LiteralExternalModulePath() {
|
||||
exists(ExternalModuleReference emr | this.getParentExpr*() = emr.getExpression())
|
||||
}
|
||||
@@ -743,7 +743,7 @@ class TypeAccess extends @typeaccess, TypeExpr, TypeRef {
|
||||
* For non-relative imports, it is the import path itself.
|
||||
*/
|
||||
private string getImportName(Import imprt) {
|
||||
exists(string path | path = imprt.getImportedPath().getValue() |
|
||||
exists(string path | path = imprt.getImportedPathString() |
|
||||
if path.regexpMatch("[./].*")
|
||||
then result = imprt.getImportedModule().getFile().getRelativePath()
|
||||
else result = path
|
||||
@@ -1731,7 +1731,7 @@ class TSGlobalDeclImport extends DataFlow::ModuleImportNode::Range {
|
||||
pkg = tt.getExpressionName() and
|
||||
// then, check pkg is imported as "import * as pkg from path"
|
||||
i.getLocal().getVariable() = pkg.getVariable() and
|
||||
path = i.getImportedPath().getValue() and
|
||||
path = i.getImportedPathString() and
|
||||
// finally, "this" needs to be a reference to gv
|
||||
this = DataFlow::exprNode(gv.getAnAccess())
|
||||
)
|
||||
|
||||
@@ -738,7 +738,7 @@ module ModuleImportNode {
|
||||
DefaultRange() {
|
||||
exists(Import i |
|
||||
this = i.getImportedModuleNode() and
|
||||
i.getImportedPath().getValue() = path
|
||||
i.getImportedPathString() = path
|
||||
)
|
||||
or
|
||||
// AMD require
|
||||
@@ -861,21 +861,61 @@ module MemberKind {
|
||||
*
|
||||
* Additional patterns can be recognized as class nodes, by extending `DataFlow::ClassNode::Range`.
|
||||
*/
|
||||
class ClassNode extends DataFlow::SourceNode instanceof ClassNode::Range {
|
||||
class ClassNode extends DataFlow::ValueNode, DataFlow::SourceNode {
|
||||
override AST::ValueNode astNode;
|
||||
AbstractCallable function;
|
||||
|
||||
ClassNode() {
|
||||
// ES6 class case
|
||||
astNode instanceof ClassDefinition and
|
||||
function.(AbstractClass).getClass() = astNode
|
||||
or
|
||||
// Function-style class case
|
||||
astNode instanceof Function and
|
||||
not astNode = any(ClassDefinition cls).getConstructor().getBody() and
|
||||
function.getFunction() = astNode and
|
||||
(
|
||||
exists(getAFunctionValueWithPrototype(function))
|
||||
or
|
||||
function = any(NewNode new).getCalleeNode().analyze().getAValue()
|
||||
or
|
||||
exists(string name | this = AccessPath::getAnAssignmentTo(name) |
|
||||
exists(getAPrototypeReferenceInFile(name, this.getFile()))
|
||||
or
|
||||
exists(getAnInstantiationInFile(name, this.getFile()))
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the unqualified name of the class, if it has one or one can be determined from the context.
|
||||
*/
|
||||
string getName() { result = super.getName() }
|
||||
string getName() {
|
||||
astNode instanceof ClassDefinition and result = astNode.(ClassDefinition).getName()
|
||||
or
|
||||
astNode instanceof Function and result = astNode.(Function).getName()
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a description of the class.
|
||||
*/
|
||||
string describe() { result = super.describe() }
|
||||
string describe() {
|
||||
astNode instanceof ClassDefinition and result = astNode.(ClassDefinition).describe()
|
||||
or
|
||||
astNode instanceof Function and result = astNode.(Function).describe()
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the constructor function of this class.
|
||||
*/
|
||||
FunctionNode getConstructor() { result = super.getConstructor() }
|
||||
FunctionNode getConstructor() {
|
||||
// For ES6 classes
|
||||
astNode instanceof ClassDefinition and
|
||||
result = astNode.(ClassDefinition).getConstructor().getBody().flow()
|
||||
or
|
||||
// For function-style classes
|
||||
astNode instanceof Function and result = this
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an instance method declared in this class, with the given name, if any.
|
||||
@@ -883,7 +923,7 @@ class ClassNode extends DataFlow::SourceNode instanceof ClassNode::Range {
|
||||
* Does not include methods from superclasses.
|
||||
*/
|
||||
FunctionNode getInstanceMethod(string name) {
|
||||
result = super.getInstanceMember(name, MemberKind::method())
|
||||
result = this.getInstanceMember(name, MemberKind::method())
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -893,7 +933,7 @@ class ClassNode extends DataFlow::SourceNode instanceof ClassNode::Range {
|
||||
*
|
||||
* Does not include methods from superclasses.
|
||||
*/
|
||||
FunctionNode getAnInstanceMethod() { result = super.getAnInstanceMember(MemberKind::method()) }
|
||||
FunctionNode getAnInstanceMethod() { result = this.getAnInstanceMember(MemberKind::method()) }
|
||||
|
||||
/**
|
||||
* Gets the instance method, getter, or setter with the given name and kind.
|
||||
@@ -901,7 +941,29 @@ class ClassNode extends DataFlow::SourceNode instanceof ClassNode::Range {
|
||||
* Does not include members from superclasses.
|
||||
*/
|
||||
FunctionNode getInstanceMember(string name, MemberKind kind) {
|
||||
result = super.getInstanceMember(name, kind)
|
||||
// ES6 class methods
|
||||
exists(MethodDeclaration method |
|
||||
astNode instanceof ClassDefinition and
|
||||
method = astNode.(ClassDefinition).getMethod(name) and
|
||||
not method.isStatic() and
|
||||
kind = MemberKind::of(method) and
|
||||
result = method.getBody().flow()
|
||||
)
|
||||
or
|
||||
// Function-style class accessors
|
||||
astNode instanceof Function and
|
||||
exists(PropertyAccessor accessor |
|
||||
accessor = this.getAnAccessor(kind) and
|
||||
accessor.getName() = name and
|
||||
result = accessor.getInit().flow()
|
||||
)
|
||||
or
|
||||
kind = MemberKind::method() and
|
||||
result =
|
||||
[
|
||||
this.getConstructor().getReceiver().getAPropertySource(name),
|
||||
this.getAPrototypeReference().getAPropertySource(name)
|
||||
]
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -909,20 +971,52 @@ class ClassNode extends DataFlow::SourceNode instanceof ClassNode::Range {
|
||||
*
|
||||
* Does not include members from superclasses.
|
||||
*/
|
||||
FunctionNode getAnInstanceMember(MemberKind kind) { result = super.getAnInstanceMember(kind) }
|
||||
FunctionNode getAnInstanceMember(MemberKind kind) {
|
||||
// ES6 class methods
|
||||
exists(MethodDeclaration method |
|
||||
astNode instanceof ClassDefinition and
|
||||
method = astNode.(ClassDefinition).getAMethod() and
|
||||
not method.isStatic() and
|
||||
kind = MemberKind::of(method) and
|
||||
result = method.getBody().flow()
|
||||
)
|
||||
or
|
||||
// Function-style class accessors
|
||||
astNode instanceof Function and
|
||||
exists(PropertyAccessor accessor |
|
||||
accessor = this.getAnAccessor(kind) and
|
||||
result = accessor.getInit().flow()
|
||||
)
|
||||
or
|
||||
kind = MemberKind::method() and
|
||||
result =
|
||||
[
|
||||
this.getConstructor().getReceiver().getAPropertySource(),
|
||||
this.getAPrototypeReference().getAPropertySource()
|
||||
]
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an instance method, getter, or setter declared in this class.
|
||||
*
|
||||
* Does not include members from superclasses.
|
||||
*/
|
||||
FunctionNode getAnInstanceMember() { result = super.getAnInstanceMember(_) }
|
||||
FunctionNode getAnInstanceMember() { result = this.getAnInstanceMember(_) }
|
||||
|
||||
/**
|
||||
* Gets the static method, getter, or setter declared in this class with the given name and kind.
|
||||
*/
|
||||
FunctionNode getStaticMember(string name, MemberKind kind) {
|
||||
result = super.getStaticMember(name, kind)
|
||||
exists(MethodDeclaration method |
|
||||
astNode instanceof ClassDefinition and
|
||||
method = astNode.(ClassDefinition).getMethod(name) and
|
||||
method.isStatic() and
|
||||
kind = MemberKind::of(method) and
|
||||
result = method.getBody().flow()
|
||||
)
|
||||
or
|
||||
kind.isMethod() and
|
||||
result = this.getAPropertySource(name)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -935,7 +1029,18 @@ class ClassNode extends DataFlow::SourceNode instanceof ClassNode::Range {
|
||||
/**
|
||||
* Gets a static method, getter, or setter declared in this class with the given kind.
|
||||
*/
|
||||
FunctionNode getAStaticMember(MemberKind kind) { result = super.getAStaticMember(kind) }
|
||||
FunctionNode getAStaticMember(MemberKind kind) {
|
||||
exists(MethodDeclaration method |
|
||||
astNode instanceof ClassDefinition and
|
||||
method = astNode.(ClassDefinition).getAMethod() and
|
||||
method.isStatic() and
|
||||
kind = MemberKind::of(method) and
|
||||
result = method.getBody().flow()
|
||||
)
|
||||
or
|
||||
kind.isMethod() and
|
||||
result = this.getAPropertySource()
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a static method declared in this class.
|
||||
@@ -944,10 +1049,79 @@ class ClassNode extends DataFlow::SourceNode instanceof ClassNode::Range {
|
||||
*/
|
||||
FunctionNode getAStaticMethod() { result = this.getAStaticMember(MemberKind::method()) }
|
||||
|
||||
/**
|
||||
* Gets a reference to the prototype of this class.
|
||||
* Only applies to function-style classes.
|
||||
*/
|
||||
DataFlow::SourceNode getAPrototypeReference() {
|
||||
exists(DataFlow::SourceNode base | base = getAFunctionValueWithPrototype(function) |
|
||||
result = base.getAPropertyRead("prototype")
|
||||
or
|
||||
result = base.getAPropertySource("prototype")
|
||||
)
|
||||
or
|
||||
exists(string name |
|
||||
this = AccessPath::getAnAssignmentTo(name) and
|
||||
result = getAPrototypeReferenceInFile(name, this.getFile())
|
||||
)
|
||||
or
|
||||
exists(string name, DataFlow::SourceNode root |
|
||||
result = AccessPath::getAReferenceOrAssignmentTo(root, name + ".prototype").getALocalSource() and
|
||||
this = AccessPath::getAnAssignmentTo(root, name)
|
||||
)
|
||||
or
|
||||
exists(ExtendCall call |
|
||||
call.getDestinationOperand() = this.getAPrototypeReference() and
|
||||
result = call.getASourceOperand()
|
||||
)
|
||||
}
|
||||
|
||||
private PropertyAccessor getAnAccessor(MemberKind kind) {
|
||||
// Only applies to function-style classes
|
||||
astNode instanceof Function and
|
||||
result.getObjectExpr() = this.getAPrototypeReference().asExpr() and
|
||||
(
|
||||
kind = MemberKind::getter() and
|
||||
result instanceof PropertyGetter
|
||||
or
|
||||
kind = MemberKind::setter() and
|
||||
result instanceof PropertySetter
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a dataflow node that refers to the superclass of this class.
|
||||
*/
|
||||
DataFlow::Node getASuperClassNode() { result = super.getASuperClassNode() }
|
||||
DataFlow::Node getASuperClassNode() {
|
||||
// ES6 class superclass
|
||||
astNode instanceof ClassDefinition and
|
||||
result = astNode.(ClassDefinition).getSuperClass().flow()
|
||||
or
|
||||
(
|
||||
// C.prototype = Object.create(D.prototype)
|
||||
exists(DataFlow::InvokeNode objectCreate, DataFlow::PropRead superProto |
|
||||
this.getAPropertySource("prototype") = objectCreate and
|
||||
objectCreate = DataFlow::globalVarRef("Object").getAMemberCall("create") and
|
||||
superProto.flowsTo(objectCreate.getArgument(0)) and
|
||||
superProto.getPropertyName() = "prototype" and
|
||||
result = superProto.getBase()
|
||||
)
|
||||
or
|
||||
// C.prototype = new D()
|
||||
exists(DataFlow::NewNode newCall |
|
||||
this.getAPropertySource("prototype") = newCall and
|
||||
result = newCall.getCalleeNode()
|
||||
)
|
||||
or
|
||||
// util.inherits(C, D);
|
||||
exists(DataFlow::CallNode inheritsCall |
|
||||
inheritsCall = DataFlow::moduleMember("util", "inherits").getACall()
|
||||
|
|
||||
this = inheritsCall.getArgument(0).getALocalSource() and
|
||||
result = inheritsCall.getArgument(1)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a direct super class of this class.
|
||||
@@ -1136,13 +1310,47 @@ class ClassNode extends DataFlow::SourceNode instanceof ClassNode::Range {
|
||||
* Gets the type annotation for the field `fieldName`, if any.
|
||||
*/
|
||||
TypeAnnotation getFieldTypeAnnotation(string fieldName) {
|
||||
result = super.getFieldTypeAnnotation(fieldName)
|
||||
exists(FieldDeclaration field |
|
||||
field.getDeclaringClass() = astNode and
|
||||
fieldName = field.getName() and
|
||||
result = field.getTypeAnnotation()
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a decorator applied to this class.
|
||||
*/
|
||||
DataFlow::Node getADecorator() { result = super.getADecorator() }
|
||||
DataFlow::Node getADecorator() {
|
||||
astNode instanceof ClassDefinition and
|
||||
result = astNode.(ClassDefinition).getADecorator().getExpression().flow()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper predicate to get a prototype reference in a file.
|
||||
*/
|
||||
private DataFlow::PropRef getAPrototypeReferenceInFile(string name, File f) {
|
||||
result.getBase() = AccessPath::getAReferenceOrAssignmentTo(name) and
|
||||
result.getPropertyName() = "prototype" and
|
||||
result.getFile() = f
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper predicate to get an instantiation in a file.
|
||||
*/
|
||||
private DataFlow::NewNode getAnInstantiationInFile(string name, File f) {
|
||||
result = AccessPath::getAReferenceTo(name).(DataFlow::LocalSourceNode).getAnInstantiation() and
|
||||
result.getFile() = f
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a reference to the function `func`, where there exists a read/write of the "prototype" property on that reference.
|
||||
*/
|
||||
pragma[noinline]
|
||||
private DataFlow::SourceNode getAFunctionValueWithPrototype(AbstractValue func) {
|
||||
exists(result.getAPropertyReference("prototype")) and
|
||||
result.analyze().getAValue() = pragma[only_bind_into](func) and
|
||||
func instanceof AbstractCallable // the join-order goes bad if `func` has type `AbstractFunction`.
|
||||
}
|
||||
|
||||
module ClassNode {
|
||||
@@ -1214,225 +1422,7 @@ module ClassNode {
|
||||
DataFlow::Node getADecorator() { none() }
|
||||
}
|
||||
|
||||
/**
|
||||
* An ES6 class as a `ClassNode` instance.
|
||||
*/
|
||||
private class ES6Class extends Range, DataFlow::ValueNode {
|
||||
override ClassDefinition astNode;
|
||||
|
||||
override string getName() { result = astNode.getName() }
|
||||
|
||||
override string describe() { result = astNode.describe() }
|
||||
|
||||
override FunctionNode getConstructor() { result = astNode.getConstructor().getBody().flow() }
|
||||
|
||||
override FunctionNode getInstanceMember(string name, MemberKind kind) {
|
||||
exists(MethodDeclaration method |
|
||||
method = astNode.getMethod(name) and
|
||||
not method.isStatic() and
|
||||
kind = MemberKind::of(method) and
|
||||
result = method.getBody().flow()
|
||||
)
|
||||
or
|
||||
kind = MemberKind::method() and
|
||||
result = this.getConstructor().getReceiver().getAPropertySource(name)
|
||||
}
|
||||
|
||||
override FunctionNode getAnInstanceMember(MemberKind kind) {
|
||||
exists(MethodDeclaration method |
|
||||
method = astNode.getAMethod() and
|
||||
not method.isStatic() and
|
||||
kind = MemberKind::of(method) and
|
||||
result = method.getBody().flow()
|
||||
)
|
||||
or
|
||||
kind = MemberKind::method() and
|
||||
result = this.getConstructor().getReceiver().getAPropertySource()
|
||||
}
|
||||
|
||||
override FunctionNode getStaticMember(string name, MemberKind kind) {
|
||||
exists(MethodDeclaration method |
|
||||
method = astNode.getMethod(name) and
|
||||
method.isStatic() and
|
||||
kind = MemberKind::of(method) and
|
||||
result = method.getBody().flow()
|
||||
)
|
||||
or
|
||||
kind.isMethod() and
|
||||
result = this.getAPropertySource(name)
|
||||
}
|
||||
|
||||
override FunctionNode getAStaticMember(MemberKind kind) {
|
||||
exists(MethodDeclaration method |
|
||||
method = astNode.getAMethod() and
|
||||
method.isStatic() and
|
||||
kind = MemberKind::of(method) and
|
||||
result = method.getBody().flow()
|
||||
)
|
||||
or
|
||||
kind.isMethod() and
|
||||
result = this.getAPropertySource()
|
||||
}
|
||||
|
||||
override DataFlow::Node getASuperClassNode() { result = astNode.getSuperClass().flow() }
|
||||
|
||||
override TypeAnnotation getFieldTypeAnnotation(string fieldName) {
|
||||
exists(FieldDeclaration field |
|
||||
field.getDeclaringClass() = astNode and
|
||||
fieldName = field.getName() and
|
||||
result = field.getTypeAnnotation()
|
||||
)
|
||||
}
|
||||
|
||||
override DataFlow::Node getADecorator() {
|
||||
result = astNode.getADecorator().getExpression().flow()
|
||||
}
|
||||
}
|
||||
|
||||
private DataFlow::PropRef getAPrototypeReferenceInFile(string name, File f) {
|
||||
result.getBase() = AccessPath::getAReferenceOrAssignmentTo(name) and
|
||||
result.getPropertyName() = "prototype" and
|
||||
result.getFile() = f
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private DataFlow::NewNode getAnInstantiationInFile(string name, File f) {
|
||||
result = AccessPath::getAReferenceTo(name).(DataFlow::LocalSourceNode).getAnInstantiation() and
|
||||
result.getFile() = f
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a reference to the function `func`, where there exists a read/write of the "prototype" property on that reference.
|
||||
*/
|
||||
pragma[noinline]
|
||||
private DataFlow::SourceNode getAFunctionValueWithPrototype(AbstractValue func) {
|
||||
exists(result.getAPropertyReference("prototype")) and
|
||||
result.analyze().getAValue() = pragma[only_bind_into](func) and
|
||||
func instanceof AbstractFunction // the join-order goes bad if `func` has type `AbstractFunction`.
|
||||
}
|
||||
|
||||
/**
|
||||
* A function definition, targeted by a `new`-call or with prototype manipulation, seen as a `ClassNode` instance.
|
||||
*/
|
||||
class FunctionStyleClass extends Range, DataFlow::ValueNode {
|
||||
override Function astNode;
|
||||
AbstractFunction function;
|
||||
|
||||
FunctionStyleClass() {
|
||||
function.getFunction() = astNode and
|
||||
(
|
||||
exists(getAFunctionValueWithPrototype(function))
|
||||
or
|
||||
function = any(NewNode new).getCalleeNode().analyze().getAValue()
|
||||
or
|
||||
exists(string name | this = AccessPath::getAnAssignmentTo(name) |
|
||||
exists(getAPrototypeReferenceInFile(name, this.getFile()))
|
||||
or
|
||||
exists(getAnInstantiationInFile(name, this.getFile()))
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
override string getName() { result = astNode.getName() }
|
||||
|
||||
override string describe() { result = astNode.describe() }
|
||||
|
||||
override FunctionNode getConstructor() { result = this }
|
||||
|
||||
private PropertyAccessor getAnAccessor(MemberKind kind) {
|
||||
result.getObjectExpr() = this.getAPrototypeReference().asExpr() and
|
||||
(
|
||||
kind = MemberKind::getter() and
|
||||
result instanceof PropertyGetter
|
||||
or
|
||||
kind = MemberKind::setter() and
|
||||
result instanceof PropertySetter
|
||||
)
|
||||
}
|
||||
|
||||
override FunctionNode getInstanceMember(string name, MemberKind kind) {
|
||||
kind = MemberKind::method() and
|
||||
result = this.getAPrototypeReference().getAPropertySource(name)
|
||||
or
|
||||
kind = MemberKind::method() and
|
||||
result = this.getConstructor().getReceiver().getAPropertySource(name)
|
||||
or
|
||||
exists(PropertyAccessor accessor |
|
||||
accessor = this.getAnAccessor(kind) and
|
||||
accessor.getName() = name and
|
||||
result = accessor.getInit().flow()
|
||||
)
|
||||
}
|
||||
|
||||
override FunctionNode getAnInstanceMember(MemberKind kind) {
|
||||
kind = MemberKind::method() and
|
||||
result = this.getAPrototypeReference().getAPropertySource()
|
||||
or
|
||||
kind = MemberKind::method() and
|
||||
result = this.getConstructor().getReceiver().getAPropertySource()
|
||||
or
|
||||
exists(PropertyAccessor accessor |
|
||||
accessor = this.getAnAccessor(kind) and
|
||||
result = accessor.getInit().flow()
|
||||
)
|
||||
}
|
||||
|
||||
override FunctionNode getStaticMember(string name, MemberKind kind) {
|
||||
kind.isMethod() and
|
||||
result = this.getAPropertySource(name)
|
||||
}
|
||||
|
||||
override FunctionNode getAStaticMember(MemberKind kind) {
|
||||
kind.isMethod() and
|
||||
result = this.getAPropertySource()
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a reference to the prototype of this class.
|
||||
*/
|
||||
DataFlow::SourceNode getAPrototypeReference() {
|
||||
exists(DataFlow::SourceNode base | base = getAFunctionValueWithPrototype(function) |
|
||||
result = base.getAPropertyRead("prototype")
|
||||
or
|
||||
result = base.getAPropertySource("prototype")
|
||||
)
|
||||
or
|
||||
exists(string name |
|
||||
this = AccessPath::getAnAssignmentTo(name) and
|
||||
result = getAPrototypeReferenceInFile(name, this.getFile())
|
||||
)
|
||||
or
|
||||
exists(ExtendCall call |
|
||||
call.getDestinationOperand() = this.getAPrototypeReference() and
|
||||
result = call.getASourceOperand()
|
||||
)
|
||||
}
|
||||
|
||||
override DataFlow::Node getASuperClassNode() {
|
||||
// C.prototype = Object.create(D.prototype)
|
||||
exists(DataFlow::InvokeNode objectCreate, DataFlow::PropRead superProto |
|
||||
this.getAPropertySource("prototype") = objectCreate and
|
||||
objectCreate = DataFlow::globalVarRef("Object").getAMemberCall("create") and
|
||||
superProto.flowsTo(objectCreate.getArgument(0)) and
|
||||
superProto.getPropertyName() = "prototype" and
|
||||
result = superProto.getBase()
|
||||
)
|
||||
or
|
||||
// C.prototype = new D()
|
||||
exists(DataFlow::NewNode newCall |
|
||||
this.getAPropertySource("prototype") = newCall and
|
||||
result = newCall.getCalleeNode()
|
||||
)
|
||||
or
|
||||
// util.inherits(C, D);
|
||||
exists(DataFlow::CallNode inheritsCall |
|
||||
inheritsCall = DataFlow::moduleMember("util", "inherits").getACall()
|
||||
|
|
||||
this = inheritsCall.getArgument(0).getALocalSource() and
|
||||
result = inheritsCall.getArgument(1)
|
||||
)
|
||||
}
|
||||
}
|
||||
deprecated class FunctionStyleClass = ClassNode;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -254,7 +254,7 @@ module CallGraph {
|
||||
not exists(DataFlow::ClassNode cls |
|
||||
node = cls.getConstructor().getReceiver()
|
||||
or
|
||||
node = cls.(DataFlow::ClassNode::FunctionStyleClass).getAPrototypeReference()
|
||||
node = cls.(DataFlow::ClassNode).getAPrototypeReference()
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -264,3 +264,7 @@ module Stage {
|
||||
cached
|
||||
predicate backref() { optionalStep(_, _, _) }
|
||||
}
|
||||
|
||||
predicate unsupportedCallable = Private::unsupportedCallable/1;
|
||||
|
||||
predicate unsupportedCallable = Private::unsupportedCallable/4;
|
||||
|
||||
@@ -25,7 +25,7 @@ private class AnalyzedImportSpecifier extends AnalyzedVarDef, @import_specifier
|
||||
|
||||
override predicate isIncomplete(DataFlow::Incompleteness cause) {
|
||||
// mark as incomplete if the import could rely on the lookup path
|
||||
mayDependOnLookupPath(id.getImportedPath().getValue()) and
|
||||
mayDependOnLookupPath(id.getImportedPathString()) and
|
||||
cause = "import"
|
||||
or
|
||||
// mark as incomplete if we cannot fully analyze this import
|
||||
@@ -260,7 +260,7 @@ private class AnalyzedAmdImport extends AnalyzedPropertyRead, DataFlow::Node {
|
||||
Module required;
|
||||
|
||||
AnalyzedAmdImport() {
|
||||
exists(AmdModule amd, PathExpr dep |
|
||||
exists(AmdModule amd, Expr dep |
|
||||
exists(Parameter p |
|
||||
amd.getDefine().dependencyParameter(dep, p) and
|
||||
this = DataFlow::parameterNode(p)
|
||||
|
||||
@@ -75,11 +75,10 @@ module SsaDataflowInput implements DataFlowIntegrationInputSig {
|
||||
Guard() { this = any(js::ConditionGuardNode g).getTest() }
|
||||
|
||||
/**
|
||||
* Holds if the control flow branching from `bb1` is dependent on this guard,
|
||||
* and that the edge from `bb1` to `bb2` corresponds to the evaluation of this
|
||||
* guard to `branch`.
|
||||
* Holds if the evaluation of this guard to `branch` corresponds to the edge
|
||||
* from `bb1` to `bb2`.
|
||||
*/
|
||||
predicate controlsBranchEdge(js::BasicBlock bb1, js::BasicBlock bb2, boolean branch) {
|
||||
predicate hasBranchEdge(js::BasicBlock bb1, js::BasicBlock bb2, boolean branch) {
|
||||
exists(js::ConditionGuardNode g |
|
||||
g.getTest() = this and
|
||||
bb1 = this.getBasicBlock() and
|
||||
@@ -87,6 +86,15 @@ module SsaDataflowInput implements DataFlowIntegrationInputSig {
|
||||
branch = g.getOutcome()
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if this guard evaluating to `branch` controls the control-flow
|
||||
* branch edge from `bb1` to `bb2`. That is, following the edge from
|
||||
* `bb1` to `bb2` implies that this guard evaluated to `branch`.
|
||||
*/
|
||||
predicate controlsBranchEdge(js::BasicBlock bb1, js::BasicBlock bb2, boolean branch) {
|
||||
this.hasBranchEdge(bb1, bb2, branch)
|
||||
}
|
||||
}
|
||||
|
||||
pragma[inline]
|
||||
|
||||
@@ -151,6 +151,11 @@ class ExternalNpmDependency extends NpmDependency {
|
||||
}
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private string getPackagePrefix(Import i) {
|
||||
result = i.getImportedPathString().(FilePath).getPackagePrefix()
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if import `i` may refer to the declared dependency `dep` of package `pkg`,
|
||||
* where the result value is the nesting depth of the file containing `i` within `pkg`.
|
||||
@@ -159,7 +164,7 @@ private int importsDependency(Import i, NpmPackage pkg, NpmDependency dep) {
|
||||
exists(string name |
|
||||
dep = pkg.getPackageJson().getADependenciesObject(_).getPropValue(name) and
|
||||
not exists(i.getImportedModule()) and
|
||||
i.getImportedPath().getComponent(0) = name and
|
||||
name = getPackagePrefix(i) and
|
||||
i.getEnclosingModule() = pkg.getAModule() and
|
||||
result = distance(pkg, i.getFile())
|
||||
)
|
||||
|
||||
@@ -9,6 +9,7 @@ private import semmle.javascript.security.dataflow.ClientSideUrlRedirectCustomiz
|
||||
private import semmle.javascript.DynamicPropertyAccess
|
||||
private import semmle.javascript.dataflow.internal.PreCallGraphStep
|
||||
private import semmle.javascript.ViewComponentInput
|
||||
private import semmle.javascript.internal.paths.PathExprResolver
|
||||
|
||||
/**
|
||||
* Provides classes for working with Angular (also known as Angular 2.x) applications.
|
||||
@@ -240,18 +241,15 @@ module Angular2 {
|
||||
|
||||
class TemplateTopLevel = Templating::TemplateTopLevel;
|
||||
|
||||
/** The RHS of a `templateUrl` property, seen as a path expression. */
|
||||
private class TemplateUrlPath extends PathExpr {
|
||||
TemplateUrlPath() {
|
||||
exists(Property prop |
|
||||
prop.getName() = "templateUrl" and
|
||||
this = prop.getInit()
|
||||
)
|
||||
}
|
||||
|
||||
override string getValue() { result = this.(Expr).getStringValue() }
|
||||
private predicate shouldResolveExpr(Expr e) {
|
||||
exists(Property prop |
|
||||
prop.getName() = "templateUrl" and
|
||||
e = prop.getInit()
|
||||
)
|
||||
}
|
||||
|
||||
private module Resolver = ResolveExpr<shouldResolveExpr/1>;
|
||||
|
||||
/**
|
||||
* Holds if the value of `attrib` is interpreted as an Angular expression.
|
||||
*/
|
||||
@@ -338,7 +336,7 @@ module Angular2 {
|
||||
*/
|
||||
pragma[noinline]
|
||||
File getTemplateFile() {
|
||||
result = decorator.getOptionArgument(0, "templateUrl").asExpr().(PathExpr).resolve()
|
||||
result = Resolver::resolveExpr(decorator.getOptionArgument(0, "templateUrl").asExpr())
|
||||
}
|
||||
|
||||
/** Gets an element in the HTML template of this component. */
|
||||
|
||||
@@ -32,7 +32,7 @@ pragma[nomagic]
|
||||
private predicate isAngularTopLevel(TopLevel tl) {
|
||||
exists(Import imprt |
|
||||
imprt.getTopLevel() = tl and
|
||||
imprt.getImportedPath().getValue() = "angular"
|
||||
imprt.getImportedPathString() = "angular"
|
||||
)
|
||||
or
|
||||
exists(GlobalVarAccess global |
|
||||
@@ -550,20 +550,25 @@ class DirectiveTargetName extends string {
|
||||
*
|
||||
* See https://docs.angularjs.org/api/ng/service/$location for details.
|
||||
*/
|
||||
private class LocationFlowSource extends RemoteFlowSource instanceof DataFlow::MethodCallNode {
|
||||
private class LocationFlowSource extends ClientSideRemoteFlowSource instanceof DataFlow::MethodCallNode
|
||||
{
|
||||
private ClientSideRemoteFlowKind kind;
|
||||
|
||||
LocationFlowSource() {
|
||||
exists(ServiceReference service, string m, int n |
|
||||
service.getName() = "$location" and
|
||||
this = service.getAMethodCall(m) and
|
||||
n = super.getNumArgument()
|
||||
|
|
||||
m = "search" and n < 2
|
||||
m = "search" and n < 2 and kind.isQuery()
|
||||
or
|
||||
m = "hash" and n = 0
|
||||
m = "hash" and n = 0 and kind.isFragment()
|
||||
)
|
||||
}
|
||||
|
||||
override string getSourceType() { result = "$location" }
|
||||
|
||||
override ClientSideRemoteFlowKind getKind() { result = kind }
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
*/
|
||||
|
||||
import javascript
|
||||
private import semmle.javascript.internal.paths.PathMapping
|
||||
|
||||
module Babel {
|
||||
/**
|
||||
@@ -138,7 +139,7 @@ module Babel {
|
||||
/**
|
||||
* An import path expression that may be transformed by `babel-plugin-root-import`.
|
||||
*/
|
||||
private class BabelRootTransformedPathExpr extends PathExpr, Expr {
|
||||
deprecated private class BabelRootTransformedPathExpr extends PathExpr, Expr {
|
||||
RootImportConfig plugin;
|
||||
string prefix;
|
||||
string mappedPrefix;
|
||||
@@ -166,7 +167,7 @@ module Babel {
|
||||
/**
|
||||
* An import path transformed by `babel-plugin-root-import`.
|
||||
*/
|
||||
private class BabelRootTransformedPath extends PathString {
|
||||
deprecated private class BabelRootTransformedPath extends PathString {
|
||||
BabelRootTransformedPathExpr pathExpr;
|
||||
|
||||
BabelRootTransformedPath() { this = pathExpr.getValue() }
|
||||
@@ -202,4 +203,12 @@ module Babel {
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private class BabelPathMapping extends PathMapping, RootImportConfig {
|
||||
override File getAnAffectedFile() { result = this.getConfig().getAContainerInScope() }
|
||||
|
||||
override predicate hasPrefixPathMapping(string pattern, Container newContext, string newPath) {
|
||||
newPath = this.getRoot(pattern) and newContext = this.getFolder()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ module LazyCache {
|
||||
|
||||
override Module getEnclosingModule() { result = this.getTopLevel() }
|
||||
|
||||
override PathExpr getImportedPath() { result = this.getArgument(0) }
|
||||
override Expr getImportedPathExpr() { result = this.getArgument(0) }
|
||||
|
||||
private LazyCacheVariable getVariable() { result = cache }
|
||||
|
||||
@@ -58,7 +58,7 @@ module LazyCache {
|
||||
}
|
||||
|
||||
/** A constant path element appearing in a call to a lazy-cache object. */
|
||||
private class LazyCachePathExpr extends PathExpr, ConstantString {
|
||||
deprecated private class LazyCachePathExpr extends PathExpr, ConstantString {
|
||||
LazyCachePathExpr() { this = any(LazyCacheImport rp).getArgument(0) }
|
||||
|
||||
override string getValue() { result = this.getStringValue() }
|
||||
|
||||
@@ -733,7 +733,7 @@ private class ReactRouterSource extends ClientSideRemoteFlowSource {
|
||||
* Holds if `mod` transitively depends on `react-router-dom`.
|
||||
*/
|
||||
private predicate dependsOnReactRouter(Module mod) {
|
||||
mod.getAnImport().getImportedPath().getValue() = "react-router-dom"
|
||||
mod.getAnImport().getImportedPathString() = "react-router-dom"
|
||||
or
|
||||
dependsOnReactRouter(mod.getAnImportedModule())
|
||||
}
|
||||
|
||||
@@ -442,7 +442,7 @@ module Vue {
|
||||
|
||||
override DataFlow::SourceNode getASource() {
|
||||
exists(Import imprt |
|
||||
imprt.getImportedPath().resolve() instanceof VueFile and
|
||||
imprt.getImportedFile() instanceof VueFile and
|
||||
result = imprt.getImportedModuleNode()
|
||||
)
|
||||
}
|
||||
@@ -494,7 +494,7 @@ module Vue {
|
||||
// There is no explicit `new Vue()` call in .vue files, so instead get all the imports
|
||||
// of the .vue file.
|
||||
exists(Import imprt |
|
||||
imprt.getImportedPath().resolve() = file and
|
||||
imprt.getImportedFile() = file and
|
||||
result.asSource() = imprt.getImportedModuleNode()
|
||||
)
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
private import javascript
|
||||
private import internal.ApiGraphModels as Shared
|
||||
private import internal.ApiGraphModelsSpecific as Specific
|
||||
private import semmle.javascript.dataflow.internal.FlowSummaryPrivate
|
||||
private import semmle.javascript.endpoints.EndpointNaming as EndpointNaming
|
||||
import Shared::ModelInput as ModelInput
|
||||
import Shared::ModelOutput as ModelOutput
|
||||
@@ -45,12 +46,94 @@ private class ThreatModelSourceFromDataExtension extends ThreatModelSource::Rang
|
||||
}
|
||||
}
|
||||
|
||||
private class SummarizedCallableFromModel extends DataFlow::SummarizedCallable {
|
||||
string type;
|
||||
string path;
|
||||
|
||||
SummarizedCallableFromModel() {
|
||||
ModelOutput::relevantSummaryModel(type, path, _, _, _, _) and
|
||||
this = type + ";" + path
|
||||
}
|
||||
|
||||
override DataFlow::InvokeNode getACall() { ModelOutput::resolvedSummaryBase(type, path, result) }
|
||||
|
||||
override predicate propagatesFlow(
|
||||
string input, string output, boolean preservesValue, string model
|
||||
) {
|
||||
exists(string kind | ModelOutput::relevantSummaryModel(type, path, input, output, kind, model) |
|
||||
kind = "value" and
|
||||
preservesValue = true
|
||||
or
|
||||
kind = "taint" and
|
||||
preservesValue = false
|
||||
)
|
||||
}
|
||||
|
||||
predicate hasTypeAndPath(string type_, string path_) { type = type_ and path = path_ }
|
||||
|
||||
predicate isUnsupportedByFlowSummaries() { unsupportedCallable(this) }
|
||||
}
|
||||
|
||||
private predicate shouldInduceStepsFromSummary(string type, string path) {
|
||||
exists(SummarizedCallableFromModel callable |
|
||||
callable.isUnsupportedByFlowSummaries() and
|
||||
callable.hasTypeAndPath(type, path)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `path` is an input or output spec for a summary with the given `base` node.
|
||||
*/
|
||||
pragma[nomagic]
|
||||
private predicate relevantInputOutputPath(API::InvokeNode base, AccessPath inputOrOutput) {
|
||||
exists(string type, string input, string output, string path |
|
||||
// If the summary for 'callable' could not be handled as a flow summary, we need to evaluate
|
||||
// its inputs and outputs to a set of nodes, so we can generate steps instead.
|
||||
shouldInduceStepsFromSummary(type, path) and
|
||||
ModelOutput::resolvedSummaryBase(type, path, base) and
|
||||
ModelOutput::relevantSummaryModel(type, path, input, output, _, _) and
|
||||
inputOrOutput = [input, output]
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the API node for the first `n` tokens of the given input/output path, evaluated relative to `baseNode`.
|
||||
*/
|
||||
private API::Node getNodeFromInputOutputPath(API::InvokeNode baseNode, AccessPath path, int n) {
|
||||
relevantInputOutputPath(baseNode, path) and
|
||||
(
|
||||
n = 1 and
|
||||
result = Shared::getSuccessorFromInvoke(baseNode, path.getToken(0))
|
||||
or
|
||||
result =
|
||||
Shared::getSuccessorFromNode(getNodeFromInputOutputPath(baseNode, path, n - 1),
|
||||
path.getToken(n - 1))
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the API node for the given input/output path, evaluated relative to `baseNode`.
|
||||
*/
|
||||
private API::Node getNodeFromInputOutputPath(API::InvokeNode baseNode, AccessPath path) {
|
||||
result = getNodeFromInputOutputPath(baseNode, path, path.getNumToken())
|
||||
}
|
||||
|
||||
private predicate summaryStep(API::Node pred, API::Node succ, string kind) {
|
||||
exists(string type, string path, API::InvokeNode base, AccessPath input, AccessPath output |
|
||||
shouldInduceStepsFromSummary(type, path) and
|
||||
ModelOutput::relevantSummaryModel(type, path, input, output, kind, _) and
|
||||
ModelOutput::resolvedSummaryBase(type, path, base) and
|
||||
pred = getNodeFromInputOutputPath(base, input) and
|
||||
succ = getNodeFromInputOutputPath(base, output)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Like `ModelOutput::summaryStep` but with API nodes mapped to data-flow nodes.
|
||||
*/
|
||||
private predicate summaryStepNodes(DataFlow::Node pred, DataFlow::Node succ, string kind) {
|
||||
exists(API::Node predNode, API::Node succNode |
|
||||
Specific::summaryStep(predNode, succNode, kind) and
|
||||
summaryStep(predNode, succNode, kind) and
|
||||
pred = predNode.asSink() and
|
||||
succ = succNode.asSource()
|
||||
)
|
||||
|
||||
@@ -58,7 +58,7 @@ predicate parseTypeString(string rawType, string package, string qualifiedName)
|
||||
predicate isPackageUsed(string package) {
|
||||
package = "global"
|
||||
or
|
||||
package = any(JS::Import imp).getImportedPath().getValue()
|
||||
package = any(JS::Import imp).getImportedPathString()
|
||||
or
|
||||
any(JS::TypeName t).hasQualifiedName(package, _)
|
||||
or
|
||||
@@ -272,51 +272,6 @@ predicate invocationMatchesExtraCallSiteFilter(API::InvokeNode invoke, AccessPat
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `path` is an input or output spec for a summary with the given `base` node.
|
||||
*/
|
||||
pragma[nomagic]
|
||||
private predicate relevantInputOutputPath(API::InvokeNode base, AccessPath inputOrOutput) {
|
||||
exists(string type, string input, string output, string path |
|
||||
ModelOutput::relevantSummaryModel(type, path, input, output, _, _) and
|
||||
ModelOutput::resolvedSummaryBase(type, path, base) and
|
||||
inputOrOutput = [input, output]
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the API node for the first `n` tokens of the given input/output path, evaluated relative to `baseNode`.
|
||||
*/
|
||||
private API::Node getNodeFromInputOutputPath(API::InvokeNode baseNode, AccessPath path, int n) {
|
||||
relevantInputOutputPath(baseNode, path) and
|
||||
(
|
||||
n = 1 and
|
||||
result = getSuccessorFromInvoke(baseNode, path.getToken(0))
|
||||
or
|
||||
result =
|
||||
getSuccessorFromNode(getNodeFromInputOutputPath(baseNode, path, n - 1), path.getToken(n - 1))
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the API node for the given input/output path, evaluated relative to `baseNode`.
|
||||
*/
|
||||
private API::Node getNodeFromInputOutputPath(API::InvokeNode baseNode, AccessPath path) {
|
||||
result = getNodeFromInputOutputPath(baseNode, path, path.getNumToken())
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if a CSV summary contributed the step `pred -> succ` of the given `kind`.
|
||||
*/
|
||||
predicate summaryStep(API::Node pred, API::Node succ, string kind) {
|
||||
exists(string type, string path, API::InvokeNode base, AccessPath input, AccessPath output |
|
||||
ModelOutput::relevantSummaryModel(type, path, input, output, kind, _) and
|
||||
ModelOutput::resolvedSummaryBase(type, path, base) and
|
||||
pred = getNodeFromInputOutputPath(base, input) and
|
||||
succ = getNodeFromInputOutputPath(base, output)
|
||||
)
|
||||
}
|
||||
|
||||
class InvokeNode = API::InvokeNode;
|
||||
|
||||
/** Gets an `InvokeNode` corresponding to an invocation of `node`. */
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
/**
|
||||
* Provides predicates for use in `Folder::ResolveSig` in order to resolve
|
||||
* paths using JavaScript semantics.
|
||||
*/
|
||||
|
||||
private import javascript
|
||||
private import semmle.javascript.TSConfig
|
||||
|
||||
/**
|
||||
* Gets a folder name that is a common source folder name.
|
||||
*/
|
||||
string getASrcFolderName() { result = ["ts", "js", "src", "lib"] }
|
||||
|
||||
/**
|
||||
* Gets a folder name that is a common build output folder name.
|
||||
*/
|
||||
string getABuildOutputFolderName() { result = ["dist", "build", "out", "lib"] }
|
||||
|
||||
/**
|
||||
* Provides predicates for use in a `Folder::ResolveSig` in order to resolve
|
||||
* paths using JavaScript semantics.
|
||||
*
|
||||
* This accounts for two things:
|
||||
* - automatic file extensions (e.g `./foo` may resolve to `./foo.js`)
|
||||
* - mapping compiled-generated files back to their original source files
|
||||
*/
|
||||
module JSPaths {
|
||||
private Container getAnAdditionalChildFromBuildMapping(Container base, string name) {
|
||||
// When importing a .js file, map to the original file that compiles to the .js file.
|
||||
exists(string stem |
|
||||
result = base.(Folder).getJavaScriptFileOrTypings(stem) and
|
||||
name = stem + ".js"
|
||||
)
|
||||
or
|
||||
// Redirect './bar' to 'foo' given a tsconfig like:
|
||||
//
|
||||
// { include: ["foo"], compilerOptions: { outDir: "./bar" }}
|
||||
//
|
||||
exists(TSConfig tsconfig |
|
||||
name =
|
||||
tsconfig.getCompilerOptions().getPropStringValue("outDir").regexpReplaceAll("^\\./", "") and
|
||||
base = tsconfig.getFolder() and
|
||||
result = tsconfig.getEffectiveRootDir()
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an additional child of `base` to include when resolving JS paths.
|
||||
*/
|
||||
pragma[nomagic]
|
||||
Container getAnAdditionalChild(Container base, string name) {
|
||||
// Automatically fill in file extensions
|
||||
result = base.(Folder).getJavaScriptFileOrTypings(name)
|
||||
or
|
||||
result = getAnAdditionalChildFromBuildMapping(base, name)
|
||||
or
|
||||
// Heuristic version of the above based on commonly used source and build folder names
|
||||
not exists(getAnAdditionalChildFromBuildMapping(base, name)) and
|
||||
exists(Folder folder | base = folder |
|
||||
folder = any(PackageJson pkg).getFolder() and
|
||||
name = getABuildOutputFolderName() and
|
||||
not exists(folder.getJavaScriptFileOrTypings(name)) and
|
||||
(
|
||||
result = folder.getChildContainer(getASrcFolderName())
|
||||
or
|
||||
result = folder
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
private import javascript
|
||||
private import semmle.javascript.internal.paths.JSPaths
|
||||
|
||||
/**
|
||||
* A `package.json` file. The class is an extension of the `PackageJson` class with some internal path-resolution predicates.
|
||||
*/
|
||||
class PackageJsonEx extends PackageJson {
|
||||
private JsonValue getAPartOfExportsSection(string pattern) {
|
||||
result = this.getPropValue("exports") and
|
||||
pattern = ""
|
||||
or
|
||||
exists(string prop, string prevPath |
|
||||
result = this.getAPartOfExportsSection(prevPath).getPropValue(prop) and
|
||||
if prop.matches("./%") then pattern = prop.suffix(2) else pattern = prevPath
|
||||
)
|
||||
}
|
||||
|
||||
predicate hasPathMapping(string pattern, string newPath) {
|
||||
this.getAPartOfExportsSection(pattern).getStringValue() = newPath
|
||||
}
|
||||
|
||||
predicate hasExactPathMapping(string pattern, string newPath) {
|
||||
this.getAPartOfExportsSection(pattern).getStringValue() = newPath and
|
||||
not pattern.matches("%*%")
|
||||
}
|
||||
|
||||
predicate hasPrefixPathMapping(string pattern, string newPath) {
|
||||
this.hasPathMapping(pattern + "*", newPath + "*")
|
||||
}
|
||||
|
||||
predicate hasExactPathMappingTo(string pattern, Container target) {
|
||||
exists(string newPath |
|
||||
this.hasExactPathMapping(pattern, newPath) and
|
||||
target = Resolver::resolve(this.getFolder(), newPath)
|
||||
)
|
||||
}
|
||||
|
||||
predicate hasPrefixPathMappingTo(string pattern, Container target) {
|
||||
exists(string newPath |
|
||||
this.hasPrefixPathMapping(pattern, newPath) and
|
||||
target = Resolver::resolve(this.getFolder(), newPath)
|
||||
)
|
||||
}
|
||||
|
||||
string getMainPath() { result = this.getPropStringValue(["main", "module"]) }
|
||||
|
||||
File getMainFile() {
|
||||
exists(Container main | main = Resolver::resolve(this.getFolder(), this.getMainPath()) |
|
||||
result = main
|
||||
or
|
||||
result = main.(Folder).getJavaScriptFileOrTypings("index")
|
||||
)
|
||||
}
|
||||
|
||||
File getMainFileOrBestGuess() {
|
||||
result = this.getMainFile()
|
||||
or
|
||||
result = guessPackageJsonMain1(this)
|
||||
or
|
||||
result = guessPackageJsonMain2(this)
|
||||
}
|
||||
|
||||
string getAPathInFilesArray() {
|
||||
result = this.getPropValue("files").(JsonArray).getElementStringValue(_)
|
||||
}
|
||||
|
||||
Container getAFileInFilesArray() {
|
||||
result = Resolver::resolve(this.getFolder(), this.getAPathInFilesArray())
|
||||
}
|
||||
|
||||
override File getTypingsFile() {
|
||||
result = Resolver::resolve(this.getFolder(), this.getTypings())
|
||||
or
|
||||
not exists(this.getTypings()) and
|
||||
exists(File mainFile |
|
||||
mainFile = this.getMainFileOrBestGuess() and
|
||||
result =
|
||||
mainFile
|
||||
.getParentContainer()
|
||||
.getFile(mainFile.getStem().regexpReplaceAll("\\.d$", "") + ".d.ts")
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private module ResolverConfig implements Folder::ResolveSig {
|
||||
additional predicate shouldResolve(PackageJsonEx pkg, Container base, string path) {
|
||||
base = pkg.getFolder() and
|
||||
(
|
||||
pkg.hasExactPathMapping(_, path)
|
||||
or
|
||||
pkg.hasPrefixPathMapping(_, path)
|
||||
or
|
||||
path = pkg.getMainPath()
|
||||
or
|
||||
path = pkg.getAPathInFilesArray()
|
||||
or
|
||||
path = pkg.getTypings()
|
||||
)
|
||||
}
|
||||
|
||||
predicate shouldResolve(Container base, string path) { shouldResolve(_, base, path) }
|
||||
|
||||
predicate getAnAdditionalChild = JSPaths::getAnAdditionalChild/2;
|
||||
|
||||
predicate isOptionalPathComponent(string segment) {
|
||||
// Try to omit paths can might refer to a build format, .e.g `dist/cjs/foo.cjs` -> `src/foo.ts`
|
||||
segment = ["cjs", "mjs", "js"]
|
||||
}
|
||||
|
||||
bindingset[segment]
|
||||
string rewritePathSegment(string segment) {
|
||||
// Try removing anything after the first dot, such as foo.min.js -> foo (the extension is then filled in by getAdditionalChild)
|
||||
result = segment.regexpReplaceAll("\\..*", "")
|
||||
}
|
||||
}
|
||||
|
||||
private module Resolver = Folder::Resolve<ResolverConfig>;
|
||||
|
||||
/**
|
||||
* Removes the scope from a package name, e.g. `@foo/bar` -> `bar`.
|
||||
*/
|
||||
bindingset[name]
|
||||
private string stripPackageScope(string name) { result = name.regexpReplaceAll("^@[^/]+/", "") }
|
||||
|
||||
private predicate isImplementationFile(File f) { not f.getBaseName().matches("%.d.ts") }
|
||||
|
||||
File guessPackageJsonMain1(PackageJsonEx pkg) {
|
||||
not isImplementationFile(pkg.getMainFile()) and
|
||||
exists(Folder folder, Folder subfolder |
|
||||
folder = pkg.getFolder() and
|
||||
(
|
||||
subfolder = folder or
|
||||
subfolder = folder.getChildContainer(getASrcFolderName()) or
|
||||
subfolder =
|
||||
folder
|
||||
.getChildContainer(getASrcFolderName())
|
||||
.(Folder)
|
||||
.getChildContainer(getASrcFolderName())
|
||||
)
|
||||
|
|
||||
result = subfolder.getJavaScriptFileOrTypings("index")
|
||||
or
|
||||
result = subfolder.getJavaScriptFileOrTypings(stripPackageScope(pkg.getDeclaredPackageName()))
|
||||
)
|
||||
}
|
||||
|
||||
File guessPackageJsonMain2(PackageJsonEx pkg) {
|
||||
not isImplementationFile(pkg.getMainFile()) and
|
||||
not isImplementationFile(guessPackageJsonMain1(pkg)) and
|
||||
result = pkg.getAFileInFilesArray()
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
private import javascript
|
||||
|
||||
/**
|
||||
* A path expression that can be constant-folded by concatenating subexpressions.
|
||||
*/
|
||||
abstract class PathConcatenation extends Expr {
|
||||
/** Gets the separator to insert between paths */
|
||||
string getSeparator() { result = "" }
|
||||
|
||||
/** Gets the `n`th operand to concatenate. */
|
||||
abstract Expr getOperand(int n);
|
||||
}
|
||||
|
||||
private class AddExprConcatenation extends PathConcatenation, AddExpr {
|
||||
override Expr getOperand(int n) {
|
||||
n = 0 and result = this.getLeftOperand()
|
||||
or
|
||||
n = 1 and result = this.getRightOperand()
|
||||
}
|
||||
}
|
||||
|
||||
private class TemplateConcatenation extends PathConcatenation, TemplateLiteral {
|
||||
override Expr getOperand(int n) { result = this.getElement(n) }
|
||||
}
|
||||
|
||||
private class JoinCallConcatenation extends PathConcatenation, CallExpr {
|
||||
JoinCallConcatenation() {
|
||||
// Heuristic recognition of path.join and path.resolve since we can't rely on SourceNode at this stage.
|
||||
this.getReceiver().(VarAccess).getName() = "path" and
|
||||
this.getCalleeName() = ["join", "resolve"]
|
||||
}
|
||||
|
||||
override Expr getOperand(int n) { result = this.getArgument(n) }
|
||||
|
||||
override string getSeparator() { result = "/" }
|
||||
}
|
||||
@@ -0,0 +1,250 @@
|
||||
private import javascript
|
||||
private import semmle.javascript.internal.paths.PackageJsonEx
|
||||
private import semmle.javascript.internal.paths.JSPaths
|
||||
private import semmle.javascript.internal.paths.PathMapping
|
||||
private import semmle.javascript.internal.paths.PathConcatenation
|
||||
private import semmle.javascript.dataflow.internal.DataFlowNode
|
||||
|
||||
/**
|
||||
* Gets the file to import when an imported path resolves to the given `folder`.
|
||||
*/
|
||||
File getFileFromFolderImport(Folder folder) {
|
||||
result = folder.getJavaScriptFileOrTypings("index")
|
||||
or
|
||||
// Note that unlike "exports" paths, "main" and "module" also take effect when the package
|
||||
// is imported via a relative path, e.g. `require("..")` targeting a folder with a package.json file.
|
||||
exists(PackageJsonEx pkg |
|
||||
pkg.getFolder() = folder and
|
||||
result = pkg.getMainFileOrBestGuess()
|
||||
)
|
||||
}
|
||||
|
||||
private Variable dirnameVar() { result.getName() = "__dirname" }
|
||||
|
||||
private Variable filenameVar() { result.getName() = "__filename" }
|
||||
|
||||
private signature predicate exprSig(Expr e);
|
||||
|
||||
module ResolveExpr<exprSig/1 shouldResolveExpr> {
|
||||
/** Holds if we need the constant string-value of `node`. */
|
||||
private predicate needsConstantFolding(EarlyStageNode node) {
|
||||
exists(Expr e |
|
||||
shouldResolveExpr(e) and
|
||||
node = TValueNode(e)
|
||||
)
|
||||
or
|
||||
exists(EarlyStageNode needsFolding | needsConstantFolding(needsFolding) |
|
||||
DataFlow::Impl::earlyStageImmediateFlowStep(node, needsFolding)
|
||||
or
|
||||
exists(PathConcatenation joiner |
|
||||
needsFolding = TValueNode(joiner) and
|
||||
node = TValueNode(joiner.getOperand(_))
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/** Gets the constant-value of `node` */
|
||||
language[monotonicAggregates]
|
||||
private string getValue(EarlyStageNode node) {
|
||||
needsConstantFolding(node) and
|
||||
(
|
||||
exists(Expr e | node = TValueNode(e) |
|
||||
result = e.getStringValue()
|
||||
or
|
||||
e = dirnameVar().getAnAccess() and
|
||||
result = "./" // Ensure the path gets interpreted relative to the current directory
|
||||
or
|
||||
e = filenameVar().getAnAccess() and
|
||||
result = "./" + e.getFile().getBaseName()
|
||||
)
|
||||
or
|
||||
exists(EarlyStageNode pred |
|
||||
DataFlow::Impl::earlyStageImmediateFlowStep(pred, node) and
|
||||
result = getValue(pred)
|
||||
)
|
||||
or
|
||||
exists(PathConcatenation join |
|
||||
node = TValueNode(join) and
|
||||
// Note: 'monotonicAggregates' above lets us use recursion in the last clause of this aggregate
|
||||
result =
|
||||
strictconcat(int n, EarlyStageNode child, string sep |
|
||||
child = TValueNode(join.getOperand(n)) and sep = join.getSeparator()
|
||||
|
|
||||
getValue(child), sep order by n
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
final private class FinalExpr = Expr;
|
||||
|
||||
private class RelevantExpr extends FinalExpr {
|
||||
RelevantExpr() { shouldResolveExpr(this) }
|
||||
|
||||
/** Gets the string-value of this path. */
|
||||
string getValue() { result = getValue(TValueNode(this)) }
|
||||
|
||||
/** Gets a path mapping affecting this path. */
|
||||
pragma[nomagic]
|
||||
PathMapping getAPathMapping() { result.getAnAffectedFile() = this.getFile() }
|
||||
|
||||
/** Gets the NPM package name from the beginning of this path. */
|
||||
pragma[nomagic]
|
||||
string getPackagePrefix() { result = this.getValue().(FilePath).getPackagePrefix() }
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `expr` matches a path mapping, and should thus be resolved as `newPath` relative to `base`.
|
||||
*/
|
||||
pragma[nomagic]
|
||||
private predicate resolveViaPathMapping(RelevantExpr expr, Container base, string newPath) {
|
||||
// Handle path mappings such as `{ "paths": { "@/*": "./src/*" }}` in a tsconfig.json file
|
||||
exists(PathMapping mapping, string value |
|
||||
mapping = expr.getAPathMapping() and
|
||||
value = expr.getValue()
|
||||
|
|
||||
mapping.hasExactPathMapping(value, base, newPath)
|
||||
or
|
||||
exists(string pattern, string suffix, string mappedPath |
|
||||
mapping.hasPrefixPathMapping(pattern, base, mappedPath) and
|
||||
value = pattern + suffix and
|
||||
newPath = mappedPath + suffix
|
||||
)
|
||||
)
|
||||
or
|
||||
// Handle imports referring to a package by name, where we have a package.json
|
||||
// file for that package in the codebase. This is treated separately from PathMapping for performance
|
||||
// reasons, as there can be a large number of packages which affect all files in the project.
|
||||
//
|
||||
// This part only handles the "exports" property of package.json. "main" and "modules" are
|
||||
// handled further down because their semantics are easier to handle there.
|
||||
exists(PackageJsonEx pkg, string packageName, string remainder |
|
||||
packageName = expr.getPackagePrefix() and
|
||||
pkg.getDeclaredPackageName() = packageName and
|
||||
remainder = expr.getValue().suffix(packageName.length()).regexpReplaceAll("^[/\\\\]", "")
|
||||
|
|
||||
// "exports": { ".": "./foo.js" }
|
||||
// "exports": { "./foo.js": "./foo/impl.js" }
|
||||
pkg.hasExactPathMappingTo(remainder, base) and
|
||||
newPath = ""
|
||||
or
|
||||
// "exports": { "./*": "./foo/*" }
|
||||
exists(string prefix |
|
||||
pkg.hasPrefixPathMappingTo(prefix, base) and
|
||||
remainder = prefix + newPath
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
pragma[noopt]
|
||||
private predicate relativePathExpr(RelevantExpr expr, Container base, FilePath path) {
|
||||
expr instanceof RelevantExpr and
|
||||
path = expr.getValue() and
|
||||
path.isDotRelativePath() and
|
||||
exists(File file |
|
||||
file = expr.getFile() and
|
||||
base = file.getParentContainer()
|
||||
)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private Container getJSDocProvidedModule(string moduleName) {
|
||||
exists(JSDocTag tag |
|
||||
tag.getTitle() = "providesModule" and
|
||||
tag.getDescription().trim() = moduleName and
|
||||
tag.getFile() = result
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `expr` should be resolved as `path` relative to `base`.
|
||||
*/
|
||||
pragma[nomagic]
|
||||
private predicate shouldResolve(RelevantExpr expr, Container base, FilePath path) {
|
||||
// Relative paths are resolved from their enclosing folder
|
||||
relativePathExpr(expr, base, path)
|
||||
or
|
||||
resolveViaPathMapping(expr, base, path)
|
||||
or
|
||||
// Resolve from baseUrl of relevant tsconfig.json file
|
||||
path = expr.getValue() and
|
||||
not path.isDotRelativePath() and
|
||||
expr.getAPathMapping().hasBaseUrl(base)
|
||||
or
|
||||
// If the path starts with the name of a package, resolve relative to the directory of that package.
|
||||
// Note that `getFileFromFolderImport` may subsequently redirect this to the package's "main",
|
||||
// so we don't have to deal with that here.
|
||||
exists(PackageJson pkg, string packageName |
|
||||
packageName = expr.getPackagePrefix() and
|
||||
pkg.getDeclaredPackageName() = packageName and
|
||||
path = expr.getValue().suffix(packageName.length()) and
|
||||
base = pkg.getFolder()
|
||||
)
|
||||
or
|
||||
base = getJSDocProvidedModule(expr.getValue()) and
|
||||
path = ""
|
||||
}
|
||||
|
||||
private module ResolverConfig implements Folder::ResolveSig {
|
||||
predicate shouldResolve(Container base, string path) { shouldResolve(_, base, path) }
|
||||
|
||||
predicate getAnAdditionalChild = JSPaths::getAnAdditionalChild/2;
|
||||
}
|
||||
|
||||
private module Resolver = Folder::Resolve<ResolverConfig>;
|
||||
|
||||
private Container resolvePathExpr1(RelevantExpr expr) {
|
||||
exists(Container base, string path |
|
||||
shouldResolve(expr, base, path) and
|
||||
result = Resolver::resolve(base, path)
|
||||
)
|
||||
}
|
||||
|
||||
File resolveExpr(RelevantExpr expr) {
|
||||
result = resolvePathExpr1(expr)
|
||||
or
|
||||
result = getFileFromFolderImport(resolvePathExpr1(expr))
|
||||
}
|
||||
|
||||
module Debug {
|
||||
class PathExprToDebug extends RelevantExpr {
|
||||
PathExprToDebug() { this.getValue() = "vs/nls" }
|
||||
}
|
||||
|
||||
query PathExprToDebug pathExprs() { any() }
|
||||
|
||||
query string getPackagePrefixFromPathExpr_(PathExprToDebug expr) {
|
||||
result = expr.getPackagePrefix()
|
||||
}
|
||||
|
||||
query predicate resolveViaPathMapping_(PathExprToDebug expr, Container base, string newPath) {
|
||||
resolveViaPathMapping(expr, base, newPath)
|
||||
}
|
||||
|
||||
query predicate shouldResolve_(PathExprToDebug expr, Container base, string newPath) {
|
||||
shouldResolve(expr, base, newPath)
|
||||
}
|
||||
|
||||
query Container resolvePathExpr1_(PathExprToDebug expr) { result = resolvePathExpr1(expr) }
|
||||
|
||||
query File resolveExpr_(PathExprToDebug expr) { result = resolveExpr(expr) }
|
||||
|
||||
// Some predicates that are usually small enough that they don't need restriction
|
||||
query File getPackageMainFile(PackageJsonEx pkg) { result = pkg.getMainFile() }
|
||||
|
||||
query predicate guessPackageJsonMain1_ = guessPackageJsonMain1/1;
|
||||
|
||||
query predicate guessPackageJsonMain2_ = guessPackageJsonMain2/1;
|
||||
|
||||
query predicate getFileFromFolderImport_ = getFileFromFolderImport/1;
|
||||
}
|
||||
}
|
||||
|
||||
private predicate isImportPathExpr(Expr e) {
|
||||
e = any(Import imprt).getImportedPathExpr()
|
||||
or
|
||||
e = any(ReExportDeclaration decl).getImportedPath()
|
||||
}
|
||||
|
||||
/** Resolves paths in imports and re-exports. */
|
||||
module ImportPathResolver = ResolveExpr<isImportPathExpr/1>;
|
||||
@@ -0,0 +1,31 @@
|
||||
/**
|
||||
* Provides an extensible mechanism for modeling path mappings.
|
||||
*/
|
||||
|
||||
private import javascript
|
||||
private import semmle.javascript.TSConfig
|
||||
|
||||
/**
|
||||
* A `tsconfig.json`-like configuration object that can affect import resolution via path mappings.
|
||||
*/
|
||||
abstract class PathMapping extends Locatable {
|
||||
/**
|
||||
* Gets a file affected by this path mapping.
|
||||
*/
|
||||
abstract File getAnAffectedFile();
|
||||
|
||||
/**
|
||||
* Holds if imports paths exactly matching `pattern` should be redirected to `newPath`
|
||||
* resolved relative to `newContext`.
|
||||
*/
|
||||
predicate hasExactPathMapping(string pattern, Container newContext, string newPath) { none() }
|
||||
|
||||
/**
|
||||
* Holds if imports paths starting with `pattern` should have the matched prefix replaced by `newPath`
|
||||
* and then resolved relative to `newContext`.
|
||||
*/
|
||||
predicate hasPrefixPathMapping(string pattern, Container newContext, string newPath) { none() }
|
||||
|
||||
/** Holds if non-relative paths in affected files should be resolved relative to `base`. */
|
||||
predicate hasBaseUrl(Container base) { none() }
|
||||
}
|
||||
@@ -88,7 +88,7 @@ module ExternalApiUsedWithUntrustedData {
|
||||
not path instanceof SafeExternalApiPackage and
|
||||
// Exclude paths that can be resolved to a file in the project
|
||||
not exists(Import imprt |
|
||||
imprt.getImportedPath().getValue() = path and exists(imprt.getImportedModule())
|
||||
imprt.getImportedPathString() = path and exists(imprt.getImportedModule())
|
||||
)
|
||||
)
|
||||
or
|
||||
|
||||
@@ -82,6 +82,13 @@ module RequestForgery {
|
||||
pred = url.getArgument(0)
|
||||
)
|
||||
or
|
||||
exists(DataFlow::NewNode url |
|
||||
url = API::moduleImport("url").getMember("URL").getAnInstantiation()
|
||||
|
|
||||
succ = url and
|
||||
pred = url.getArgument(0)
|
||||
)
|
||||
or
|
||||
exists(HtmlSanitizerCall call |
|
||||
pred = call.getInput() and
|
||||
succ = call
|
||||
|
||||
@@ -1,3 +1,17 @@
|
||||
## 1.6.0
|
||||
|
||||
### Query Metadata Changes
|
||||
|
||||
* The tag `external/cwe/cwe-79` has been removed from `js/disabling-electron-websecurity` and the tag `external/cwe/cwe-079` has been added.
|
||||
* The tag `external/cwe/cwe-20` has been removed from `js/count-untrusted-data-external-api` and the tag `external/cwe/cwe-020` has been added.
|
||||
* The tag `external/cwe/cwe-20` has been removed from `js/untrusted-data-to-external-api` and the tag `external/cwe/cwe-020` has been added.
|
||||
* The tag `external/cwe/cwe-20` has been removed from `js/untrusted-data-to-external-api-more-sources` and the tag `external/cwe/cwe-020` has been added.
|
||||
|
||||
### Minor Analysis Improvements
|
||||
|
||||
* Type information is now propagated more precisely through `Promise.all()` calls,
|
||||
leading to more resolved calls and more sources and sinks being detected.
|
||||
|
||||
## 1.5.4
|
||||
|
||||
No user-facing changes.
|
||||
|
||||
@@ -152,4 +152,4 @@ where cycleAlert(mod, import_, importedModule, access)
|
||||
select access,
|
||||
access.getName() + " is uninitialized if $@ is loaded first in the cyclic import:" + " " +
|
||||
repr(import_) + " => " + min(pathToModule(importedModule, mod, _)) + " => " + repr(import_) +
|
||||
".", import_.getImportedPath(), importedModule.getName()
|
||||
".", import_.getImportedPathExpr(), importedModule.getName()
|
||||
|
||||
@@ -24,7 +24,7 @@ PackageJson getClosestPackageJson(Folder f) {
|
||||
|
||||
from Require r, string path, string mod
|
||||
where
|
||||
path = r.getImportedPath().getValue() and
|
||||
path = r.getImportedPathString() and
|
||||
// the imported module is the initial segment of the path, up to
|
||||
// `/` or the end of the string, whichever comes first; we exclude
|
||||
// local paths starting with `.` or `/`, since they might refer to files
|
||||
@@ -36,7 +36,7 @@ where
|
||||
not exists(r.getImportedModule()) and
|
||||
// no enclosing NPM package declares a dependency on `mod`
|
||||
forex(NpmPackage pkg, PackageJson pkgJson |
|
||||
pkg.getAModule() = r.getTopLevel() and pkgJson = pkg.getPackageJson()
|
||||
pkg.getAModule() = r.getTopLevel() and pkgJson = pkg.getPackageJson().getEnclosingPackage*()
|
||||
|
|
||||
not pkgJson.declaresDependency(mod, _) and
|
||||
not pkgJson.getPeerDependencies().getADependency(mod, _) and
|
||||
|
||||
@@ -23,7 +23,17 @@ predicate declaresDependency(NpmPackage pkg, string name, JsonValue dep) {
|
||||
/**
|
||||
* Gets a path expression in a module belonging to `pkg`.
|
||||
*/
|
||||
PathExpr getAPathExpr(NpmPackage pkg) { result.getEnclosingModule() = pkg.getAModule() }
|
||||
Expr getAPathExpr(NpmPackage pkg) {
|
||||
exists(Import imprt |
|
||||
result = imprt.getImportedPathExpr() and
|
||||
pkg.getAModule() = imprt.getEnclosingModule()
|
||||
)
|
||||
or
|
||||
exists(ReExportDeclaration decl |
|
||||
result = decl.getImportedPath() and
|
||||
pkg.getAModule() = decl.getEnclosingModule()
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a URL-valued attribute in a module or HTML file belonging to `pkg`.
|
||||
@@ -56,9 +66,8 @@ predicate usesDependency(NpmPackage pkg, string name) {
|
||||
(
|
||||
// there is a path expression (e.g., in a `require` or `import`) that
|
||||
// references `pkg`
|
||||
exists(PathExpr path | path = getAPathExpr(pkg) |
|
||||
// check whether the path is `name` or starts with `name/`, ignoring a prefix that ends with '!' (example: "scriptloader!moment")
|
||||
path.getValue().regexpMatch("(.*!)?\\Q" + name + "\\E(/.*)?")
|
||||
exists(Expr path | path = getAPathExpr(pkg) |
|
||||
path.getStringValue().(FilePath).getPackagePrefix() = name
|
||||
)
|
||||
or
|
||||
// there is an HTML URL attribute that may reference `pkg`
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
* @kind problem
|
||||
* @problem.severity warning
|
||||
* @security-severity 7.5
|
||||
* @precision medium
|
||||
* @precision low
|
||||
* @id js/password-in-configuration-file
|
||||
* @tags security
|
||||
* external/cwe/cwe-256
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
* @kind path-problem
|
||||
* @problem.severity warning
|
||||
* @security-severity 9.8
|
||||
* @precision high
|
||||
* @precision low
|
||||
* @id js/hardcoded-credentials
|
||||
* @tags security
|
||||
* external/cwe/cwe-259
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
category: minorAnalysis
|
||||
---
|
||||
* Type information is now propagated more precisely through `Promise.all()` calls,
|
||||
leading to more resolved calls and more sources and sinks being detected.
|
||||
@@ -0,0 +1,4 @@
|
||||
---
|
||||
category: minorAnalysis
|
||||
---
|
||||
* The queries `js/hardcoded-credentials` and `js/password-in-configuration-file` have been removed from all query suites.
|
||||
@@ -1,8 +1,13 @@
|
||||
---
|
||||
category: queryMetadata
|
||||
---
|
||||
## 1.6.0
|
||||
|
||||
### Query Metadata Changes
|
||||
|
||||
* The tag `external/cwe/cwe-79` has been removed from `js/disabling-electron-websecurity` and the tag `external/cwe/cwe-079` has been added.
|
||||
* The tag `external/cwe/cwe-20` has been removed from `js/count-untrusted-data-external-api` and the tag `external/cwe/cwe-020` has been added.
|
||||
* The tag `external/cwe/cwe-20` has been removed from `js/untrusted-data-to-external-api` and the tag `external/cwe/cwe-020` has been added.
|
||||
* The tag `external/cwe/cwe-20` has been removed from `js/untrusted-data-to-external-api-more-sources` and the tag `external/cwe/cwe-020` has been added.
|
||||
|
||||
### Minor Analysis Improvements
|
||||
|
||||
* Type information is now propagated more precisely through `Promise.all()` calls,
|
||||
leading to more resolved calls and more sources and sinks being detected.
|
||||
@@ -1,2 +1,2 @@
|
||||
---
|
||||
lastReleaseVersion: 1.5.4
|
||||
lastReleaseVersion: 1.6.0
|
||||
|
||||
@@ -1,4 +1,138 @@
|
||||
- description: Security-and-quality queries for JavaScript
|
||||
- queries: .
|
||||
- apply: security-and-quality-selectors.yml
|
||||
from: codeql/suite-helpers
|
||||
- include:
|
||||
kind:
|
||||
- problem
|
||||
- path-problem
|
||||
precision:
|
||||
- high
|
||||
- very-high
|
||||
tags contain:
|
||||
- security
|
||||
- include:
|
||||
kind:
|
||||
- problem
|
||||
- path-problem
|
||||
precision: medium
|
||||
problem.severity:
|
||||
- error
|
||||
- warning
|
||||
tags contain:
|
||||
- security
|
||||
- include:
|
||||
id:
|
||||
- js/node/assignment-to-exports-variable
|
||||
- js/node/missing-exports-qualifier
|
||||
- js/angular/duplicate-dependency
|
||||
- js/angular/missing-explicit-injection
|
||||
- js/angular/dependency-injection-mismatch
|
||||
- js/angular/incompatible-service
|
||||
- js/angular/expression-in-url-attribute
|
||||
- js/angular/repeated-dependency-injection
|
||||
- js/regex/back-reference-to-negative-lookahead
|
||||
- js/regex/unmatchable-dollar
|
||||
- js/regex/empty-character-class
|
||||
- js/regex/back-reference-before-group
|
||||
- js/regex/unbound-back-reference
|
||||
- js/regex/always-matches
|
||||
- js/regex/unmatchable-caret
|
||||
- js/regex/duplicate-in-character-class
|
||||
- js/vue/arrow-method-on-vue-instance
|
||||
- js/conditional-comment
|
||||
- js/superfluous-trailing-arguments
|
||||
- js/illegal-invocation
|
||||
- js/invalid-prototype-value
|
||||
- js/incomplete-object-initialization
|
||||
- js/useless-type-test
|
||||
- js/template-syntax-in-string-literal
|
||||
- js/with-statement
|
||||
- js/property-assignment-on-primitive
|
||||
- js/deletion-of-non-property
|
||||
- js/setter-return
|
||||
- js/index-out-of-bounds
|
||||
- js/unused-index-variable
|
||||
- js/non-standard-language-feature
|
||||
- js/syntax-error
|
||||
- js/for-in-comprehension
|
||||
- js/strict-mode-call-stack-introspection
|
||||
- js/automatic-semicolon-insertion
|
||||
- js/inconsistent-use-of-new
|
||||
- js/non-linear-pattern
|
||||
- js/yield-outside-generator
|
||||
- js/mixed-static-instance-this-access
|
||||
- js/arguments-redefinition
|
||||
- js/nested-function-reference-in-default-parameter
|
||||
- js/duplicate-parameter-name
|
||||
- js/unreachable-method-overloads
|
||||
- js/duplicate-variable-declaration
|
||||
- js/function-declaration-conflict
|
||||
- js/ineffective-parameter-type
|
||||
- js/assignment-to-constant
|
||||
- js/use-before-declaration
|
||||
- js/suspicious-method-name-declaration
|
||||
- js/overwritten-property
|
||||
- js/useless-assignment-to-local
|
||||
- js/useless-assignment-to-property
|
||||
- js/variable-initialization-conflict
|
||||
- js/variable-use-in-temporal-dead-zone
|
||||
- js/missing-variable-declaration
|
||||
- js/missing-this-qualifier
|
||||
- js/unused-local-variable
|
||||
- js/label-in-switch
|
||||
- js/ignore-array-result
|
||||
- js/inconsistent-loop-direction
|
||||
- js/unreachable-statement
|
||||
- js/trivial-conditional
|
||||
- js/useless-comparison-test
|
||||
- js/misleading-indentation-of-dangling-else
|
||||
- js/use-of-returnless-function
|
||||
- js/useless-assignment-in-return
|
||||
- js/loop-iteration-skipped-due-to-shifting
|
||||
- js/misleading-indentation-after-control-statement
|
||||
- js/unused-loop-variable
|
||||
- js/implicit-operand-conversion
|
||||
- js/whitespace-contradicts-precedence
|
||||
- js/missing-space-in-concatenation
|
||||
- js/unbound-event-handler-receiver
|
||||
- js/shift-out-of-range
|
||||
- js/missing-dot-length-in-comparison
|
||||
- js/redundant-operation
|
||||
- js/comparison-with-nan
|
||||
- js/duplicate-property
|
||||
- js/unclear-operator-precedence
|
||||
- js/unknown-directive
|
||||
- js/string-instead-of-regex
|
||||
- js/unneeded-defensive-code
|
||||
- js/duplicate-switch-case
|
||||
- js/duplicate-condition
|
||||
- js/useless-expression
|
||||
- js/redundant-assignment
|
||||
- js/misspelled-variable-name
|
||||
- js/call-to-non-callable
|
||||
- js/missing-await
|
||||
- js/comparison-between-incompatible-types
|
||||
- js/property-access-on-non-object
|
||||
- js/malformed-html-id
|
||||
- js/eval-like-call
|
||||
- js/duplicate-html-attribute
|
||||
- js/react/unsupported-state-update-in-lifecycle-method
|
||||
- js/react/unused-or-undefined-state-property
|
||||
- js/react/direct-state-mutation
|
||||
- js/react/inconsistent-state-update
|
||||
- js/diagnostics/extraction-errors
|
||||
- js/diagnostics/successfully-extracted-files
|
||||
- js/summary/lines-of-code
|
||||
- js/summary/lines-of-user-code
|
||||
- include:
|
||||
kind:
|
||||
- diagnostic
|
||||
- include:
|
||||
kind:
|
||||
- metric
|
||||
tags contain:
|
||||
- summary
|
||||
- exclude:
|
||||
deprecated: //
|
||||
- exclude:
|
||||
query path:
|
||||
- /^experimental\/.*/
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
name: codeql/javascript-queries
|
||||
version: 1.5.5-dev
|
||||
version: 1.6.1-dev
|
||||
groups:
|
||||
- javascript
|
||||
- queries
|
||||
|
||||
@@ -18,8 +18,8 @@ query predicate amdModuleDefinition(AmdModuleDefinition mod, DataFlow::SourceNod
|
||||
mod.getFactoryNode() = factory
|
||||
}
|
||||
|
||||
query predicate amdModuleDependencies(AmdModuleDefinition mod, PathExpr dependency) {
|
||||
dependency = mod.getADependency()
|
||||
query predicate amdModuleDependencies(AmdModuleDefinition mod, Expr dependency) {
|
||||
dependency = mod.getADependencyExpr()
|
||||
}
|
||||
|
||||
query predicate amdModuleExportedSymbol(AmdModule m, string sym) { sym = m.getAnExportedSymbol() }
|
||||
|
||||
@@ -1,8 +1,17 @@
|
||||
nodes
|
||||
| file://:0:0:0:0 | (Arguments) | semmle.label | (Arguments) |
|
||||
| file://:0:0:0:0 | (Parameters) | semmle.label | (Parameters) |
|
||||
| tsconfig.json:1:1:7:1 | [JsonObject] {compilerOptions: ...} | semmle.label | [JsonObject] {compilerOptions: ...} |
|
||||
| tsconfig.json:1:1:7:1 | [JsonObject] {compilerOptions: ...} | semmle.order | 1 |
|
||||
| tsconfig.json:2:24:4:5 | [JsonObject] {experimentalDecorators: ...} | semmle.label | [JsonObject] {experimentalDecorators: ...} |
|
||||
| tsconfig.json:3:35:3:38 | [JsonBoolean] true | semmle.label | [JsonBoolean] true |
|
||||
| tsconfig.json:5:16:5:26 | [JsonArray] ["**/*.ts"] | semmle.label | [JsonArray] ["**/*.ts"] |
|
||||
| tsconfig.json:5:17:5:25 | [JsonString] "**/*.ts" | semmle.label | [JsonString] "**/*.ts" |
|
||||
| tsconfig.json:6:12:6:28 | [JsonArray] ["es2015", ...] | semmle.label | [JsonArray] ["es2015", ...] |
|
||||
| tsconfig.json:6:13:6:20 | [JsonString] "es2015" | semmle.label | [JsonString] "es2015" |
|
||||
| tsconfig.json:6:23:6:27 | [JsonString] "dom" | semmle.label | [JsonString] "dom" |
|
||||
| tst.ts:1:1:1:22 | [DeclStmt] const Dec = ... | semmle.label | [DeclStmt] const Dec = ... |
|
||||
| tst.ts:1:1:1:22 | [DeclStmt] const Dec = ... | semmle.order | 1 |
|
||||
| tst.ts:1:1:1:22 | [DeclStmt] const Dec = ... | semmle.order | 2 |
|
||||
| tst.ts:1:7:1:9 | [VarDecl] Dec | semmle.label | [VarDecl] Dec |
|
||||
| tst.ts:1:7:1:21 | [VariableDeclarator] Dec: any = null | semmle.label | [VariableDeclarator] Dec: any = null |
|
||||
| tst.ts:1:12:1:14 | [KeywordTypeExpr] any | semmle.label | [KeywordTypeExpr] any |
|
||||
@@ -11,7 +20,7 @@ nodes
|
||||
| tst.ts:3:2:3:4 | [VarRef] Dec | semmle.label | [VarRef] Dec |
|
||||
| tst.ts:3:2:3:6 | [CallExpr] Dec() | semmle.label | [CallExpr] Dec() |
|
||||
| tst.ts:4:1:8:1 | [ExportDeclaration] export ... id {} } | semmle.label | [ExportDeclaration] export ... id {} } |
|
||||
| tst.ts:4:1:8:1 | [ExportDeclaration] export ... id {} } | semmle.order | 2 |
|
||||
| tst.ts:4:1:8:1 | [ExportDeclaration] export ... id {} } | semmle.order | 3 |
|
||||
| tst.ts:4:8:8:1 | [ClassDefinition,TypeDefinition] class O ... id {} } | semmle.label | [ClassDefinition,TypeDefinition] class O ... id {} } |
|
||||
| tst.ts:4:14:4:30 | [VarDecl] OperatorResolvers | semmle.label | [VarDecl] OperatorResolvers |
|
||||
| tst.ts:4:32:4:31 | [BlockStmt] {} | semmle.label | [BlockStmt] {} |
|
||||
@@ -30,7 +39,7 @@ nodes
|
||||
| tst.ts:7:16:7:19 | [KeywordTypeExpr] void | semmle.label | [KeywordTypeExpr] void |
|
||||
| tst.ts:7:21:7:22 | [BlockStmt] {} | semmle.label | [BlockStmt] {} |
|
||||
| tst.ts:10:1:10:41 | [DeclStmt] const createMethodDecorator = ... | semmle.label | [DeclStmt] const createMethodDecorator = ... |
|
||||
| tst.ts:10:1:10:41 | [DeclStmt] const createMethodDecorator = ... | semmle.order | 3 |
|
||||
| tst.ts:10:1:10:41 | [DeclStmt] const createMethodDecorator = ... | semmle.order | 4 |
|
||||
| tst.ts:10:7:10:27 | [VarDecl] createMethodDecorator | semmle.label | [VarDecl] createMethodDecorator |
|
||||
| tst.ts:10:7:10:40 | [VariableDeclarator] createM ... = null | semmle.label | [VariableDeclarator] createM ... = null |
|
||||
| tst.ts:10:31:10:33 | [KeywordTypeExpr] any | semmle.label | [KeywordTypeExpr] any |
|
||||
@@ -38,7 +47,7 @@ nodes
|
||||
| tst.ts:12:1:12:21 | [VarRef] createMethodDecorator | semmle.label | [VarRef] createMethodDecorator |
|
||||
| tst.ts:12:1:14:2 | [CallExpr] createM ... { }) | semmle.label | [CallExpr] createM ... { }) |
|
||||
| tst.ts:12:1:14:3 | [ExprStmt] createM ... }); | semmle.label | [ExprStmt] createM ... }); |
|
||||
| tst.ts:12:1:14:3 | [ExprStmt] createM ... }); | semmle.order | 4 |
|
||||
| tst.ts:12:1:14:3 | [ExprStmt] createM ... }); | semmle.order | 5 |
|
||||
| tst.ts:12:23:14:1 | [ArrowFunctionExpr] ({ args ... { } | semmle.label | [ArrowFunctionExpr] ({ args ... { } |
|
||||
| tst.ts:12:24:12:40 | [ObjectPattern,Parameter] { args, context } | semmle.label | [ObjectPattern,Parameter] { args, context } |
|
||||
| tst.ts:12:26:12:29 | [Label] args | semmle.label | [Label] args |
|
||||
@@ -56,6 +65,20 @@ edges
|
||||
| file://:0:0:0:0 | (Parameters) | tst.ts:12:24:12:40 | [ObjectPattern,Parameter] { args, context } | semmle.order | 0 |
|
||||
| file://:0:0:0:0 | (Parameters) | tst.ts:12:43:12:46 | [SimpleParameter] next | semmle.label | 1 |
|
||||
| file://:0:0:0:0 | (Parameters) | tst.ts:12:43:12:46 | [SimpleParameter] next | semmle.order | 1 |
|
||||
| tsconfig.json:1:1:7:1 | [JsonObject] {compilerOptions: ...} | tsconfig.json:2:24:4:5 | [JsonObject] {experimentalDecorators: ...} | semmle.label | 0 |
|
||||
| tsconfig.json:1:1:7:1 | [JsonObject] {compilerOptions: ...} | tsconfig.json:2:24:4:5 | [JsonObject] {experimentalDecorators: ...} | semmle.order | 0 |
|
||||
| tsconfig.json:1:1:7:1 | [JsonObject] {compilerOptions: ...} | tsconfig.json:5:16:5:26 | [JsonArray] ["**/*.ts"] | semmle.label | 1 |
|
||||
| tsconfig.json:1:1:7:1 | [JsonObject] {compilerOptions: ...} | tsconfig.json:5:16:5:26 | [JsonArray] ["**/*.ts"] | semmle.order | 1 |
|
||||
| tsconfig.json:1:1:7:1 | [JsonObject] {compilerOptions: ...} | tsconfig.json:6:12:6:28 | [JsonArray] ["es2015", ...] | semmle.label | 2 |
|
||||
| tsconfig.json:1:1:7:1 | [JsonObject] {compilerOptions: ...} | tsconfig.json:6:12:6:28 | [JsonArray] ["es2015", ...] | semmle.order | 2 |
|
||||
| tsconfig.json:2:24:4:5 | [JsonObject] {experimentalDecorators: ...} | tsconfig.json:3:35:3:38 | [JsonBoolean] true | semmle.label | 0 |
|
||||
| tsconfig.json:2:24:4:5 | [JsonObject] {experimentalDecorators: ...} | tsconfig.json:3:35:3:38 | [JsonBoolean] true | semmle.order | 0 |
|
||||
| tsconfig.json:5:16:5:26 | [JsonArray] ["**/*.ts"] | tsconfig.json:5:17:5:25 | [JsonString] "**/*.ts" | semmle.label | 0 |
|
||||
| tsconfig.json:5:16:5:26 | [JsonArray] ["**/*.ts"] | tsconfig.json:5:17:5:25 | [JsonString] "**/*.ts" | semmle.order | 0 |
|
||||
| tsconfig.json:6:12:6:28 | [JsonArray] ["es2015", ...] | tsconfig.json:6:13:6:20 | [JsonString] "es2015" | semmle.label | 0 |
|
||||
| tsconfig.json:6:12:6:28 | [JsonArray] ["es2015", ...] | tsconfig.json:6:13:6:20 | [JsonString] "es2015" | semmle.order | 0 |
|
||||
| tsconfig.json:6:12:6:28 | [JsonArray] ["es2015", ...] | tsconfig.json:6:23:6:27 | [JsonString] "dom" | semmle.label | 1 |
|
||||
| tsconfig.json:6:12:6:28 | [JsonArray] ["es2015", ...] | tsconfig.json:6:23:6:27 | [JsonString] "dom" | semmle.order | 1 |
|
||||
| tst.ts:1:1:1:22 | [DeclStmt] const Dec = ... | tst.ts:1:7:1:21 | [VariableDeclarator] Dec: any = null | semmle.label | 1 |
|
||||
| tst.ts:1:1:1:22 | [DeclStmt] const Dec = ... | tst.ts:1:7:1:21 | [VariableDeclarator] Dec: any = null | semmle.order | 1 |
|
||||
| tst.ts:1:7:1:21 | [VariableDeclarator] Dec: any = null | tst.ts:1:7:1:9 | [VarDecl] Dec | semmle.label | 1 |
|
||||
|
||||
@@ -31,7 +31,7 @@ class AnnotatedCall extends DataFlow::Node {
|
||||
|
||||
AnnotatedCall() {
|
||||
this instanceof DataFlow::InvokeNode and
|
||||
calls = getAnnotation(this.asExpr(), kind) and
|
||||
calls = getAnnotation(this.getEnclosingExpr(), kind) and
|
||||
kind = "calls"
|
||||
or
|
||||
this instanceof DataFlow::PropRef and
|
||||
@@ -79,12 +79,14 @@ query predicate spuriousCallee(AnnotatedCall call, Function target, int boundArg
|
||||
}
|
||||
|
||||
query predicate missingCallee(
|
||||
AnnotatedCall call, AnnotatedFunction target, int boundArgs, string kind
|
||||
InvokeExpr invoke, AnnotatedFunction target, int boundArgs, string kind
|
||||
) {
|
||||
not callEdge(call, target, boundArgs) and
|
||||
kind = call.getKind() and
|
||||
target = call.getAnExpectedCallee(kind) and
|
||||
boundArgs = call.getBoundArgsOrMinusOne()
|
||||
forex(AnnotatedCall call | call.getEnclosingExpr() = invoke |
|
||||
not callEdge(call, target, boundArgs) and
|
||||
kind = call.getKind() and
|
||||
target = call.getAnExpectedCallee(kind) and
|
||||
boundArgs = call.getBoundArgsOrMinusOne()
|
||||
)
|
||||
}
|
||||
|
||||
query predicate badAnnotation(string name) {
|
||||
|
||||
142
javascript/ql/test/library-tests/CallGraphs/AnnotatedTest/prototypes.js
vendored
Normal file
142
javascript/ql/test/library-tests/CallGraphs/AnnotatedTest/prototypes.js
vendored
Normal file
@@ -0,0 +1,142 @@
|
||||
import 'dummy'
|
||||
|
||||
class Baz {
|
||||
baz() {
|
||||
console.log("Baz baz");
|
||||
/** calls:Baz.greet calls:Derived.greet1 calls:BazExtented.greet2 */
|
||||
this.greet();
|
||||
}
|
||||
/** name:Baz.greet */
|
||||
greet() { console.log("Baz greet"); }
|
||||
}
|
||||
|
||||
/** name:Baz.shout */
|
||||
Baz.prototype.shout = function() { console.log("Baz shout"); };
|
||||
/** name:Baz.staticShout */
|
||||
Baz.staticShout = function() { console.log("Baz staticShout"); };
|
||||
|
||||
function foo(baz){
|
||||
/** calls:Baz.greet */
|
||||
baz.greet();
|
||||
/** calls:Baz.shout */
|
||||
baz.shout();
|
||||
/** calls:Baz.staticShout */
|
||||
Baz.staticShout();
|
||||
}
|
||||
|
||||
const baz = new Baz();
|
||||
foo(baz);
|
||||
|
||||
class Derived extends Baz {
|
||||
/** name:Derived.greet1 */
|
||||
greet() {
|
||||
console.log("Derived greet");
|
||||
super.greet();
|
||||
}
|
||||
|
||||
/** name:Derived.shout1 */
|
||||
shout() {
|
||||
console.log("Derived shout");
|
||||
super.shout();
|
||||
}
|
||||
}
|
||||
|
||||
function bar(derived){
|
||||
/** calls:Derived.greet1 */
|
||||
derived.greet();
|
||||
/** calls:Derived.shout1 */
|
||||
derived.shout();
|
||||
}
|
||||
|
||||
bar(new Derived());
|
||||
|
||||
class BazExtented {
|
||||
constructor() {
|
||||
console.log("BazExtented construct");
|
||||
}
|
||||
|
||||
/** name:BazExtented.greet2 */
|
||||
greet() {
|
||||
console.log("BazExtented greet");
|
||||
/** calls:Baz.greet */
|
||||
Baz.prototype.greet.call(this);
|
||||
};
|
||||
}
|
||||
|
||||
BazExtented.prototype = Object.create(Baz.prototype);
|
||||
BazExtented.prototype.constructor = BazExtented;
|
||||
BazExtented.staticShout = Baz.staticShout;
|
||||
|
||||
/** name:BazExtented.talk */
|
||||
BazExtented.prototype.talk = function() { console.log("BazExtented talk"); };
|
||||
|
||||
/** name:BazExtented.shout2 */
|
||||
BazExtented.prototype.shout = function() {
|
||||
console.log("BazExtented shout");
|
||||
/** calls:Baz.shout */
|
||||
Baz.prototype.shout.call(this);
|
||||
};
|
||||
|
||||
function barbar(bazExtented){
|
||||
/** calls:BazExtented.talk */
|
||||
bazExtented.talk();
|
||||
/** calls:BazExtented.shout2 */
|
||||
bazExtented.shout();
|
||||
/** calls:BazExtented.greet2 */
|
||||
bazExtented.greet();
|
||||
/** calls:Baz.staticShout */
|
||||
BazExtented.staticShout();
|
||||
}
|
||||
|
||||
barbar(new BazExtented());
|
||||
|
||||
class Base {
|
||||
constructor() {
|
||||
/** calls:Base.read calls:Derived1.read calls:Derived2.read */
|
||||
this.read();
|
||||
}
|
||||
/** name:Base.read */
|
||||
read() { }
|
||||
}
|
||||
|
||||
class Derived1 extends Base {}
|
||||
/** name:Derived1.read */
|
||||
Derived1.prototype.read = function() {};
|
||||
|
||||
class Derived2 {}
|
||||
Derived2.prototype = Object.create(Base.prototype);
|
||||
/** name:Derived2.read */
|
||||
Derived2.prototype.read = function() {};
|
||||
|
||||
|
||||
/** name:BanClass.tmpClass */
|
||||
function tmpClass() {}
|
||||
|
||||
function callerClass() {
|
||||
/** calls:BanClass.tmpClass */
|
||||
this.tmpClass();
|
||||
}
|
||||
class BanClass {
|
||||
constructor() {
|
||||
this.tmpClass = tmpClass;
|
||||
this.callerClass = callerClass;
|
||||
}
|
||||
}
|
||||
|
||||
/** name:BanProtytpe.tmpPrototype */
|
||||
function tmpPrototype() {}
|
||||
|
||||
function callerPrototype() {
|
||||
/** calls:BanProtytpe.tmpPrototype */
|
||||
this.tmpPrototype();
|
||||
}
|
||||
|
||||
function BanProtytpe() {
|
||||
this.tmpPrototype = tmpPrototype;
|
||||
this.callerPrototype = callerPrototype;
|
||||
}
|
||||
|
||||
function banInstantiation(){
|
||||
const instance = new BanProtytpe();
|
||||
instance.callerPrototype();
|
||||
}
|
||||
@@ -22,8 +22,8 @@ query predicate test_ImportNamespaceSpecifier(ImportNamespaceSpecifier ins) { an
|
||||
|
||||
query predicate test_ImportSpecifiers(ImportSpecifier is, VarDecl res) { res = is.getLocal() }
|
||||
|
||||
query predicate test_Imports(ImportDeclaration id, PathExpr res0, int res1) {
|
||||
res0 = id.getImportedPath() and res1 = count(id.getASpecifier())
|
||||
query predicate test_Imports(ImportDeclaration id, Expr res0, int res1) {
|
||||
res0 = id.getImportedPathExpr() and res1 = count(id.getASpecifier())
|
||||
}
|
||||
|
||||
query predicate test_Module_exports(Module m, string name, DataFlow::Node exportValue) {
|
||||
|
||||
4
javascript/ql/test/library-tests/NPM/src/node_modules/a/package.json
generated
vendored
Normal file
4
javascript/ql/test/library-tests/NPM/src/node_modules/a/package.json
generated
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"name": "a",
|
||||
"main": "index.js"
|
||||
}
|
||||
5
javascript/ql/test/library-tests/NPM/src/node_modules/nested/node_modules/a/package.json
generated
vendored
Normal file
5
javascript/ql/test/library-tests/NPM/src/node_modules/nested/node_modules/a/package.json
generated
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"name": "a",
|
||||
"main": "index.js",
|
||||
"description": "Nested version of package A"
|
||||
}
|
||||
3
javascript/ql/test/library-tests/NPM/src/node_modules/nested/package.json
generated
vendored
Normal file
3
javascript/ql/test/library-tests/NPM/src/node_modules/nested/package.json
generated
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"name": "nested"
|
||||
}
|
||||
@@ -6,6 +6,7 @@ dependencies
|
||||
importedFile
|
||||
| src/lib/tst2.js:1:1:1:13 | require("..") | src/index.js:0:0:0:0 | src/index.js |
|
||||
| src/node_modules/nested/tst3.js:1:1:1:29 | require ... odule') | src/node_modules/third-party-module/fancy.js:0:0:0:0 | src/node_modules/third-party-module/fancy.js |
|
||||
| src/node_modules/nested/tst3.js:2:1:2:12 | require('a') | src/node_modules/a/index.js:0:0:0:0 | src/node_modules/a/index.js |
|
||||
| src/node_modules/nested/tst3.js:2:1:2:12 | require('a') | src/node_modules/nested/node_modules/a/index.js:0:0:0:0 | src/node_modules/nested/node_modules/a/index.js |
|
||||
| src/node_modules/tst2.js:1:1:1:38 | require ... cy.js') | src/node_modules/third-party-module/fancy.js:0:0:0:0 | src/node_modules/third-party-module/fancy.js |
|
||||
| src/test-submodule.js:1:1:1:24 | require ... odule") | src/node_modules/parent-module/main.js:0:0:0:0 | src/node_modules/parent-module/main.js |
|
||||
@@ -16,6 +17,7 @@ importedFile
|
||||
importedModule
|
||||
| src/lib/tst2.js:1:1:1:13 | require("..") | src/index.js:1:1:4:0 | <toplevel> |
|
||||
| src/node_modules/nested/tst3.js:1:1:1:29 | require ... odule') | src/node_modules/third-party-module/fancy.js:1:1:4:0 | <toplevel> |
|
||||
| src/node_modules/nested/tst3.js:2:1:2:12 | require('a') | src/node_modules/a/index.js:1:1:1:25 | <toplevel> |
|
||||
| src/node_modules/nested/tst3.js:2:1:2:12 | require('a') | src/node_modules/nested/node_modules/a/index.js:1:1:1:25 | <toplevel> |
|
||||
| src/node_modules/tst2.js:1:1:1:38 | require ... cy.js') | src/node_modules/third-party-module/fancy.js:1:1:4:0 | <toplevel> |
|
||||
| src/test-submodule.js:1:1:1:24 | require ... odule") | src/node_modules/parent-module/main.js:1:1:2:0 | <toplevel> |
|
||||
@@ -29,29 +31,36 @@ modules
|
||||
| src | test-package | src/test-submodule.js:1:1:3:0 | <toplevel> |
|
||||
| src | test-package | src/tst2.js:1:1:1:13 | <toplevel> |
|
||||
| src | test-package | src/tst.js:1:1:2:38 | <toplevel> |
|
||||
| src/node_modules/a | a | src/node_modules/a/index.js:1:1:1:25 | <toplevel> |
|
||||
| src/node_modules/b | b | src/node_modules/b/lib/index.js:1:1:2:0 | <toplevel> |
|
||||
| src/node_modules/b | b | src/node_modules/b/lib/util.ts:1:1:2:0 | <toplevel> |
|
||||
| src/node_modules/c | c | src/node_modules/c/src/index.js:1:1:2:0 | <toplevel> |
|
||||
| src/node_modules/d | d | src/node_modules/d/main.js:1:1:2:0 | <toplevel> |
|
||||
| src/node_modules/nested | nested | src/node_modules/nested/tst3.js:1:1:2:13 | <toplevel> |
|
||||
| src/node_modules/nested/node_modules/a | a | src/node_modules/nested/node_modules/a/index.js:1:1:1:25 | <toplevel> |
|
||||
| src/node_modules/parent-module | parent-module | src/node_modules/parent-module/main.js:1:1:2:0 | <toplevel> |
|
||||
| src/node_modules/parent-module | parent-module | src/node_modules/parent-module/sub-module/main.js:1:1:2:0 | <toplevel> |
|
||||
| src/node_modules/parent-module/sub-module | parent-module/sub-module | src/node_modules/parent-module/sub-module/main.js:1:1:2:0 | <toplevel> |
|
||||
| src/node_modules/third-party-module | third-party-module | src/node_modules/third-party-module/fancy.js:1:1:4:0 | <toplevel> |
|
||||
npm
|
||||
| src/node_modules/third-party-module/package.json:1:1:5:1 | {\\n "na ... y.js"\\n} | third-party-module | 23.4.0 |
|
||||
| src/package.json:1:1:20:1 | {\\n "na ... "\\n }\\n} | test-package | 0.1.0 |
|
||||
getMainModule
|
||||
| src/node_modules/a/package.json:1:1:4:1 | {\\n " ... x.js"\\n} | a | src/node_modules/a/index.js:1:1:1:25 | <toplevel> |
|
||||
| src/node_modules/b/package.json:1:1:4:1 | {\\n "na ... "lib"\\n} | b | src/node_modules/b/lib/index.js:1:1:2:0 | <toplevel> |
|
||||
| src/node_modules/c/package.json:1:1:4:1 | {\\n "na ... src/"\\n} | c | src/node_modules/c/src/index.js:1:1:2:0 | <toplevel> |
|
||||
| src/node_modules/d/package.json:1:1:4:1 | {\\n "na ... main"\\n} | d | src/node_modules/d/main.js:1:1:2:0 | <toplevel> |
|
||||
| src/node_modules/nested/node_modules/a/package.json:1:1:5:1 | {\\n " ... ge A"\\n} | a | src/node_modules/nested/node_modules/a/index.js:1:1:1:25 | <toplevel> |
|
||||
| src/node_modules/parent-module/package.json:1:1:4:1 | {\\n "na ... n.js"\\n} | parent-module | src/node_modules/parent-module/main.js:1:1:2:0 | <toplevel> |
|
||||
| src/node_modules/parent-module/sub-module/package.json:1:1:3:1 | {\\n "ma ... n.js"\\n} | parent-module/sub-module | src/node_modules/parent-module/sub-module/main.js:1:1:2:0 | <toplevel> |
|
||||
| src/node_modules/third-party-module/package.json:1:1:5:1 | {\\n "na ... y.js"\\n} | third-party-module | src/node_modules/third-party-module/fancy.js:1:1:4:0 | <toplevel> |
|
||||
| src/package.json:1:1:20:1 | {\\n "na ... "\\n }\\n} | test-package | src/index.js:1:1:4:0 | <toplevel> |
|
||||
packageJson
|
||||
| src/node_modules/a/package.json:1:1:4:1 | {\\n " ... x.js"\\n} |
|
||||
| src/node_modules/b/package.json:1:1:4:1 | {\\n "na ... "lib"\\n} |
|
||||
| src/node_modules/c/package.json:1:1:4:1 | {\\n "na ... src/"\\n} |
|
||||
| src/node_modules/d/package.json:1:1:4:1 | {\\n "na ... main"\\n} |
|
||||
| src/node_modules/nested/node_modules/a/package.json:1:1:5:1 | {\\n " ... ge A"\\n} |
|
||||
| src/node_modules/nested/package.json:1:1:3:1 | {\\n " ... sted"\\n} |
|
||||
| src/node_modules/parent-module/package.json:1:1:4:1 | {\\n "na ... n.js"\\n} |
|
||||
| src/node_modules/parent-module/sub-module/package.json:1:1:3:1 | {\\n "ma ... n.js"\\n} |
|
||||
| src/node_modules/third-party-module/package.json:1:1:5:1 | {\\n "na ... y.js"\\n} |
|
||||
|
||||
@@ -94,14 +94,12 @@ requireImport
|
||||
| a.js:3:6:3:23 | require('./sub/c') | ./sub/c | sub/c.js:1:1:4:0 | <toplevel> |
|
||||
| a.js:4:6:4:29 | require ... /d.js') | ./sub/../d.js | d.js:1:1:7:15 | <toplevel> |
|
||||
| a.js:7:1:7:18 | require('./sub/c') | ./sub/c | sub/c.js:1:1:4:0 | <toplevel> |
|
||||
| a.js:10:1:10:18 | require(__dirname) | | index.js:1:1:3:0 | <toplevel> |
|
||||
| a.js:11:1:11:25 | require ... + '/e') | /e | e.js:1:1:6:0 | <toplevel> |
|
||||
| a.js:12:1:12:28 | require ... + 'c') | ./sub/c | sub/c.js:1:1:4:0 | <toplevel> |
|
||||
| b.js:1:1:1:18 | require('./sub/c') | ./sub/c | sub/c.js:1:1:4:0 | <toplevel> |
|
||||
| d.js:7:1:7:14 | require('foo') | foo | sub/f.js:1:1:4:17 | <toplevel> |
|
||||
| index.js:2:1:2:41 | require ... b.js")) | /index.js/../b.js | b.js:1:1:8:0 | <toplevel> |
|
||||
| mjs-files/require-from-js.js:1:12:1:36 | require ... on-me') | ./depend-on-me | mjs-files/depend-on-me.mjs:1:1:7:1 | <toplevel> |
|
||||
| mjs-files/require-from-js.js:2:12:2:39 | require ... me.js') | ./depend-on-me.js | mjs-files/depend-on-me.js:1:1:8:0 | <toplevel> |
|
||||
| mjs-files/require-from-js.js:2:12:2:39 | require ... me.js') | ./depend-on-me.js | mjs-files/depend-on-me.mjs:1:1:7:1 | <toplevel> |
|
||||
| mjs-files/require-from-js.js:3:12:3:40 | require ... e.mjs') | ./depend-on-me.mjs | mjs-files/depend-on-me.mjs:1:1:7:1 | <toplevel> |
|
||||
| reexport/b.js:1:11:1:24 | require("./a") | ./a | reexport/a.js:1:1:3:1 | <toplevel> |
|
||||
| sub/c.js:1:1:1:15 | require('../a') | ../a | a.js:1:1:14:0 | <toplevel> |
|
||||
|
||||
@@ -26,7 +26,7 @@ query predicate require(Require r) { any() }
|
||||
|
||||
query predicate requireImport(Require r, string path, Module mod) {
|
||||
exists(string fullpath, string prefix |
|
||||
fullpath = r.getImportedPath().getValue() and
|
||||
fullpath = r.getImportedPathString() and
|
||||
sourceLocationPrefix(prefix) and
|
||||
path = fullpath.replaceAll(prefix, "") and
|
||||
mod = r.getImportedModule()
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
import f from '~/a'; // $ importTarget=BabelRootImport/tst1/a.js
|
||||
@@ -0,0 +1 @@
|
||||
import f from '~/a'; // $ importTarget=BabelRootImport/tst1/a.js
|
||||
@@ -0,0 +1,2 @@
|
||||
import g from '~/b.js'; // $ importTarget=BabelRootImport/tst2/src/js/b.js
|
||||
import f from '#/a'; // $ importTarget=BabelRootImport/tst1/a.js
|
||||
@@ -0,0 +1 @@
|
||||
import greeting from '~/b.js'; // $ importTarget=BabelRootImport/tst3/src/b.js
|
||||
@@ -0,0 +1,2 @@
|
||||
import g from '~/b.js'; // $ importTarget=BabelRootImport/tst4/src/js/b.js
|
||||
import f from '#/a'; // $ importTarget=BabelRootImport/tst1/a.js
|
||||
@@ -0,0 +1 @@
|
||||
export const x = "file.ts"
|
||||
@@ -0,0 +1 @@
|
||||
export const x = "index.ts";
|
||||
@@ -0,0 +1 @@
|
||||
export const x = "nostar.ts"
|
||||
@@ -0,0 +1 @@
|
||||
export const x = 1;
|
||||
@@ -0,0 +1,47 @@
|
||||
// Relative import
|
||||
import "../base/lib/file"; // $ importTarget=BaseUrl/base/lib/file.ts
|
||||
import "../base/lib/file.ts"; // $ importTarget=BaseUrl/base/lib/file.ts
|
||||
import "../base/lib/file.js"; // $ importTarget=BaseUrl/base/lib/file.ts
|
||||
import "../base/lib"; // $ importTarget=BaseUrl/base/lib/index.ts
|
||||
import "../base/lib/index"; // $ importTarget=BaseUrl/base/lib/index.ts
|
||||
import "../base/lib/index.ts"; // $ importTarget=BaseUrl/base/lib/index.ts
|
||||
import "../base/lib/index.js"; // $ importTarget=BaseUrl/base/lib/index.ts
|
||||
|
||||
// Import relative to baseUrl
|
||||
import "lib/file"; // $ importTarget=BaseUrl/base/lib/file.ts
|
||||
import "lib/file.ts"; // $ importTarget=BaseUrl/base/lib/file.ts
|
||||
import "lib/file.js"; // $ importTarget=BaseUrl/base/lib/file.ts
|
||||
import "lib"; // $ importTarget=BaseUrl/base/lib/index.ts
|
||||
import "lib/index"; // $ importTarget=BaseUrl/base/lib/index.ts
|
||||
import "lib/index.ts"; // $ importTarget=BaseUrl/base/lib/index.ts
|
||||
import "lib/index.js"; // $ importTarget=BaseUrl/base/lib/index.ts
|
||||
|
||||
// Import matching "@/*" path mapping
|
||||
import "@/file"; // $ importTarget=BaseUrl/base/lib/file.ts
|
||||
import "@/file.ts"; // $ importTarget=BaseUrl/base/lib/file.ts
|
||||
import "@/file.js"; // $ importTarget=BaseUrl/base/lib/file.ts
|
||||
import "@"; // $ importTarget=BaseUrl/base/lib/nostar.ts
|
||||
import "@/index"; // $ importTarget=BaseUrl/base/lib/index.ts
|
||||
import "@/index.ts"; // $ importTarget=BaseUrl/base/lib/index.ts
|
||||
import "@/index.js"; // $ importTarget=BaseUrl/base/lib/index.ts
|
||||
|
||||
// Import matching "#/*" path mapping
|
||||
import "#/file"; // $ importTarget=BaseUrl/base/lib/file.ts
|
||||
import "#/file.ts"; // $ importTarget=BaseUrl/base/lib/file.ts
|
||||
import "#/file.js"; // $ importTarget=BaseUrl/base/lib/file.ts
|
||||
import "#/index"; // $ importTarget=BaseUrl/base/lib/index.ts
|
||||
import "#/index.ts"; // $ importTarget=BaseUrl/base/lib/index.ts
|
||||
import "#/index.js"; // $ importTarget=BaseUrl/base/lib/index.ts
|
||||
|
||||
// Import matching "^lib*" path mapping
|
||||
import "^lib/file"; // $ importTarget=BaseUrl/base/lib/file.ts
|
||||
import "^lib2/file"; // $ importTarget=BaseUrl/base/lib2/file.ts
|
||||
|
||||
// Import matching "@/*.xyz" path mapping. Note that this is not actually supported by TypeScript.
|
||||
import "@/file.xyz";
|
||||
import "@/file.ts.xyz";
|
||||
import "@/file.js.xyz";
|
||||
import "@.xyz";
|
||||
import "@/index.xyz";
|
||||
import "@/index.ts.xyz";
|
||||
import "@/index.js.xyz";
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"include": ["."],
|
||||
"compilerOptions": {
|
||||
// Path mappings are resolved relative to baseUrl
|
||||
"baseUrl": "./base",
|
||||
"paths": {
|
||||
"@/*": ["lib/*"],
|
||||
"#/*": ["./lib/*"], // relative paths here are also resolved from the base url
|
||||
"^lib*": ["./lib*"], // must end with "*" but not necessarily "/*"
|
||||
"@/*.xyz": ["lib/*"],
|
||||
"@": ["lib/nostar.ts"]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
import '..'; // $ importTarget=Basic/index.ts
|
||||
import '../'; // $ importTarget=Basic/index.ts
|
||||
import './..'; // $ importTarget=Basic/index.ts
|
||||
import './../'; // $ importTarget=Basic/index.ts
|
||||
@@ -0,0 +1 @@
|
||||
import './Subdir/sub'; // $ importTarget=Basic/Subdir/sub.ts
|
||||
5
javascript/ql/test/library-tests/PathResolution/DeclarationFiles/lib/split.d.ts
vendored
Normal file
5
javascript/ql/test/library-tests/PathResolution/DeclarationFiles/lib/split.d.ts
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
// Type declaration for dual-module
|
||||
export interface DualType {
|
||||
prop: string;
|
||||
}
|
||||
export declare function helper(): DualType;
|
||||
@@ -0,0 +1,4 @@
|
||||
// Implementation of dual-module
|
||||
export function helper() {
|
||||
return { prop: "implementation" };
|
||||
}
|
||||
1
javascript/ql/test/library-tests/PathResolution/DeclarationFiles/lib/typescript.d.ts
vendored
Normal file
1
javascript/ql/test/library-tests/PathResolution/DeclarationFiles/lib/typescript.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
||||
export const x: number;
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user