mirror of
https://github.com/github/codeql.git
synced 2026-04-22 23:35:14 +02:00
Merge branch 'main' into ts-54
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
load("@//:dist.bzl", "dist")
|
||||
load("@semmle_code//:dist.bzl", "dist")
|
||||
load("@rules_pkg//pkg:mappings.bzl", "pkg_files")
|
||||
load("@//buildutils-internal:zipmerge.bzl", "zipmerge")
|
||||
load("@semmle_code//buildutils-internal:zipmerge.bzl", "zipmerge")
|
||||
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
@@ -30,7 +30,7 @@ dist(
|
||||
"//javascript/downgrades",
|
||||
"//javascript/externs",
|
||||
"//javascript/extractor:tools-extractor",
|
||||
"@//language-packs/javascript:resources",
|
||||
"@semmle_code//language-packs/javascript:resources",
|
||||
],
|
||||
prefix = "javascript",
|
||||
)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
load("@//:dist.bzl", "pack_zip")
|
||||
load("@semmle_code//:dist.bzl", "pack_zip")
|
||||
|
||||
pack_zip(
|
||||
name = "downgrades",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
load("@//:dist.bzl", "pack_zip")
|
||||
load("@semmle_code//:dist.bzl", "pack_zip")
|
||||
|
||||
pack_zip(
|
||||
name = "externs",
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
load("@//:common.bzl", "codeql_fat_jar", "codeql_java_project")
|
||||
load("@semmle_code//:common.bzl", "codeql_fat_jar", "codeql_java_project")
|
||||
load("@rules_pkg//pkg:mappings.bzl", "pkg_files")
|
||||
|
||||
java_library(
|
||||
name = "deps",
|
||||
visibility = [":__subpackages__"],
|
||||
exports = [
|
||||
"@//extractor:html",
|
||||
"@//extractor:yaml",
|
||||
"@//resources/lib/java:commons-compress",
|
||||
"@//resources/lib/java:gson",
|
||||
"@//resources/lib/java:jericho-html",
|
||||
"@//resources/lib/java:slf4j-api",
|
||||
"@//resources/lib/java:snakeyaml",
|
||||
"@//third_party:jackson",
|
||||
"@//third_party:logback",
|
||||
"@//util-java7",
|
||||
"@//util-java8",
|
||||
"@semmle_code//extractor:html",
|
||||
"@semmle_code//extractor:yaml",
|
||||
"@semmle_code//resources/lib/java:commons-compress",
|
||||
"@semmle_code//resources/lib/java:gson",
|
||||
"@semmle_code//resources/lib/java:jericho-html",
|
||||
"@semmle_code//resources/lib/java:slf4j-api",
|
||||
"@semmle_code//resources/lib/java:snakeyaml",
|
||||
"@semmle_code//third_party:jackson",
|
||||
"@semmle_code//third_party:logback",
|
||||
"@semmle_code//util-java7",
|
||||
"@semmle_code//util-java8",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -36,18 +36,18 @@ codeql_fat_jar(
|
||||
name = "extractor-javascript",
|
||||
srcs = [
|
||||
":extractor",
|
||||
"@//extractor:html",
|
||||
"@//extractor:xml-trap-writer",
|
||||
"@//extractor:yaml",
|
||||
"@//resources/lib/java:commons-compress",
|
||||
"@//resources/lib/java:gson",
|
||||
"@//resources/lib/java:jericho-html",
|
||||
"@//resources/lib/java:slf4j-api",
|
||||
"@//resources/lib/java:snakeyaml",
|
||||
"@//third_party:jackson",
|
||||
"@//third_party:logback",
|
||||
"@//util-java7",
|
||||
"@//util-java8",
|
||||
"@semmle_code//extractor:html",
|
||||
"@semmle_code//extractor:xml-trap-writer",
|
||||
"@semmle_code//extractor:yaml",
|
||||
"@semmle_code//resources/lib/java:commons-compress",
|
||||
"@semmle_code//resources/lib/java:gson",
|
||||
"@semmle_code//resources/lib/java:jericho-html",
|
||||
"@semmle_code//resources/lib/java:slf4j-api",
|
||||
"@semmle_code//resources/lib/java:snakeyaml",
|
||||
"@semmle_code//third_party:jackson",
|
||||
"@semmle_code//third_party:logback",
|
||||
"@semmle_code//util-java7",
|
||||
"@semmle_code//util-java8",
|
||||
],
|
||||
files = [":javascript-extractor-resources"],
|
||||
main_class = "com.semmle.js.extractor.Main",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
load("@//:common.bzl", "on_windows")
|
||||
load("@semmle_code//:common.bzl", "on_windows")
|
||||
|
||||
# Builds a zip file of the compiled typscript-parser-wrapper and its dependencies.
|
||||
genrule(
|
||||
|
||||
@@ -1,5 +1,15 @@
|
||||
package com.semmle.js.extractor.test;
|
||||
|
||||
import com.google.devtools.build.runfiles.Runfiles;
|
||||
import com.semmle.util.process.Env;
|
||||
import java.io.FileInputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipInputStream;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.Suite;
|
||||
import org.junit.runners.Suite.SuiteClasses;
|
||||
@@ -18,4 +28,35 @@ import org.junit.runners.Suite.SuiteClasses;
|
||||
RobustnessTests.class,
|
||||
NumericSeparatorTests.class
|
||||
})
|
||||
public class AllTests {}
|
||||
public class AllTests {
|
||||
|
||||
@BeforeClass
|
||||
public static void setUp() throws Exception {
|
||||
Runfiles.Preloaded runfiles = Runfiles.preload();
|
||||
String nodePath = runfiles.unmapped().rlocation(System.getenv("NODE_BIN"));
|
||||
String tsWrapperZip = runfiles.unmapped().rlocation(System.getenv("TS_WRAPPER_ZIP"));
|
||||
Path tempDir = Files.createTempDirectory("ts-wrapper");
|
||||
// extract the ts-wrapper.zip to tempDir:
|
||||
try (ZipInputStream zis = new ZipInputStream(new FileInputStream(tsWrapperZip))) {
|
||||
ZipEntry entry = zis.getNextEntry();
|
||||
while (entry != null) {
|
||||
Path entryPath = tempDir.resolve(entry.getName());
|
||||
if (entry.isDirectory()) {
|
||||
Files.createDirectories(entryPath);
|
||||
} else {
|
||||
Files.copy(zis, entryPath);
|
||||
}
|
||||
entry = zis.getNextEntry();
|
||||
}
|
||||
}
|
||||
Path tsWrapper = tempDir.resolve("javascript/tools/typescript-parser-wrapper/main.js");
|
||||
if (!Files.exists(tsWrapper)) {
|
||||
throw new RuntimeException("Could not find ts-wrapper at " + tsWrapper);
|
||||
}
|
||||
Map<String, String> envUpdate = new HashMap<>();
|
||||
envUpdate.put("SEMMLE_TYPESCRIPT_NODE_RUNTIME", nodePath);
|
||||
envUpdate.put("SEMMLE_TYPESCRIPT_PARSER_WRAPPER", tsWrapper.toString());
|
||||
|
||||
Env.systemEnv().pushEnvironmentContext(envUpdate);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,33 +1,21 @@
|
||||
java_test(
|
||||
name = "test_jar",
|
||||
name = "test",
|
||||
srcs = glob(["**/*.java"]),
|
||||
data = [
|
||||
"//javascript/extractor/lib/typescript",
|
||||
"//javascript/extractor/parser-tests",
|
||||
"//javascript/extractor/tests",
|
||||
"@nodejs//:node_bin",
|
||||
],
|
||||
env = {
|
||||
"NODE_BIN": "$(rlocationpath @nodejs//:node_bin)",
|
||||
"TS_WRAPPER_ZIP": "$(rlocationpath //javascript/extractor/lib/typescript)",
|
||||
},
|
||||
test_class = "com.semmle.js.extractor.test.AllTests",
|
||||
deps = [
|
||||
"//javascript/extractor",
|
||||
"//javascript/extractor:deps",
|
||||
"@//resources/lib/java/DO_NOT_DISTRIBUTE:junit",
|
||||
"@bazel_tools//tools/java/runfiles",
|
||||
"@semmle_code//resources/lib/java/DO_NOT_DISTRIBUTE:junit",
|
||||
],
|
||||
)
|
||||
|
||||
# We need to unzip the typescript wrapper, and provide node on the path.
|
||||
# Therefore, we're wrapping the java_test inside a sh_test.
|
||||
sh_test(
|
||||
name = "test",
|
||||
size = "small",
|
||||
srcs = ["run_tests.sh"],
|
||||
args = [
|
||||
"$(execpath @nodejs//:node_bin)",
|
||||
"$(JAVABASE)/bin/java",
|
||||
"$(rootpath //javascript/extractor/lib/typescript)",
|
||||
"$(rootpath test_jar_deploy.jar)",
|
||||
],
|
||||
data = [
|
||||
":test_jar_deploy.jar",
|
||||
"//javascript/extractor/lib/typescript",
|
||||
"//javascript/extractor/parser-tests",
|
||||
"//javascript/extractor/tests",
|
||||
"@bazel_tools//tools/jdk:current_java_runtime",
|
||||
"@nodejs//:node_bin",
|
||||
],
|
||||
toolchains = ["@bazel_tools//tools/jdk:current_java_runtime"],
|
||||
)
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
NODE=$1
|
||||
JAVA=$2
|
||||
PARSER_WRAPPER=$3
|
||||
TEST_JAR=$4
|
||||
|
||||
TEMP=$(mktemp -d)
|
||||
|
||||
UNAME=$(uname -s)
|
||||
echo $UNAME
|
||||
# On Windows, the symlink set up by bazel that points at the test jar is a msys2/linux-style path
|
||||
# The JVM can't resolve that, therefore copy the jar to the temp directory, and then set the
|
||||
# windows path to it
|
||||
if [[ "$UNAME" =~ _NT ]]; then
|
||||
cp $TEST_JAR $TEMP/test.jar
|
||||
TEST_JAR=$(cygpath -w $TEMP/test.jar)
|
||||
echo "On Windows, new test jar: $TEST_JAR"
|
||||
fi
|
||||
|
||||
# unpack parser wrapper
|
||||
unzip -q $PARSER_WRAPPER -d $TEMP/parser_wrapper
|
||||
export SEMMLE_TYPESCRIPT_PARSER_WRAPPER=$TEMP/parser_wrapper/javascript/tools/typescript-parser-wrapper/main.js
|
||||
|
||||
# setup node on path
|
||||
NODE=$(realpath $NODE)
|
||||
export PATH="$PATH:$(dirname $NODE)"
|
||||
|
||||
$JAVA -Dbazel.test_suite=com.semmle.js.extractor.test.AllTests -jar $TEST_JAR
|
||||
EXIT_CODE=$?
|
||||
|
||||
rm -rf $TEMP
|
||||
exit $EXIT_CODE
|
||||
@@ -1 +1 @@
|
||||
**/ql/javascript/extractor/tests/*/input//
|
||||
**/*ql*/javascript/extractor/tests/*/input//
|
||||
|
||||
@@ -1,3 +1,13 @@
|
||||
## 0.8.9
|
||||
|
||||
### Minor Analysis Improvements
|
||||
|
||||
* The name "certification" is no longer seen as possibly being a certificate, and will therefore no longer be flagged in queries like "clear-text-logging" which look for sensitive data.
|
||||
|
||||
## 0.8.8
|
||||
|
||||
No user-facing changes.
|
||||
|
||||
## 0.8.7
|
||||
|
||||
### Minor Analysis Improvements
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
*/
|
||||
|
||||
import semmle.files.FileSystem
|
||||
private import codeql.util.FileSystem
|
||||
|
||||
/**
|
||||
* Returns the `File` matching the given source file name as encoded by the VS
|
||||
@@ -10,13 +11,5 @@ import semmle.files.FileSystem
|
||||
*/
|
||||
cached
|
||||
File getFileBySourceArchiveName(string name) {
|
||||
// The name provided for a file in the source archive by the VS Code extension
|
||||
// has some differences from the absolute path in the database:
|
||||
// 1. colons are replaced by underscores
|
||||
// 2. there's a leading slash, even for Windows paths: "C:/foo/bar" ->
|
||||
// "/C_/foo/bar"
|
||||
// 3. double slashes in UNC prefixes are replaced with a single slash
|
||||
// We can handle 2 and 3 together by unconditionally adding a leading slash
|
||||
// before replacing double slashes.
|
||||
name = ("/" + result.getAbsolutePath().replaceAll(":", "_")).replaceAll("//", "/")
|
||||
result = IdeContextual<File>::getFileBySourceArchiveName(name)
|
||||
}
|
||||
|
||||
3
javascript/ql/lib/change-notes/released/0.8.8.md
Normal file
3
javascript/ql/lib/change-notes/released/0.8.8.md
Normal file
@@ -0,0 +1,3 @@
|
||||
## 0.8.8
|
||||
|
||||
No user-facing changes.
|
||||
5
javascript/ql/lib/change-notes/released/0.8.9.md
Normal file
5
javascript/ql/lib/change-notes/released/0.8.9.md
Normal file
@@ -0,0 +1,5 @@
|
||||
## 0.8.9
|
||||
|
||||
### Minor Analysis Improvements
|
||||
|
||||
* The name "certification" is no longer seen as possibly being a certificate, and will therefore no longer be flagged in queries like "clear-text-logging" which look for sensitive data.
|
||||
@@ -1,2 +1,2 @@
|
||||
---
|
||||
lastReleaseVersion: 0.8.7
|
||||
lastReleaseVersion: 0.8.9
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
name: codeql/javascript-all
|
||||
version: 0.8.8-dev
|
||||
version: 0.8.10-dev
|
||||
groups: javascript
|
||||
dbscheme: semmlecode.javascript.dbscheme
|
||||
extractor: javascript
|
||||
|
||||
@@ -594,6 +594,9 @@ module API {
|
||||
exportedName = "" and
|
||||
result = getAModuleImportRaw(moduleName)
|
||||
}
|
||||
|
||||
/** Gets a sink node that represents instances of `cls`. */
|
||||
Node getClassInstance(DataFlow::ClassNode cls) { result = Impl::MkClassInstance(cls) }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1621,6 +1624,7 @@ private predicate exports(string m, DataFlow::Node rhs) {
|
||||
exists(Module mod | mod = importableModule(m) |
|
||||
rhs = mod.(AmdModule).getDefine().getModuleExpr().flow()
|
||||
or
|
||||
not mod.(ES2015Module).hasBothNamedAndDefaultExports() and
|
||||
exports(m, "default", rhs)
|
||||
or
|
||||
exists(ExportAssignDeclaration assgn | assgn.getTopLevel() = mod |
|
||||
@@ -1634,6 +1638,7 @@ private predicate exports(string m, DataFlow::Node rhs) {
|
||||
/** Holds if module `m` exports `rhs` under the name `prop`. */
|
||||
private predicate exports(string m, string prop, DataFlow::Node rhs) {
|
||||
exists(ExportDeclaration exp | exp.getEnclosingModule() = importableModule(m) |
|
||||
not exp.isTypeOnly() and
|
||||
rhs = exp.getSourceNode(prop)
|
||||
or
|
||||
exists(Variable v |
|
||||
|
||||
@@ -516,16 +516,37 @@ class MemberDeclaration extends @property, Documentable {
|
||||
*/
|
||||
predicate hasPublicKeyword() { has_public_keyword(this) }
|
||||
|
||||
/**
|
||||
* Holds if this member is considered private.
|
||||
*
|
||||
* This may occur in two cases:
|
||||
* - it is a TypeScript member annotated with the `private` keyword, or
|
||||
* - the member has a private name, such as `#foo`, referring to a private field in the class
|
||||
*/
|
||||
predicate isPrivate() { this.hasPrivateKeyword() or this.hasPrivateFieldName() }
|
||||
|
||||
/**
|
||||
* Holds if this is a TypeScript member annotated with the `private` keyword.
|
||||
*/
|
||||
predicate isPrivate() { has_private_keyword(this) }
|
||||
predicate hasPrivateKeyword() { has_private_keyword(this) }
|
||||
|
||||
/**
|
||||
* Holds if this is a TypeScript member annotated with the `protected` keyword.
|
||||
*/
|
||||
predicate isProtected() { has_protected_keyword(this) }
|
||||
|
||||
/**
|
||||
* Holds if the member has a private name, such as `#foo`, referring to a private field in the class.
|
||||
*
|
||||
* For example:
|
||||
* ```js
|
||||
* class Foo {
|
||||
* #method() {}
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
predicate hasPrivateFieldName() { this.getNameExpr().(Label).getName().charAt(0) = "#" }
|
||||
|
||||
/**
|
||||
* Gets the expression specifying the name of this member,
|
||||
* or nothing if this is a call signature.
|
||||
|
||||
@@ -39,6 +39,20 @@ class ES2015Module extends Module {
|
||||
// modules are implicitly strict
|
||||
any()
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if this module contains both named and `default` exports.
|
||||
*
|
||||
* This is used to determine whether a default-import of the module should be reinterpreted
|
||||
* as a namespace-import, to accommodate the non-standard behavior implemented by some compilers.
|
||||
*
|
||||
* When a module has both named and `default` exports, the non-standard interpretation can lead to
|
||||
* ambiguities, so we only allow the standard interpretation in that case.
|
||||
*/
|
||||
predicate hasBothNamedAndDefaultExports() {
|
||||
hasNamedExports(this) and
|
||||
hasDefaultExport(this)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -64,17 +78,6 @@ private predicate hasDefaultExport(ES2015Module mod) {
|
||||
mod.getAnExport().(ExportNamedDeclaration).getASpecifier().getExportedName() = "default"
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `mod` contains both named and `default` exports.
|
||||
*
|
||||
* This is used to determine whether a default-import of the module should be reinterpreted
|
||||
* as a namespace-import, to accommodate the non-standard behavior implemented by some compilers.
|
||||
*/
|
||||
private predicate hasBothNamedAndDefaultExports(ES2015Module mod) {
|
||||
hasNamedExports(mod) and
|
||||
hasDefaultExport(mod)
|
||||
}
|
||||
|
||||
/**
|
||||
* An import declaration.
|
||||
*
|
||||
@@ -131,7 +134,7 @@ class ImportDeclaration extends Stmt, Import, @import_declaration {
|
||||
// For compatibility with the non-standard implementation of default imports,
|
||||
// treat default imports as namespace imports in cases where it can't cause ambiguity
|
||||
// between named exports and the properties of a default-exported object.
|
||||
not hasBothNamedAndDefaultExports(this.getImportedModule()) and
|
||||
not this.getImportedModule().(ES2015Module).hasBothNamedAndDefaultExports() and
|
||||
is.getImportedName() = "default"
|
||||
)
|
||||
or
|
||||
|
||||
@@ -29,7 +29,8 @@ class PackageJson extends JsonObject {
|
||||
parentDir.getAChildContainer+() = currentDir and
|
||||
pkgNameDiff = currentDir.getAbsolutePath().suffix(parentDir.getAbsolutePath().length()) and
|
||||
not exists(pkgNameDiff.indexOf("/node_modules/")) and
|
||||
result = parentPkgName + pkgNameDiff
|
||||
result = parentPkgName + pkgNameDiff and
|
||||
not parentPkg.isPrivate()
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -1693,7 +1693,11 @@ module DataFlow {
|
||||
exists(Expr predExpr, Expr succExpr |
|
||||
pred = valueNode(predExpr) and succ = valueNode(succExpr)
|
||||
|
|
||||
predExpr = succExpr.(LogicalBinaryExpr).getAnOperand()
|
||||
predExpr = succExpr.(LogicalOrExpr).getAnOperand()
|
||||
or
|
||||
predExpr = succExpr.(NullishCoalescingExpr).getAnOperand()
|
||||
or
|
||||
predExpr = succExpr.(LogicalAndExpr).getRightOperand()
|
||||
or
|
||||
predExpr = succExpr.(ConditionalExpr).getABranch()
|
||||
or
|
||||
|
||||
@@ -238,6 +238,26 @@ private class AnalyzedBinaryExpr extends DataFlow::AnalyzedValueNode {
|
||||
}
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate falsyValue(AbstractValue value) { value.getBooleanValue() = false }
|
||||
|
||||
/**
|
||||
* Flow analysis for `&&` operators.
|
||||
*/
|
||||
private class AnalyzedLogicalAndExpr extends DataFlow::AnalyzedValueNode {
|
||||
override LogicalAndExpr astNode;
|
||||
|
||||
pragma[nomagic]
|
||||
private AnalyzedValueNode leftOperand() { result = astNode.getLeftOperand().analyze() }
|
||||
|
||||
override AbstractValue getALocalValue() {
|
||||
result = super.getALocalValue()
|
||||
or
|
||||
result = this.leftOperand().getALocalValue() and
|
||||
falsyValue(result)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the `n`th operand of the given `+` or `+=` expression.
|
||||
*/
|
||||
|
||||
497
javascript/ql/lib/semmle/javascript/endpoints/EndpointNaming.qll
Normal file
497
javascript/ql/lib/semmle/javascript/endpoints/EndpointNaming.qll
Normal file
@@ -0,0 +1,497 @@
|
||||
/**
|
||||
* Provides predicates for generating names for classes and functions that are part
|
||||
* of the public API of a library.
|
||||
*
|
||||
* When possible, we try to use the qualified name by which a class/function can be accessed
|
||||
* from client code.
|
||||
*
|
||||
* However, there are cases where classes and functions can be exposed to client
|
||||
* code without being accessible as a qualified name. For example;
|
||||
* ```js
|
||||
* // 'Foo' is internal, but clients can call its methods, e.g. `getFoo().m()`
|
||||
* class Foo {
|
||||
* m() {}
|
||||
* }
|
||||
* export function getFoo() {
|
||||
* return new Foo();
|
||||
* }
|
||||
*
|
||||
* // Clients can call m() via getObj().m()
|
||||
* export function getObj() {
|
||||
* return {
|
||||
* m() {}
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* In these cases, we try to make up human-readable names for the endpoints.
|
||||
* We make an effort to make these unambiguous in practice, though this is not always guaranteed.
|
||||
*/
|
||||
|
||||
private import javascript
|
||||
|
||||
/** Concatenates two access paths. */
|
||||
bindingset[x, y]
|
||||
private string join(string x, string y) {
|
||||
if x = "" or y = "" then result = x + y else result = x + "." + y
|
||||
}
|
||||
|
||||
private predicate isPackageExport(API::Node node) { node = API::moduleExport(_) }
|
||||
|
||||
/**
|
||||
* A version of `getInstance()` only from sink nodes to the special `ClassInstance` node.
|
||||
*
|
||||
* This ensures we see instance methods, but not side effects on `this` or on instantiations of the class.
|
||||
*/
|
||||
private predicate instanceEdge(API::Node pred, API::Node succ) {
|
||||
exists(DataFlow::ClassNode cls |
|
||||
pred.getAValueReachingSink() = cls and
|
||||
succ = API::Internal::getClassInstance(cls)
|
||||
)
|
||||
}
|
||||
|
||||
/** Holds if `pred -> succ` is an edge we can use for naming. */
|
||||
private predicate relevantEdge(API::Node pred, API::Node succ) {
|
||||
succ = pred.getMember(_) and
|
||||
not isPrivateLike(succ)
|
||||
or
|
||||
instanceEdge(pred, succ)
|
||||
}
|
||||
|
||||
private signature predicate isRootNodeSig(API::Node node);
|
||||
|
||||
private signature predicate edgeSig(API::Node pred, API::Node succ);
|
||||
|
||||
/** Builds `shortestDistances` using the API graph root node as the only origin node, to ensure unique results. */
|
||||
private module ApiGraphDistance<isRootNodeSig/1 isRootNode, edgeSig/2 edges> {
|
||||
private predicate edgesWithEntry(API::Node pred, API::Node succ) {
|
||||
edges(pred, succ)
|
||||
or
|
||||
pred = API::root() and
|
||||
isRootNode(succ)
|
||||
}
|
||||
|
||||
int distanceTo(API::Node node) = shortestDistances(API::root/0, edgesWithEntry/2)(_, node, result)
|
||||
}
|
||||
|
||||
/** Gets the shortest distance from a package export to `nd` in the API graph. */
|
||||
private predicate distanceFromPackageExport =
|
||||
ApiGraphDistance<isPackageExport/1, relevantEdge/2>::distanceTo/1;
|
||||
|
||||
/**
|
||||
* Holds if `(package, name)` is the fallback name for `cls`, to be used as a last resort
|
||||
* in order to name its instance methods.
|
||||
*
|
||||
* This happens when the class is not accessible via an access path, but instances of the
|
||||
* class can still escape via more complex access patterns, for example:
|
||||
*
|
||||
* class InternalClass {}
|
||||
* function foo() {
|
||||
* return new InternalClass();
|
||||
* }
|
||||
*/
|
||||
private predicate classHasFallbackName(
|
||||
DataFlow::ClassNode cls, string package, string name, int badness
|
||||
) {
|
||||
hasEscapingInstance(cls) and
|
||||
not exists(distanceFromPackageExport(any(API::Node node | node.getAValueReachingSink() = cls))) and
|
||||
exists(string baseName |
|
||||
InternalModuleNaming::fallbackModuleName(cls.getTopLevel(), package, baseName, badness - 100) and
|
||||
name = join(baseName, cls.getName())
|
||||
)
|
||||
}
|
||||
|
||||
/** Holds if `node` describes instances of a class that has a fallback name. */
|
||||
private predicate isClassInstanceWithFallbackName(API::Node node) {
|
||||
exists(DataFlow::ClassNode cls |
|
||||
classHasFallbackName(cls, _, _, _) and
|
||||
node = API::Internal::getClassInstance(cls)
|
||||
)
|
||||
}
|
||||
|
||||
/** Gets the shortest distance from a node with a fallback name, to `nd` in the API graph. */
|
||||
private predicate distanceFromFallbackName =
|
||||
ApiGraphDistance<isClassInstanceWithFallbackName/1, relevantEdge/2>::distanceTo/1;
|
||||
|
||||
/** Gets the shortest distance from a name-root (package export or fallback name) to `nd` */
|
||||
private int distanceFromRoot(API::Node nd) {
|
||||
result = distanceFromPackageExport(nd)
|
||||
or
|
||||
not exists(distanceFromPackageExport(nd)) and
|
||||
result = 100 + distanceFromFallbackName(nd)
|
||||
}
|
||||
|
||||
/** Holds if `node` can be given a name. */
|
||||
private predicate isRelevant(API::Node node) { exists(distanceFromRoot(node)) }
|
||||
|
||||
/**
|
||||
* Holds if `node` is a default export that can be reinterpreted as a namespace export,
|
||||
* because the enclosing module has no named exports.
|
||||
*/
|
||||
private predicate defaultExportCanBeInterpretedAsNamespaceExport(API::Node node) {
|
||||
exists(ES2015Module mod |
|
||||
node.asSink() = mod.getAnExportedValue("default") and
|
||||
not mod.hasBothNamedAndDefaultExports()
|
||||
)
|
||||
}
|
||||
|
||||
private predicate isPrivateAssignment(DataFlow::Node node) {
|
||||
exists(MemberDeclaration decl |
|
||||
node = decl.getInit().flow() and
|
||||
decl.isPrivate()
|
||||
)
|
||||
or
|
||||
exists(DataFlow::PropWrite write |
|
||||
write.isPrivateField() and
|
||||
node = write.getRhs()
|
||||
)
|
||||
}
|
||||
|
||||
private predicate isPrivateLike(API::Node node) { isPrivateAssignment(node.asSink()) }
|
||||
|
||||
bindingset[name]
|
||||
private int getNameBadness(string name) {
|
||||
if name = ["constructor", "default"] then result = 10 else result = 0
|
||||
}
|
||||
|
||||
private API::Node getASuccessor(API::Node node, string name, int badness) {
|
||||
isRelevant(node) and
|
||||
isRelevant(result) and
|
||||
(
|
||||
exists(string member |
|
||||
result = node.getMember(member) and
|
||||
if member = "default" and defaultExportCanBeInterpretedAsNamespaceExport(node)
|
||||
then (
|
||||
badness = 5 and name = ""
|
||||
) else (
|
||||
name = member and
|
||||
badness = getNameBadness(name)
|
||||
)
|
||||
)
|
||||
or
|
||||
instanceEdge(node, result) and
|
||||
name = "prototype" and
|
||||
badness = 0
|
||||
)
|
||||
}
|
||||
|
||||
private API::Node getAPredecessor(API::Node node, string name, int badness) {
|
||||
node = getASuccessor(result, name, badness)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the predecessor of `node` to use when constructing a qualified name for it,
|
||||
* and binds `name` and `badness` corresponding to the label on that edge.
|
||||
*/
|
||||
private API::Node getPreferredPredecessor(API::Node node, string name, int badness) {
|
||||
// For root nodes, we prefer not having a predecessor, as we use the package name.
|
||||
not isPackageExport(node) and
|
||||
// Rank predecessors by name-badness, export-distance, and name.
|
||||
// Since min() can only return a single value, we need a separate min() call per column.
|
||||
badness =
|
||||
min(API::Node pred, int b |
|
||||
pred = getAPredecessor(node, _, b) and
|
||||
// ensure the preferred predecessor is strictly closer to a root export, even if it means accepting more badness
|
||||
distanceFromRoot(pred) < distanceFromRoot(node)
|
||||
|
|
||||
b
|
||||
) and
|
||||
result =
|
||||
min(API::Node pred, string name1 |
|
||||
pred = getAPredecessor(node, name1, badness) and
|
||||
// ensure the preferred predecessor is strictly closer to a root export, even if it means accepting more badness
|
||||
distanceFromRoot(pred) < distanceFromRoot(node)
|
||||
|
|
||||
pred order by distanceFromRoot(pred), name1
|
||||
) and
|
||||
name = min(string n | result = getAPredecessor(node, n, badness) | n)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `(package, name)` is a potential name to associate with `sink`.
|
||||
*
|
||||
* `badness` is bound to the associated badness of the name.
|
||||
*/
|
||||
private predicate sinkHasNameCandidate(API::Node sink, string package, string name, int badness) {
|
||||
sink = API::moduleExport(package) and
|
||||
name = "" and
|
||||
badness = 0
|
||||
or
|
||||
exists(DataFlow::ClassNode cls, string className |
|
||||
sink = API::Internal::getClassInstance(cls) and
|
||||
classHasFallbackName(cls, package, className, badness) and
|
||||
name = join(className, "prototype")
|
||||
)
|
||||
or
|
||||
exists(API::Node baseNode, string baseName, int baseBadness, string step, int stepBadness |
|
||||
sinkHasNameCandidate(baseNode, package, baseName, baseBadness) and
|
||||
baseNode = getPreferredPredecessor(sink, step, stepBadness) and
|
||||
badness = baseBadness + stepBadness and
|
||||
name = join(baseName, step)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `(package, name)` is the primary name to associate with `sink`.
|
||||
*
|
||||
* `badness` is bound to the associated badness of the name.
|
||||
*/
|
||||
private predicate sinkHasPrimaryName(API::Node sink, string package, string name, int badness) {
|
||||
badness = min(int b | sinkHasNameCandidate(sink, _, _, b) | b) and
|
||||
package = min(string p | sinkHasNameCandidate(sink, p, _, badness) | p) and
|
||||
name = min(string n | sinkHasNameCandidate(sink, package, n, badness) | n order by n.length(), n)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `(package, name)` is the primary name to associate with `node`.
|
||||
*/
|
||||
predicate sinkHasPrimaryName(API::Node sink, string package, string name) {
|
||||
sinkHasPrimaryName(sink, package, name, _)
|
||||
}
|
||||
|
||||
/** Gets a source node that can flow to `sink` without using a return step. */
|
||||
private DataFlow::SourceNode nodeReachingSink(API::Node sink, DataFlow::TypeBackTracker t) {
|
||||
t.start() and
|
||||
result = sink.asSink().getALocalSource()
|
||||
or
|
||||
exists(DataFlow::TypeBackTracker t2 |
|
||||
result = nodeReachingSink(sink, t2).backtrack(t2, t) and
|
||||
t.hasReturn() = false
|
||||
)
|
||||
}
|
||||
|
||||
/** Gets a source node that can flow to `sink` without using a return step. */
|
||||
DataFlow::SourceNode nodeReachingSink(API::Node sink) {
|
||||
result = nodeReachingSink(sink, DataFlow::TypeBackTracker::end())
|
||||
}
|
||||
|
||||
/** Gets a sink node reachable from `node`. */
|
||||
private API::Node getASinkNode(DataFlow::SourceNode node) { node = nodeReachingSink(result) }
|
||||
|
||||
/**
|
||||
* Holds if `node` is assigned to a global access path. Note that such nodes generally do not have API nodes.
|
||||
*/
|
||||
private predicate nameFromGlobal(DataFlow::Node node, string package, string name, int badness) {
|
||||
package = "global" and
|
||||
node = AccessPath::getAnAssignmentTo(name) and
|
||||
(if node.getTopLevel().isExterns() then badness = -10 else badness = 10)
|
||||
}
|
||||
|
||||
/** Holds if an instance of `cls` can be exposed to client code. */
|
||||
private predicate hasEscapingInstance(DataFlow::ClassNode cls) {
|
||||
cls.getAnInstanceReference().flowsTo(any(API::Node n).asSink())
|
||||
}
|
||||
|
||||
private predicate sourceNodeHasNameCandidate(
|
||||
DataFlow::SourceNode node, string package, string name, int badness
|
||||
) {
|
||||
sinkHasPrimaryName(getASinkNode(node), package, name, badness)
|
||||
or
|
||||
nameFromGlobal(node, package, name, badness)
|
||||
}
|
||||
|
||||
private predicate sourceNodeHasPrimaryName(
|
||||
DataFlow::SourceNode node, string package, string name, int badness
|
||||
) {
|
||||
badness = min(int b | sourceNodeHasNameCandidate(node, _, _, b) | b) and
|
||||
package =
|
||||
min(string p | sourceNodeHasNameCandidate(node, p, _, badness) | p order by p.length(), p) and
|
||||
name =
|
||||
min(string n | sourceNodeHasNameCandidate(node, package, n, badness) | n order by n.length(), n)
|
||||
}
|
||||
|
||||
/** Gets a data flow node referring to a function value. */
|
||||
private DataFlow::SourceNode functionValue(DataFlow::TypeTracker t) {
|
||||
t.start() and
|
||||
(
|
||||
result instanceof DataFlow::FunctionNode
|
||||
or
|
||||
result instanceof DataFlow::ClassNode
|
||||
or
|
||||
result instanceof DataFlow::PartialInvokeNode
|
||||
or
|
||||
result = DataFlow::globalVarRef(["Function", "eval"]).getAnInvocation()
|
||||
or
|
||||
// Assume double-invocation of Function also returns a function
|
||||
result = DataFlow::globalVarRef("Function").getAnInvocation().getAnInvocation()
|
||||
)
|
||||
or
|
||||
exists(DataFlow::TypeTracker t2 | result = functionValue(t2).track(t2, t))
|
||||
}
|
||||
|
||||
/** Gets a data flow node referring to a function value. */
|
||||
private DataFlow::SourceNode functionValue() {
|
||||
result = functionValue(DataFlow::TypeTracker::end())
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `node` is a function or a call that returns a function.
|
||||
*/
|
||||
private predicate isFunctionSource(DataFlow::SourceNode node) {
|
||||
(
|
||||
exists(getASinkNode(node))
|
||||
or
|
||||
nameFromGlobal(node, _, _, _)
|
||||
) and
|
||||
(
|
||||
node instanceof DataFlow::FunctionNode
|
||||
or
|
||||
node instanceof DataFlow::ClassNode
|
||||
or
|
||||
node = functionValue() and
|
||||
node instanceof DataFlow::InvokeNode and
|
||||
// `getASinkNode` steps through imports (but not other calls) so exclude calls that are imports (i.e. require calls)
|
||||
// as we want to get as close to the source as possible.
|
||||
not node instanceof DataFlow::ModuleImportNode
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `(package, name)` is the primary name for the given `function`.
|
||||
*
|
||||
* The `function` node may be an actual function expression, or a call site from which a function is returned.
|
||||
*/
|
||||
predicate functionHasPrimaryName(DataFlow::SourceNode function, string package, string name) {
|
||||
sourceNodeHasPrimaryName(function, package, name, _) and
|
||||
isFunctionSource(function)
|
||||
}
|
||||
|
||||
private predicate sinkHasSourceName(API::Node sink, string package, string name, int badness) {
|
||||
exists(DataFlow::SourceNode source |
|
||||
sink = getASinkNode(source) and
|
||||
sourceNodeHasPrimaryName(source, package, name, badness)
|
||||
)
|
||||
}
|
||||
|
||||
private predicate sinkHasPrimarySourceName(API::Node sink, string package, string name) {
|
||||
strictcount(string p, string n | sinkHasSourceName(sink, p, n, _)) = 1 and
|
||||
sinkHasSourceName(sink, package, name, _)
|
||||
}
|
||||
|
||||
private predicate aliasCandidate(
|
||||
string package, string name, string targetPackage, string targetName, API::Node aliasDef
|
||||
) {
|
||||
sinkHasPrimaryName(aliasDef, package, name) and
|
||||
sinkHasPrimarySourceName(aliasDef, targetPackage, targetName) and
|
||||
not sinkHasSourceName(_, package, name, _) // (package, name) cannot be an alias if a source has it as its primary name
|
||||
}
|
||||
|
||||
private predicate nonAlias(string package, string name) {
|
||||
// `(package, name)` appears to be an alias for multiple things. Treat it as a primary name instead.
|
||||
strictcount(string targetPackage, string targetName |
|
||||
aliasCandidate(package, name, targetPackage, targetName, _)
|
||||
) > 1
|
||||
or
|
||||
// Not all sinks with this name agree on the alias target
|
||||
exists(API::Node sink, string targetPackage, string targetName |
|
||||
aliasCandidate(package, name, targetPackage, targetName, _) and
|
||||
sinkHasPrimaryName(sink, package, name) and
|
||||
not sinkHasPrimarySourceName(sink, targetPackage, targetName)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `(package, name)` is an alias for `(targetPackage, targetName)`,
|
||||
* defined at `aliasDef`.
|
||||
*
|
||||
* Only the last component of an access path is reported as an alias, the prefix always
|
||||
* uses the primary name for that access path. The aliases for the prefix are reported
|
||||
* as separate tuples.
|
||||
*
|
||||
* For example, we might report that `a.b.C` is an alias for `a.b.c`, and that `a.B` is an alias for `a.b`.
|
||||
* By combining the two aliasing facts, we may conclude that `a.B.C` is an alias for `a.b.c`, but this fact is not
|
||||
* reported separately.
|
||||
*/
|
||||
predicate aliasDefinition(
|
||||
string package, string name, string targetPackage, string targetName, API::Node aliasDef
|
||||
) {
|
||||
aliasCandidate(package, name, targetPackage, targetName, aliasDef) and
|
||||
not nonAlias(package, name)
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a `(package, name)` pair to a string of form `(package).name`.
|
||||
*/
|
||||
bindingset[package, name]
|
||||
string renderName(string package, string name) { result = join("(" + package + ")", name) }
|
||||
|
||||
/**
|
||||
* Contains predicates for naming individual modules (i.e. files) inside of a package.
|
||||
*
|
||||
* These names are not necessarily part of a package's public API, and so we only used them
|
||||
* as a fallback when a publicly-accessible access path cannot be found.
|
||||
*/
|
||||
private module InternalModuleNaming {
|
||||
/** Gets the path to `folder` relative to its enclosing non-private `package.json` file. */
|
||||
private string getPackageRelativePathFromFolder(Folder folder) {
|
||||
exists(PackageJson json |
|
||||
json.getFile() = folder.getFile("package.json") and
|
||||
not json.isPrivate() and
|
||||
result = json.getPackageName()
|
||||
)
|
||||
or
|
||||
not exists(folder.getFile("package.json")) and
|
||||
result =
|
||||
getPackageRelativePathFromFolder(folder.getParentContainer()) + "/" + folder.getBaseName()
|
||||
}
|
||||
|
||||
private string getPackageRelativePath(Module mod) {
|
||||
exists(PackageJson json, string relativePath |
|
||||
not json.isPrivate() and
|
||||
json.getExportedModule(relativePath) = mod and
|
||||
if relativePath = "."
|
||||
then result = json.getPackageName()
|
||||
else result = json.getPackageName() + "/" + relativePath.regexpReplaceAll("^\\./", "")
|
||||
)
|
||||
or
|
||||
not mod = any(PackageJson json | not json.isPrivate()).getExportedModule(_) and
|
||||
not mod.isAmbient() and
|
||||
exists(string folderPath |
|
||||
folderPath = getPackageRelativePathFromFolder(mod.getFile().getParentContainer()) and
|
||||
if mod.getName() = "index"
|
||||
then result = folderPath
|
||||
else result = folderPath + "/" + mod.getName()
|
||||
)
|
||||
}
|
||||
|
||||
/** Holds if `(package, name)` should be used to refer to code inside `mod`. */
|
||||
predicate fallbackModuleName(Module mod, string package, string name, int badness) {
|
||||
badness = 50 and
|
||||
package = getPackageRelativePath(mod) and
|
||||
name = ""
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Contains query predicates for emitting debugging information about endpoint naming.
|
||||
*/
|
||||
module Debug {
|
||||
/** Holds if `node` has multiple preferred predecessors. */
|
||||
query predicate ambiguousPreferredPredecessor(API::Node node) {
|
||||
strictcount(API::Node pred, string name, int badness |
|
||||
pred = getPreferredPredecessor(node, name, badness)
|
||||
) > 1
|
||||
}
|
||||
|
||||
/** Holds if the given `node` has multiple primary names. */
|
||||
query string ambiguousSinkName(API::Node node) {
|
||||
strictcount(string package, string name | sinkHasPrimaryName(node, package, name)) > 1 and
|
||||
result =
|
||||
concat(string package, string name |
|
||||
sinkHasPrimaryName(node, package, name)
|
||||
|
|
||||
renderName(package, name), ", "
|
||||
)
|
||||
}
|
||||
|
||||
/** Holds if the given `node` has multiple primary names. */
|
||||
query string ambiguousFunctionName(DataFlow::FunctionNode node) {
|
||||
strictcount(string package, string name | functionHasPrimaryName(node, package, name)) > 1 and
|
||||
result =
|
||||
concat(string package, string name |
|
||||
functionHasPrimaryName(node, package, name)
|
||||
|
|
||||
renderName(package, name), ", "
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -75,7 +75,7 @@ module HeuristicNames {
|
||||
* Gets a regular expression that identifies strings that may indicate the presence of
|
||||
* a certificate.
|
||||
*/
|
||||
string maybeCertificate() { result = "(?is).*(cert)(?!.*(format|name)).*" }
|
||||
string maybeCertificate() { result = "(?is).*(cert)(?!.*(format|name|ification)).*" }
|
||||
|
||||
/**
|
||||
* Gets a regular expression that identifies strings that may indicate the presence
|
||||
|
||||
@@ -1,3 +1,13 @@
|
||||
## 0.8.9
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* The left operand of the `&&` operator no longer propagates data flow by default.
|
||||
|
||||
## 0.8.8
|
||||
|
||||
No user-facing changes.
|
||||
|
||||
## 0.8.7
|
||||
|
||||
### Minor Analysis Improvements
|
||||
|
||||
@@ -46,7 +46,8 @@ class SpuriousArguments extends Expr {
|
||||
|
||||
SpuriousArguments() {
|
||||
this = invk.getArgument(maxArity(invk)).asExpr() and
|
||||
not invk.isIncomplete()
|
||||
not invk.isIncomplete() and
|
||||
not invk.getAstNode() instanceof TaggedTemplateExpr
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
3
javascript/ql/src/change-notes/released/0.8.8.md
Normal file
3
javascript/ql/src/change-notes/released/0.8.8.md
Normal file
@@ -0,0 +1,3 @@
|
||||
## 0.8.8
|
||||
|
||||
No user-facing changes.
|
||||
5
javascript/ql/src/change-notes/released/0.8.9.md
Normal file
5
javascript/ql/src/change-notes/released/0.8.9.md
Normal file
@@ -0,0 +1,5 @@
|
||||
## 0.8.9
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* The left operand of the `&&` operator no longer propagates data flow by default.
|
||||
@@ -1,2 +1,2 @@
|
||||
---
|
||||
lastReleaseVersion: 0.8.7
|
||||
lastReleaseVersion: 0.8.9
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
name: codeql/javascript-queries
|
||||
version: 0.8.8-dev
|
||||
version: 0.8.10-dev
|
||||
groups:
|
||||
- javascript
|
||||
- queries
|
||||
|
||||
@@ -1022,7 +1022,6 @@ flowStep
|
||||
| tst.js:4:9:4:12 | "hi" | tst.js:4:5:4:12 | y |
|
||||
| tst.js:9:2:9:2 | x | tst.js:9:1:9:3 | (x) |
|
||||
| tst.js:10:4:10:4 | y | tst.js:10:1:10:4 | x, y |
|
||||
| tst.js:11:1:11:1 | x | tst.js:11:1:11:6 | x && y |
|
||||
| tst.js:11:1:11:1 | x | tst.js:12:1:12:1 | x |
|
||||
| tst.js:11:1:11:1 | x | tst.js:12:1:12:1 | x |
|
||||
| tst.js:11:6:11:6 | y | tst.js:11:1:11:6 | x && y |
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
testFailures
|
||||
ambiguousPreferredPredecessor
|
||||
| pack2/lib.js:1:1:3:1 | def moduleImport("pack2").getMember("exports").getMember("lib").getMember("LibClass").getInstance() |
|
||||
| pack2/lib.js:8:22:8:34 | def moduleImport("pack2").getMember("exports").getMember("lib").getMember("LibClass").getMember("foo") |
|
||||
| pack2/main.js:1:1:3:1 | def moduleImport("pack2").getMember("exports").getMember("MainClass").getInstance() |
|
||||
ambiguousSinkName
|
||||
ambiguousFunctionName
|
||||
failures
|
||||
@@ -0,0 +1,40 @@
|
||||
import javascript
|
||||
import semmle.javascript.RestrictedLocations
|
||||
import semmle.javascript.Lines
|
||||
import semmle.javascript.endpoints.EndpointNaming as EndpointNaming
|
||||
import testUtilities.InlineExpectationsTest
|
||||
import EndpointNaming::Debug
|
||||
|
||||
private predicate isIgnored(DataFlow::FunctionNode function) {
|
||||
function.getFunction() = any(ConstructorDeclaration decl | decl.isSynthetic()).getBody()
|
||||
}
|
||||
|
||||
module TestConfig implements TestSig {
|
||||
string getARelevantTag() { result = ["name", "alias"] }
|
||||
|
||||
predicate hasActualResult(Location location, string element, string tag, string value) {
|
||||
element = "" and
|
||||
tag = "name" and
|
||||
exists(DataFlow::SourceNode function, string package, string name |
|
||||
EndpointNaming::functionHasPrimaryName(function, package, name) and
|
||||
not isIgnored(function) and
|
||||
location = function.getAstNode().getLocation() and
|
||||
value = EndpointNaming::renderName(package, name)
|
||||
)
|
||||
or
|
||||
element = "" and
|
||||
tag = "alias" and
|
||||
exists(
|
||||
API::Node aliasDef, string primaryPackage, string primaryName, string aliasPackage,
|
||||
string aliasName
|
||||
|
|
||||
EndpointNaming::aliasDefinition(aliasPackage, aliasName, primaryPackage, primaryName, aliasDef) and
|
||||
value =
|
||||
EndpointNaming::renderName(aliasPackage, aliasName) + "==" +
|
||||
EndpointNaming::renderName(primaryPackage, primaryName) and
|
||||
location = aliasDef.asSink().asExpr().getLocation()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
import MakeTest<TestConfig>
|
||||
@@ -0,0 +1,15 @@
|
||||
export class PublicClass {} // $ name=(pack1).PublicClass
|
||||
|
||||
class PrivateClass {}
|
||||
|
||||
export const ExportedConst = class ExportedConstClass {} // $ name=(pack1).ExportedConst
|
||||
|
||||
class ClassWithEscapingInstance {
|
||||
m() {} // $ name=(pack1).ClassWithEscapingInstance.prototype.m
|
||||
}
|
||||
|
||||
export function getEscapingInstance() {
|
||||
return new ClassWithEscapingInstance();
|
||||
} // $ name=(pack1).getEscapingInstance
|
||||
|
||||
export function publicFunction() {} // $ name=(pack1).publicFunction
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"name": "pack1",
|
||||
"main": "./main.js"
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export default class FooClass {} // $ name=(pack10).Foo
|
||||
@@ -0,0 +1,3 @@
|
||||
import { default as Foo } from "./foo";
|
||||
|
||||
export { Foo }
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"name": "pack10",
|
||||
"main": "./index.js"
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
const f1 = {
|
||||
m() {} // $ name=(pack11).C1.publicField.really.long.name.m
|
||||
};
|
||||
|
||||
export class C1 {
|
||||
private static privateField = f1;
|
||||
|
||||
public static publicField = {
|
||||
really: {
|
||||
long: {
|
||||
name: f1
|
||||
}
|
||||
}
|
||||
}
|
||||
} // $ name=(pack11).C1
|
||||
|
||||
const f2 = {
|
||||
m() {} // $ name=(pack11).C2.publicField.really.long.name.m
|
||||
}
|
||||
|
||||
export class C2 {
|
||||
static #privateField = f2;
|
||||
|
||||
static publicField = {
|
||||
really: {
|
||||
long: {
|
||||
name: f2
|
||||
}
|
||||
}
|
||||
}
|
||||
} // $ name=(pack11).C2
|
||||
|
||||
function f3() {} // $ name=(pack11).C3.publicField.really.long.name
|
||||
|
||||
export class C3 {
|
||||
private static privateField = f3;
|
||||
|
||||
public static publicField = {
|
||||
really: {
|
||||
long: {
|
||||
name: f3
|
||||
}
|
||||
}
|
||||
}
|
||||
} // $ name=(pack11).C3
|
||||
|
||||
|
||||
const f4 = {
|
||||
m() {} // $ name=(pack11).C4.really.long.name.m
|
||||
};
|
||||
|
||||
export const C4 = {
|
||||
[Math.random()]: f4,
|
||||
really: {
|
||||
long: {
|
||||
name: f4
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"name": "pack11",
|
||||
"main": "./index.js"
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
function wrap(fn) {
|
||||
return x => fn(x);
|
||||
}
|
||||
|
||||
function f() {}
|
||||
export const f1 = wrap(f); // $ name=(pack12).f1
|
||||
export const f2 = wrap(f); // $ name=(pack12).f2
|
||||
|
||||
function g() {}
|
||||
export const g1 = wrap(g); // $ name=(pack12).g1
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"name": "pack12",
|
||||
"main": "./index.js"
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
class AmbiguousClass {
|
||||
instanceMethod(foo) {} // $ name=(pack2).lib.LibClass.prototype.instanceMethod
|
||||
} // $ name=(pack2).lib.LibClass
|
||||
|
||||
export default AmbiguousClass; // $ alias=(pack2).lib.default==(pack2).lib.LibClass
|
||||
export { AmbiguousClass as LibClass }
|
||||
|
||||
AmbiguousClass.foo = function() {} // $ name=(pack2).lib.LibClass.foo
|
||||
@@ -0,0 +1,9 @@
|
||||
class AmbiguousClass {
|
||||
instanceMethod() {} // $ name=(pack2).MainClass.prototype.instanceMethod
|
||||
} // $ name=(pack2).MainClass
|
||||
|
||||
export default AmbiguousClass; // $ alias=(pack2).default==(pack2).MainClass
|
||||
export { AmbiguousClass as MainClass }
|
||||
|
||||
import * as lib from "./lib";
|
||||
export { lib }
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"name": "pack2",
|
||||
"main": "./main.js"
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export default function(x,y,z) {} // $ name=(pack3).libFunction alias=(pack3).libFunction.default==(pack3).libFunction
|
||||
@@ -0,0 +1,7 @@
|
||||
function ambiguousFunction(x, y, z) {} // $ name=(pack3).namedFunction
|
||||
|
||||
export default ambiguousFunction; // $ alias=(pack3).default==(pack3).namedFunction
|
||||
export { ambiguousFunction as namedFunction };
|
||||
|
||||
import libFunction from "./lib";
|
||||
export { libFunction };
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"name": "pack3",
|
||||
"main": "./main.js"
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export default class C {} // $ name=(pack4)
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"name": "pack4",
|
||||
"main": "./index.js"
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"name": "pack5",
|
||||
"main": "./dist/index.js"
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export default class C {} // $ name=(pack5)
|
||||
@@ -0,0 +1,6 @@
|
||||
class C {
|
||||
instanceMethod() {} // $ name=(pack6).instanceMethod
|
||||
static staticMethod() {} // not accessible
|
||||
}
|
||||
|
||||
export default new C();
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"name": "pack6",
|
||||
"main": "./index.js"
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
export class D {} // $ name=(pack7).D
|
||||
|
||||
// In this case we are forced to include ".default" to avoid ambiguity with class D above.
|
||||
export default {
|
||||
D: class {} // $ name=(pack7).default.D
|
||||
};
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"name": "pack7",
|
||||
"main": "./index.js"
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
class Foo {} // $ name=(pack8).Foo
|
||||
|
||||
module.exports = Foo;
|
||||
module.exports.default = Foo; // $ alias=(pack8).Foo.default==(pack8).Foo
|
||||
module.exports.Foo = Foo; // $ alias=(pack8).Foo.Foo==(pack8).Foo
|
||||
@@ -0,0 +1,5 @@
|
||||
class Main {} // $ name=(pack8)
|
||||
|
||||
Main.Foo = require('./foo');
|
||||
|
||||
module.exports = Main;
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"name": "pack8",
|
||||
"main": "./index.js"
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export class Foo {}
|
||||
@@ -0,0 +1,9 @@
|
||||
// Only the type is exposed. For the time being we do not consider type-only declarations or .d.ts files
|
||||
// when naming classes.
|
||||
export type { Foo } from "./foo";
|
||||
|
||||
import * as foo from "./foo";
|
||||
|
||||
export function expose() {
|
||||
return new foo.Foo(); // expose an instance of Foo but not the class
|
||||
} // $ name=(pack9).expose
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"name": "pack9",
|
||||
"main": "./index.js"
|
||||
}
|
||||
@@ -154,6 +154,7 @@ typeInferenceMismatch
|
||||
| json-stringify.js:2:16:2:23 | source() | json-stringify.js:42:8:42:51 | JSON.st ... urce))) |
|
||||
| json-stringify.js:2:16:2:23 | source() | json-stringify.js:45:8:45:23 | fastJson(source) |
|
||||
| json-stringify.js:3:15:3:22 | source() | json-stringify.js:8:8:8:31 | jsonStr ... (taint) |
|
||||
| logical-and.js:2:17:2:24 | source() | logical-and.js:4:10:4:24 | "safe" && taint |
|
||||
| nested-props.js:4:13:4:20 | source() | nested-props.js:5:10:5:14 | obj.x |
|
||||
| nested-props.js:9:18:9:25 | source() | nested-props.js:10:10:10:16 | obj.x.y |
|
||||
| nested-props.js:35:13:35:20 | source() | nested-props.js:36:10:36:20 | doLoad(obj) |
|
||||
|
||||
@@ -73,6 +73,7 @@
|
||||
| importedReactComponent.jsx:4:40:4:47 | source() | exportedReactComponent.jsx:2:10:2:19 | props.text |
|
||||
| indexOf.js:4:11:4:18 | source() | indexOf.js:9:10:9:10 | x |
|
||||
| indexOf.js:4:11:4:18 | source() | indexOf.js:13:10:13:10 | x |
|
||||
| logical-and.js:2:17:2:24 | source() | logical-and.js:4:10:4:24 | "safe" && taint |
|
||||
| nested-props.js:4:13:4:20 | source() | nested-props.js:5:10:5:14 | obj.x |
|
||||
| nested-props.js:9:18:9:25 | source() | nested-props.js:10:10:10:16 | obj.x.y |
|
||||
| nested-props.js:35:13:35:20 | source() | nested-props.js:36:10:36:20 | doLoad(obj) |
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
function test() {
|
||||
var taint = source();
|
||||
|
||||
sink("safe" && taint); // NOT OK
|
||||
sink(taint && "safe"); // OK
|
||||
}
|
||||
@@ -3,6 +3,7 @@ groups: [javascript, test]
|
||||
dependencies:
|
||||
codeql/javascript-all: ${workspace}
|
||||
codeql/javascript-queries: ${workspace}
|
||||
codeql/util: ${workspace}
|
||||
extractor: javascript
|
||||
tests: .
|
||||
warnOnImplicitThis: true
|
||||
|
||||
@@ -129,4 +129,13 @@ function sum2() {
|
||||
}
|
||||
|
||||
// OK
|
||||
sum2(1, 2, 3);
|
||||
sum2(1, 2, 3);
|
||||
|
||||
const $ = function (x, arr) {
|
||||
console.log(x, arr);
|
||||
};
|
||||
|
||||
// OK
|
||||
async function tagThing(repoUrl, directory) {
|
||||
await $`git clone ${repoUrl} ${directory}`;
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
| highlight.js:19:56:19:61 | [^\\]]+ | Strings starting with '[' and with many repetitions of '.[' can start matching anywhere after the start of the preceeding (\\.\|\\.\\/\|\\/)?(""\|"[^"]+"\|''\|'[^']+'\|\\[\\]\|\\[[^\\]]+\\]\|[^\\s!"#%&'()*+,.\\/;<=>@\\[\\\\\\]^`{\|}~]+)((\\.\|\\/)(""\|"[^"]+"\|''\|'[^']+'\|\\[\\]\|\\[[^\\]]+\\]\|[^\\s!"#%&'()*+,.\\/;<=>@\\[\\\\\\]^`{\|}~]+))* |
|
||||
| highlight.js:22:12:22:82 | ((decltype\\(auto\\)\|(?:[a-zA-Z_]\\w*::)?[a-zA-Z_]\\w*(?:<.*?>)?)[\\*&\\s]+)+ | Strings with many repetitions of 'A\\t' can start matching anywhere after the start of the preceeding .*? |
|
||||
| highlight.js:22:43:22:45 | \\w* | Strings starting with 'A' and with many repetitions of 'A' can start matching anywhere after the start of the preceeding .*? |
|
||||
| highlight.js:22:66:22:68 | .*? | Strings starting with 'A<' and with many repetitions of 'A<' can start matching anywhere after the start of the preceeding \\w* |
|
||||
| highlight.js:22:66:22:68 | .*? | Strings starting with 'A<' and with many repetitions of 'a<' can start matching anywhere after the start of the preceeding \\w* |
|
||||
| highlight.js:22:73:22:80 | [\\*&\\s]+ | Strings starting with 'A' and with many repetitions of '\\tA\\t' can start matching anywhere after the start of the preceeding .*? |
|
||||
| highlight.js:23:13:23:82 | ((decltype\\(auto\\)\|([a-zA-Z_]\\w*::)?[a-zA-Z_]\\w*(<[^<>]+>)?)[\\*&\\s]+)+ | Strings with many repetitions of 'A\\t' can start matching anywhere after the start of the preceeding ((decltype\\(auto\\)\|([a-zA-Z_]\\w*::)?[a-zA-Z_]\\w*(<[^<>]+>)?)[\\*&\\s]+)+([a-zA-Z_]\\w*::)?[a-zA-Z]\\w*\\s*\\( |
|
||||
| highlight.js:23:42:23:44 | \\w* | Strings starting with 'A' and with many repetitions of 'A' can start matching anywhere after the start of the preceeding ((decltype\\(auto\\)\|([a-zA-Z_]\\w*::)?[a-zA-Z_]\\w*(<[^<>]+>)?)[\\*&\\s]+)+([a-zA-Z_]\\w*::)?[a-zA-Z]\\w*\\s*\\( |
|
||||
@@ -279,7 +279,7 @@
|
||||
| regexplib/misc.js:117:25:117:26 | .+ | Strings starting with '(a}' and with many repetitions of 'a)' can start matching anywhere after the start of the preceeding .+ |
|
||||
| regexplib/misc.js:119:20:119:22 | \\w+ | Strings with many repetitions of '0' can start matching anywhere after the start of the preceeding (NOT)?(\\s*\\(*)\\s*(\\w+)\\s*(=\|<>\|<\|>\|LIKE\|IN)\\s*(\\(([^\\)]*)\\)\|'([^']*)'\|(-?\\d*\\.?\\d+))(\\s*\\)*\\s*)(AND\|OR)? |
|
||||
| regexplib/misc.js:119:52:119:57 | [^\\)]* | Strings starting with '0=(' and with many repetitions of '0<((' can start matching anywhere after the start of the preceeding (NOT)?(\\s*\\(*)\\s*(\\w+)\\s*(=\|<>\|<\|>\|LIKE\|IN)\\s*(\\(([^\\)]*)\\)\|'([^']*)'\|(-?\\d*\\.?\\d+))(\\s*\\)*\\s*)(AND\|OR)? |
|
||||
| regexplib/misc.js:123:36:123:38 | .*? | Strings starting with '?se[A' and with many repetitions of '?se[Aa' can start matching anywhere after the start of the preceeding (?s)(?:\\e\\[(?:(\\d+);?)*([A-Za-z])(.*?))(?=\\e\\[\|\\z) |
|
||||
| regexplib/misc.js:123:36:123:38 | .*? | Strings starting with '?se[A' and with many repetitions of '?se[aa' can start matching anywhere after the start of the preceeding (?s)(?:\\e\\[(?:(\\d+);?)*([A-Za-z])(.*?))(?=\\e\\[\|\\z) |
|
||||
| regexplib/misc.js:126:15:126:20 | [a-z]+ | Strings starting with 'a' and with many repetitions of 'aa' can start matching anywhere after the start of the preceeding [a-z]+ |
|
||||
| regexplib/misc.js:141:15:141:19 | [^;]+ | Strings starting with '{\\\\f\\\\' and with many repetitions of '{\\\\f\\\\:' can start matching anywhere after the start of the preceeding (\\{\\\\f\\d*)\\\\([^;]+;) |
|
||||
| regexplib/misc.js:144:52:144:70 | [a-z0-9\\/\\.\\?\\=\\&]* | Strings starting with '".htm' and with many repetitions of '.asp' can start matching anywhere after the start of the preceeding [a-z0-9\\/\\.\\?\\=\\&]* |
|
||||
@@ -334,7 +334,7 @@
|
||||
| regexplib/strings.js:54:20:54:22 | \\w+ | Strings with many repetitions of '0' can start matching anywhere after the start of the preceeding (NOT)?(\\s*\\(*)\\s*(\\w+)\\s*(=\|<>\|<\|>\|LIKE\|IN)\\s*(\\(([^\\)]*)\\)\|'([^']*)'\|(-?\\d*\\.?\\d+))(\\s*\\)*\\s*)(AND\|OR)? |
|
||||
| regexplib/strings.js:54:52:54:57 | [^\\)]* | Strings starting with '0=(' and with many repetitions of '0<((' can start matching anywhere after the start of the preceeding (NOT)?(\\s*\\(*)\\s*(\\w+)\\s*(=\|<>\|<\|>\|LIKE\|IN)\\s*(\\(([^\\)]*)\\)\|'([^']*)'\|(-?\\d*\\.?\\d+))(\\s*\\)*\\s*)(AND\|OR)? |
|
||||
| regexplib/strings.js:56:52:56:53 | .+ | Strings starting with 'AUX.' and with many repetitions of '.' can start matching anywhere after the start of the preceeding .* |
|
||||
| regexplib/strings.js:57:36:57:38 | .*? | Strings starting with '?se[A' and with many repetitions of '?se[Aa' can start matching anywhere after the start of the preceeding (?s)(?:\\e\\[(?:(\\d+);?)*([A-Za-z])(.*?))(?=\\e\\[\|\\z) |
|
||||
| regexplib/strings.js:57:36:57:38 | .*? | Strings starting with '?se[A' and with many repetitions of '?se[aa' can start matching anywhere after the start of the preceeding (?s)(?:\\e\\[(?:(\\d+);?)*([A-Za-z])(.*?))(?=\\e\\[\|\\z) |
|
||||
| regexplib/strings.js:64:3:64:5 | \\w+ | Strings with many repetitions of '0' can start matching anywhere after the start of the preceeding (\\w+)\\s+\\1 |
|
||||
| regexplib/strings.js:70:6:70:17 | [a-zA-Z,\\s]+ | Strings with many repetitions of '\\t' can start matching anywhere after the start of the preceeding \\s* |
|
||||
| regexplib/strings.js:70:18:70:20 | \\s* | Strings starting with '\\t' and with many repetitions of '\\t' can start matching anywhere after the start of the preceeding \\s* |
|
||||
@@ -345,7 +345,7 @@
|
||||
| regexplib/strings.js:74:2:74:3 | .* | Strings with many repetitions of 'a' can start matching anywhere after the start of the preceeding .*[Pp]re[Ss\\$]cr[iI1]pt.* |
|
||||
| regexplib/strings.js:75:2:75:3 | .* | Strings with many repetitions of 'a' can start matching anywhere after the start of the preceeding .*[Vv][Ii1]agr.* |
|
||||
| regexplib/strings.js:76:2:76:3 | .* | Strings with many repetitions of 'a' can start matching anywhere after the start of the preceeding .*[Oo0][Ee][Mm].* |
|
||||
| regexplib/strings.js:81:36:81:38 | .*? | Strings starting with '?se[A' and with many repetitions of '?se[Aa' can start matching anywhere after the start of the preceeding (?s)(?:\\e\\[(?:(\\d+);?)*([A-Za-z])(.*?))(?=\\e\\[\|\\z) |
|
||||
| regexplib/strings.js:81:36:81:38 | .*? | Strings starting with '?se[A' and with many repetitions of '?se[aa' can start matching anywhere after the start of the preceeding (?s)(?:\\e\\[(?:(\\d+);?)*([A-Za-z])(.*?))(?=\\e\\[\|\\z) |
|
||||
| regexplib/strings.js:82:20:82:22 | \\w+ | Strings with many repetitions of '0' can start matching anywhere after the start of the preceeding (NOT)?(\\s*\\(*)\\s*(\\w+)\\s*(=\|<>\|<\|>\|LIKE\|IN)\\s*(\\(([^\\)]*)\\)\|'([^']*)'\|(-?\\d*\\.?\\d+))(\\s*\\)*\\s*)(AND\|OR)? |
|
||||
| regexplib/strings.js:82:52:82:57 | [^\\)]* | Strings starting with '0=(' and with many repetitions of '0<((' can start matching anywhere after the start of the preceeding (NOT)?(\\s*\\(*)\\s*(\\w+)\\s*(=\|<>\|<\|>\|LIKE\|IN)\\s*(\\(([^\\)]*)\\)\|'([^']*)'\|(-?\\d*\\.?\\d+))(\\s*\\)*\\s*)(AND\|OR)? |
|
||||
| regexplib/strings.js:88:3:88:12 | [^\\.\\?\\!]* | Strings with many repetitions of ' ' can start matching anywhere after the start of the preceeding ([^\\.\\?\\!]*)[\\.\\?\\!] |
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
| UselessConditional.js:94:16:94:16 | x | This use of variable 'x' always evaluates to false. |
|
||||
| UselessConditional.js:100:13:100:24 | true && true | This expression always evaluates to true. |
|
||||
| UselessConditional.js:101:18:101:18 | x | This use of variable 'x' always evaluates to false. |
|
||||
| UselessConditional.js:102:13:102:20 | y && (x) | This expression always evaluates to false. |
|
||||
| UselessConditional.js:102:19:102:19 | x | This use of variable 'x' always evaluates to false. |
|
||||
| UselessConditional.js:103:23:103:23 | x | This use of variable 'x' always evaluates to false. |
|
||||
| UselessConditional.js:109:15:109:16 | {} | This expression always evaluates to true. |
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
/**
|
||||
* Inline expectation tests for JS.
|
||||
* See `shared/util/codeql/util/test/InlineExpectationsTest.qll`
|
||||
*/
|
||||
|
||||
private import codeql.util.test.InlineExpectationsTest
|
||||
private import internal.InlineExpectationsTestImpl
|
||||
import Make<Impl>
|
||||
@@ -0,0 +1,12 @@
|
||||
private import javascript as JS
|
||||
private import codeql.util.test.InlineExpectationsTest
|
||||
|
||||
module Impl implements InlineExpectationsTestSig {
|
||||
private import javascript
|
||||
|
||||
class ExpectationComment extends LineComment {
|
||||
string getContents() { result = this.getText() }
|
||||
}
|
||||
|
||||
class Location = JS::Location;
|
||||
}
|
||||
Reference in New Issue
Block a user