Merge branch 'main' into js/quality/stream_pipe

This commit is contained in:
Napalys Klicius
2025-06-03 13:48:41 +02:00
committed by GitHub
1299 changed files with 157154 additions and 12607 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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))

View File

@@ -11,5 +11,5 @@
import javascript
from ImportDeclaration id
where id.getImportedPath().getValue() = "react"
where id.getImportedPathString() = "react"
select id

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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.

View File

@@ -1,4 +0,0 @@
---
category: minorAnalysis
---
* Added support for the `fastify` `addHook` method.

View File

@@ -0,0 +1,4 @@
---
category: minorAnalysis
---
* Improved analysis for `ES6 classes` mixed with `function prototypes`, leading to more accurate call graph resolution.

View File

@@ -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.

View File

@@ -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`.

View File

@@ -0,0 +1,4 @@
---
category: minorAnalysis
---
* Added taint flow through the `URL` constructor from the `url` package, improving the identification of SSRF vulnerabilities.

View 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.

View File

@@ -1,2 +1,2 @@
---
lastReleaseVersion: 2.6.2
lastReleaseVersion: 2.6.3

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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)

View File

@@ -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() }

View File

@@ -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())
}

View File

@@ -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() }
}

View File

@@ -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) }

View File

@@ -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()
)
}

View File

@@ -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()
}

View File

@@ -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

View File

@@ -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 }
}

View File

@@ -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

View File

@@ -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

View 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() }
}

View File

@@ -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())
)

View File

@@ -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;
}
/**

View File

@@ -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()
)
}

View File

@@ -264,3 +264,7 @@ module Stage {
cached
predicate backref() { optionalStep(_, _, _) }
}
predicate unsupportedCallable = Private::unsupportedCallable/1;
predicate unsupportedCallable = Private::unsupportedCallable/4;

View File

@@ -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)

View File

@@ -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]

View File

@@ -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())
)

View File

@@ -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. */

View File

@@ -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 }
}
/**

View File

@@ -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()
}
}
}

View File

@@ -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() }

View File

@@ -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())
}

View File

@@ -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()
)
}

View File

@@ -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()
)

View File

@@ -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`. */

View File

@@ -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
)
)
}
}

View File

@@ -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()
}

View File

@@ -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 = "/" }
}

View File

@@ -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>;

View File

@@ -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() }
}

View File

@@ -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

View File

@@ -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

View File

@@ -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.

View File

@@ -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()

View File

@@ -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

View File

@@ -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`

View File

@@ -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

View File

@@ -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

View File

@@ -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.

View File

@@ -0,0 +1,4 @@
---
category: minorAnalysis
---
* The queries `js/hardcoded-credentials` and `js/password-in-configuration-file` have been removed from all query suites.

View File

@@ -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.

View File

@@ -1,2 +1,2 @@
---
lastReleaseVersion: 1.5.4
lastReleaseVersion: 1.6.0

View File

@@ -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\/.*/

View File

@@ -1,5 +1,5 @@
name: codeql/javascript-queries
version: 1.5.5-dev
version: 1.6.1-dev
groups:
- javascript
- queries

View File

@@ -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() }

View File

@@ -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 |

View File

@@ -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) {

View 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();
}

View File

@@ -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) {

View File

@@ -0,0 +1,4 @@
{
"name": "a",
"main": "index.js"
}

View File

@@ -0,0 +1,5 @@
{
"name": "a",
"main": "index.js",
"description": "Nested version of package A"
}

View File

@@ -0,0 +1,3 @@
{
"name": "nested"
}

View File

@@ -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} |

View File

@@ -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> |

View File

@@ -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()

View File

@@ -0,0 +1 @@
import f from '~/a'; // $ importTarget=BabelRootImport/tst1/a.js

View File

@@ -0,0 +1 @@
import f from '~/a'; // $ importTarget=BabelRootImport/tst1/a.js

View File

@@ -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

View File

@@ -0,0 +1 @@
import greeting from '~/b.js'; // $ importTarget=BabelRootImport/tst3/src/b.js

View File

@@ -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

View File

@@ -0,0 +1 @@
export const x = "file.ts"

View File

@@ -0,0 +1 @@
export const x = "index.ts";

View File

@@ -0,0 +1 @@
export const x = "nostar.ts"

View File

@@ -0,0 +1 @@
export const x = 1;

View File

@@ -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";

View File

@@ -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"]
}
}
}

View File

@@ -0,0 +1,4 @@
import '..'; // $ importTarget=Basic/index.ts
import '../'; // $ importTarget=Basic/index.ts
import './..'; // $ importTarget=Basic/index.ts
import './../'; // $ importTarget=Basic/index.ts

View File

@@ -0,0 +1 @@
import './Subdir/sub'; // $ importTarget=Basic/Subdir/sub.ts

View File

@@ -0,0 +1,5 @@
// Type declaration for dual-module
export interface DualType {
prop: string;
}
export declare function helper(): DualType;

View File

@@ -0,0 +1,4 @@
// Implementation of dual-module
export function helper() {
return { prop: "implementation" };
}

View File

@@ -0,0 +1 @@
export const x: number;

Some files were not shown because too many files have changed in this diff Show More