mirror of
https://github.com/github/codeql.git
synced 2026-07-20 02:38:23 +02:00
Merge remote-tracking branch 'upstream/master' into deferredModel
This commit is contained in:
@@ -1,13 +0,0 @@
|
||||
+ semmlecode-javascript-queries/Declarations/BuiltinRedefined.ql: /Maintainability/Declarations
|
||||
+ semmlecode-javascript-queries/Declarations/InefficientMethodDefinition.ql: /Maintainability/Declarations
|
||||
+ semmlecode-javascript-queries/Expressions/BadParityCheck.ql: /Correctness/Expressions
|
||||
+ semmlecode-javascript-queries/Expressions/HapaxLegomenon.ql: /Correctness/Expressions
|
||||
+ semmlecode-javascript-queries/JSDoc/UnknownTagType.ql: /Readability/JSDoc
|
||||
+ semmlecode-javascript-queries/JSLint/InvalidJSLintDirective.ql: /Frameworks/JSLint
|
||||
+ semmlecode-javascript-queries/JSLint/MalformedJSLintDirective.ql: /Frameworks/JSLint
|
||||
+ semmlecode-javascript-queries/LanguageFeatures/HTMLComments.ql: /Maintainability/Language Features
|
||||
+ semmlecode-javascript-queries/LanguageFeatures/MultilineStringLiteral.ql: /Readability/Language Features
|
||||
+ semmlecode-javascript-queries/LanguageFeatures/ReservedWords.ql: /Maintainability/Language Features
|
||||
+ semmlecode-javascript-queries/LanguageFeatures/TrailingComma.ql: /Correctness/Language Features
|
||||
+ semmlecode-javascript-queries/LanguageFeatures/OctalLiteral.ql: /Maintainability/Language Features
|
||||
+ semmlecode-javascript-queries/StandardLibrary/ParseIntRadix.ql: /Maintainability/Standard Library
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
+ semmlecode-javascript-queries/Declarations/UnusedParameter.ql: /Maintainability/Declarations
|
||||
+ semmlecode-javascript-queries/Declarations/UnusedProperty.ql: /Maintainability/Declarations
|
||||
+ semmlecode-javascript-queries/Declarations/UnusedVariable.ql: /Maintainability/Declarations
|
||||
+ semmlecode-javascript-queries/Declarations/UnreachableMethodOverloads.ql: /Maintainability/Declarations
|
||||
+ semmlecode-javascript-queries/Expressions/UnneededDefensiveProgramming.ql: /Maintainability/Expressions
|
||||
+ semmlecode-javascript-queries/LanguageFeatures/ArgumentsCallerCallee.ql: /Maintainability/Language Features
|
||||
+ semmlecode-javascript-queries/LanguageFeatures/ConditionalComments.ql: /Maintainability/Language Features
|
||||
|
||||
@@ -4,10 +4,12 @@
|
||||
+ semmlecode-javascript-queries/Security/CWE-020/IncompleteUrlSubstringSanitization.ql: /Security/CWE/CWE-020
|
||||
+ semmlecode-javascript-queries/Security/CWE-020/IncorrectSuffixCheck.ql: /Security/CWE/CWE-020
|
||||
+ semmlecode-javascript-queries/Security/CWE-020/MissingRegExpAnchor.ql: /Security/CWE/CWE-020
|
||||
+ semmlecode-javascript-queries/Security/CWE-020/UselessRegExpCharacterEscape.ql: /Security/CWE/CWE-020
|
||||
+ semmlecode-javascript-queries/Security/CWE-022/TaintedPath.ql: /Security/CWE/CWE-022
|
||||
+ semmlecode-javascript-queries/Security/CWE-022/ZipSlip.ql: /Security/CWE/CWE-022
|
||||
+ semmlecode-javascript-queries/Security/CWE-078/CommandInjection.ql: /Security/CWE/CWE-078
|
||||
+ semmlecode-javascript-queries/Security/CWE-078/IndirectCommandInjection.ql: /Security/CWE/CWE-078
|
||||
+ semmlecode-javascript-queries/Security/CWE-078/ShellCommandInjectionFromEnvironment.ql: /Security/CWE/CWE-078
|
||||
+ semmlecode-javascript-queries/Security/CWE-079/ReflectedXss.ql: /Security/CWE/CWE-079
|
||||
+ semmlecode-javascript-queries/Security/CWE-079/StoredXss.ql: /Security/CWE/CWE-079
|
||||
+ semmlecode-javascript-queries/Security/CWE-079/Xss.ql: /Security/CWE/CWE-079
|
||||
|
||||
@@ -2,19 +2,16 @@
|
||||
"name": "typescript-parser-wrapper",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"typescript": "3.5.1"
|
||||
"typescript": "^3.7.2"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "tsc --project tsconfig.json && rollup -c",
|
||||
"build": "tsc --project tsconfig.json",
|
||||
"check": "tsc --noEmit --project . && tslint --project .",
|
||||
"lint": "tslint --project .",
|
||||
"lint-fix": "tslint --project . --fix"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^9.3.0",
|
||||
"rollup": "^0.66.6",
|
||||
"rollup-plugin-commonjs": "^9.2.0",
|
||||
"rollup-plugin-node-resolve": "^3.4.0",
|
||||
"@types/node": "12.7.11",
|
||||
"tslint": "^5.9.1"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
import resolve from "rollup-plugin-node-resolve";
|
||||
import commonjs from "rollup-plugin-commonjs";
|
||||
import * as fs from "fs";
|
||||
import * as pathlib from "path";
|
||||
|
||||
const copyTypeScriptFiles = (options) => ({
|
||||
generateBundle() {
|
||||
if (!fs.existsSync('dist')) {
|
||||
fs.mkdirSync('dist');
|
||||
}
|
||||
let typescriptDir = pathlib.dirname(require.resolve("typescript"));
|
||||
for (let file of fs.readdirSync(typescriptDir)) {
|
||||
// Don't include bundles like `tsc.js`.
|
||||
if (file.endsWith(".js")) continue;
|
||||
|
||||
// Only include library typings, not `typescript.d.ts` and friends
|
||||
if (file.endsWith(".d.ts") && !file.startsWith("lib")) continue;
|
||||
|
||||
let filePath = `${typescriptDir}/${file}`;
|
||||
|
||||
// Skip directories. They contain locale translations and are not needed.
|
||||
if (fs.statSync(filePath).isDirectory()) continue;
|
||||
|
||||
fs.copyFileSync(filePath, `dist/${file}`);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export default {
|
||||
input: 'build/main.js',
|
||||
output: {
|
||||
file: 'dist/main.js',
|
||||
format: 'cjs'
|
||||
},
|
||||
plugins: [
|
||||
resolve(), // Resolve paths using Node.js rules
|
||||
commonjs({ // Make rollup understand `require`
|
||||
ignore: [ 'source-map-support' ] // Optional required - do not hoist to top-level.
|
||||
}),
|
||||
copyTypeScriptFiles(), // Copy files needed by TypeScript compiler
|
||||
],
|
||||
// List Node.js modules to avoid warnings about unresolved modules.
|
||||
external: [
|
||||
'buffer',
|
||||
'crypto',
|
||||
'fs',
|
||||
'os',
|
||||
'path',
|
||||
'readline',
|
||||
],
|
||||
}
|
||||
@@ -126,7 +126,7 @@ function checkCycle(root: any) {
|
||||
function isBlacklistedProperty(k: string) {
|
||||
return k === "parent" || k === "pos" || k === "end"
|
||||
|| k === "symbol" || k === "localSymbol"
|
||||
|| k === "flowNode" || k === "returnFlowNode"
|
||||
|| k === "flowNode" || k === "returnFlowNode" || k === "endFlowNode"
|
||||
|| k === "nextContainer" || k === "locals"
|
||||
|| k === "bindDiagnostics" || k === "bindSuggestionDiagnostics";
|
||||
}
|
||||
@@ -162,10 +162,10 @@ function prepareNextFile() {
|
||||
}
|
||||
}
|
||||
|
||||
function handleParseCommand(command: ParseCommand) {
|
||||
function handleParseCommand(command: ParseCommand, checkPending = true) {
|
||||
let filename = command.filename;
|
||||
let expectedFilename = state.pendingFiles[state.pendingFileIndex];
|
||||
if (expectedFilename !== filename) {
|
||||
if (expectedFilename !== filename && checkPending) {
|
||||
throw new Error("File requested out of order. Expected '" + expectedFilename + "' but got '" + filename + "'");
|
||||
}
|
||||
++state.pendingFileIndex;
|
||||
@@ -515,13 +515,13 @@ if (process.argv.length > 2) {
|
||||
handleParseCommand({
|
||||
command: "parse",
|
||||
filename: sf.fileName,
|
||||
});
|
||||
}, false);
|
||||
}
|
||||
} else if (pathlib.extname(argument) === ".ts" || pathlib.extname(argument) === ".tsx") {
|
||||
handleParseCommand({
|
||||
command: "parse",
|
||||
filename: argument,
|
||||
});
|
||||
}, false);
|
||||
} else {
|
||||
console.error("Unrecognized file or flag: " + argument);
|
||||
}
|
||||
|
||||
@@ -597,7 +597,7 @@ export class TypeTable {
|
||||
let tupleType = tupleReference.target;
|
||||
let minLength = tupleType.minLength != null
|
||||
? tupleType.minLength
|
||||
: tupleReference.typeArguments.length;
|
||||
: this.typeChecker.getTypeArguments(tupleReference).length;
|
||||
let hasRestElement = tupleType.hasRestElement ? 't' : 'f';
|
||||
let prefix = `tuple;${minLength};${hasRestElement}`;
|
||||
return this.makeTypeStringVectorFromTypeReferenceArguments(prefix, type);
|
||||
@@ -714,11 +714,12 @@ export class TypeTable {
|
||||
// There can be an extra type argument at the end, denoting an explicit 'this' type argument.
|
||||
// We discard the extra argument in our model.
|
||||
let target = type.target;
|
||||
if (type.typeArguments == null) return tag;
|
||||
let typeArguments = this.typeChecker.getTypeArguments(type);
|
||||
if (typeArguments == null) return tag;
|
||||
if (target.typeParameters != null) {
|
||||
return this.makeTypeStringVector(tag, type.typeArguments, target.typeParameters.length);
|
||||
return this.makeTypeStringVector(tag, typeArguments, target.typeParameters.length);
|
||||
} else {
|
||||
return this.makeTypeStringVector(tag, type.typeArguments);
|
||||
return this.makeTypeStringVector(tag, typeArguments);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -992,7 +993,7 @@ export class TypeTable {
|
||||
* `T` is the type parameter declared on the `Promise` interface.
|
||||
*/
|
||||
private getSelfType(type: ts.Type): ts.TypeReference {
|
||||
if (isTypeReference(type) && type.typeArguments != null && type.typeArguments.length > 0) {
|
||||
if (isTypeReference(type) && this.typeChecker.getTypeArguments(type).length > 0) {
|
||||
return type.target;
|
||||
}
|
||||
return null;
|
||||
@@ -1181,8 +1182,9 @@ export class TypeTable {
|
||||
if (isTypeReference(type)) {
|
||||
// Note that this case also handles tuple types, since a tuple type is represented as
|
||||
// a reference to a synthetic generic interface.
|
||||
if (type.typeArguments != null) {
|
||||
type.typeArguments.forEach(callback);
|
||||
let typeArguments = this.typeChecker.getTypeArguments(type);
|
||||
if (typeArguments != null) {
|
||||
typeArguments.forEach(callback);
|
||||
}
|
||||
} else if (type.flags & ts.TypeFlags.UnionOrIntersection) {
|
||||
(type as ts.UnionOrIntersectionType).types.forEach(callback);
|
||||
|
||||
@@ -3,6 +3,5 @@ let overridePath = process.env['SEMMLE_TYPESCRIPT_HOME'];
|
||||
if (overridePath != null) {
|
||||
module.exports = require(overridePath);
|
||||
} else {
|
||||
// Unlike the above, this require() call will be rewritten by rollup.
|
||||
module.exports = require('typescript');
|
||||
}
|
||||
|
||||
@@ -2,17 +2,9 @@
|
||||
# yarn lockfile v1
|
||||
|
||||
|
||||
"@types/estree@0.0.39":
|
||||
version "0.0.39"
|
||||
resolved "estree-0.0.39.tgz#e177e699ee1b8c22d23174caaa7422644389509f"
|
||||
|
||||
"@types/node@*":
|
||||
version "10.12.0"
|
||||
resolved "node-10.12.0.tgz#ea6dcbddbc5b584c83f06c60e82736d8fbb0c235"
|
||||
|
||||
"@types/node@^9.3.0":
|
||||
version "9.3.0"
|
||||
resolved "@types-node-9.3.0.tgz#3a129cda7c4e5df2409702626892cb4b96546dd5"
|
||||
"@types/node@12.7.11":
|
||||
version "12.7.11"
|
||||
resolved "node-12.7.11.tgz#be879b52031cfb5d295b047f5462d8ef1a716446"
|
||||
|
||||
ansi-regex@^2.0.0:
|
||||
version "2.1.1"
|
||||
@@ -34,20 +26,6 @@ argparse@^1.0.7:
|
||||
dependencies:
|
||||
sprintf-js "~1.0.2"
|
||||
|
||||
arr-diff@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "arr-diff-2.0.0.tgz#8f3b827f955a8bd669697e4a4256ac3ceae356cf"
|
||||
dependencies:
|
||||
arr-flatten "^1.0.1"
|
||||
|
||||
arr-flatten@^1.0.1:
|
||||
version "1.1.0"
|
||||
resolved "arr-flatten-1.1.0.tgz#36048bbff4e7b47e136644316c99669ea5ae91f1"
|
||||
|
||||
array-unique@^0.2.1:
|
||||
version "0.2.1"
|
||||
resolved "array-unique-0.2.1.tgz#a1d97ccafcbc2625cc70fadceb36a50c58b01a53"
|
||||
|
||||
babel-code-frame@^6.22.0:
|
||||
version "6.26.0"
|
||||
resolved "babel-code-frame-6.26.0.tgz#63fd43f7dc1e3bb7ce35947db8fe369a3f58c74b"
|
||||
@@ -67,22 +45,10 @@ brace-expansion@^1.1.7:
|
||||
balanced-match "^1.0.0"
|
||||
concat-map "0.0.1"
|
||||
|
||||
braces@^1.8.2:
|
||||
version "1.8.5"
|
||||
resolved "ces-1.8.5.tgz#ba77962e12dff969d6b76711e914b737857bf6a7"
|
||||
dependencies:
|
||||
expand-range "^1.8.1"
|
||||
preserve "^0.2.0"
|
||||
repeat-element "^1.1.2"
|
||||
|
||||
builtin-modules@^1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "builtin-modules-1.1.1.tgz#270f076c5a72c02f5b65a47df94c5fe3a278892f"
|
||||
|
||||
builtin-modules@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "builtin-modules-2.0.0.tgz#60b7ef5ae6546bd7deefa74b08b62a43a232648e"
|
||||
|
||||
chalk@^1.1.3:
|
||||
version "1.1.3"
|
||||
resolved "chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98"
|
||||
@@ -131,73 +97,14 @@ esprima@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "esprima-4.0.0.tgz#4499eddcd1110e0b218bacf2fa7f7f59f55ca804"
|
||||
|
||||
estree-walker@^0.5.2:
|
||||
version "0.5.2"
|
||||
resolved "estree-walker-0.5.2.tgz#d3850be7529c9580d815600b53126515e146dd39"
|
||||
|
||||
esutils@^2.0.2:
|
||||
version "2.0.2"
|
||||
resolved "esutils-2.0.2.tgz#0abf4f1caa5bcb1f7a9d8acc6dea4faaa04bac9b"
|
||||
|
||||
expand-brackets@^0.1.4:
|
||||
version "0.1.5"
|
||||
resolved "expand-brackets-0.1.5.tgz#df07284e342a807cd733ac5af72411e581d1177b"
|
||||
dependencies:
|
||||
is-posix-bracket "^0.1.0"
|
||||
|
||||
expand-range@^1.8.1:
|
||||
version "1.8.2"
|
||||
resolved "expand-range-1.8.2.tgz#a299effd335fe2721ebae8e257ec79644fc85337"
|
||||
dependencies:
|
||||
fill-range "^2.1.0"
|
||||
|
||||
extglob@^0.3.1:
|
||||
version "0.3.2"
|
||||
resolved "glob-0.3.2.tgz#2e18ff3d2f49ab2765cec9023f011daa8d8349a1"
|
||||
dependencies:
|
||||
is-extglob "^1.0.0"
|
||||
|
||||
filename-regex@^2.0.0:
|
||||
version "2.0.1"
|
||||
resolved "filename-regex-2.0.1.tgz#c1c4b9bee3e09725ddb106b75c1e301fe2f18b26"
|
||||
|
||||
fill-range@^2.1.0:
|
||||
version "2.2.4"
|
||||
resolved "fill-range-2.2.4.tgz#eb1e773abb056dcd8df2bfdf6af59b8b3a936565"
|
||||
dependencies:
|
||||
is-number "^2.1.0"
|
||||
isobject "^2.0.0"
|
||||
randomatic "^3.0.0"
|
||||
repeat-element "^1.1.2"
|
||||
repeat-string "^1.5.2"
|
||||
|
||||
for-in@^1.0.1:
|
||||
version "1.0.2"
|
||||
resolved "for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80"
|
||||
|
||||
for-own@^0.1.4:
|
||||
version "0.1.5"
|
||||
resolved "for-own-0.1.5.tgz#5265c681a4f294dabbf17c9509b6763aa84510ce"
|
||||
dependencies:
|
||||
for-in "^1.0.1"
|
||||
|
||||
fs.realpath@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
|
||||
|
||||
glob-base@^0.3.0:
|
||||
version "0.3.0"
|
||||
resolved "glob-base-0.3.0.tgz#dbb164f6221b1c0b1ccf82aea328b497df0ea3c4"
|
||||
dependencies:
|
||||
glob-parent "^2.0.0"
|
||||
is-glob "^2.0.0"
|
||||
|
||||
glob-parent@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "glob-parent-2.0.0.tgz#81383d72db054fcccf5336daa902f182f6edbb28"
|
||||
dependencies:
|
||||
is-glob "^2.0.0"
|
||||
|
||||
glob@^7.1.1:
|
||||
version "7.1.2"
|
||||
resolved "glob-7.1.2.tgz#c19c9df9a028702d678612384a6552404c636d15"
|
||||
@@ -230,66 +137,6 @@ inherits@2:
|
||||
version "2.0.3"
|
||||
resolved "inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de"
|
||||
|
||||
is-buffer@^1.1.5:
|
||||
version "1.1.6"
|
||||
resolved "is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be"
|
||||
|
||||
is-dotfile@^1.0.0:
|
||||
version "1.0.3"
|
||||
resolved "is-dotfile-1.0.3.tgz#a6a2f32ffd2dfb04f5ca25ecd0f6b83cf798a1e1"
|
||||
|
||||
is-equal-shallow@^0.1.3:
|
||||
version "0.1.3"
|
||||
resolved "allow/-/is-equal-shallow-0.1.3.tgz#2238098fc221de0bcfa5d9eac4c45d638aa1c534"
|
||||
dependencies:
|
||||
is-primitive "^2.0.0"
|
||||
|
||||
is-extendable@^0.1.1:
|
||||
version "0.1.1"
|
||||
resolved "is-extendable-0.1.1.tgz#62b110e289a471418e3ec36a617d472e301dfc89"
|
||||
|
||||
is-extglob@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "is-extglob-1.0.0.tgz#ac468177c4943405a092fc8f29760c6ffc6206c0"
|
||||
|
||||
is-glob@^2.0.0, is-glob@^2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "is-glob-2.0.1.tgz#d096f926a3ded5600f3fdfd91198cb0888c2d863"
|
||||
dependencies:
|
||||
is-extglob "^1.0.0"
|
||||
|
||||
is-module@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "is-module-1.0.0.tgz#3258fb69f78c14d5b815d664336b4cffb6441591"
|
||||
|
||||
is-number@^2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "is-number-2.1.0.tgz#01fcbbb393463a548f2f466cce16dece49db908f"
|
||||
dependencies:
|
||||
kind-of "^3.0.2"
|
||||
|
||||
is-number@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "is-number-4.0.0.tgz#0026e37f5454d73e356dfe6564699867c6a7f0ff"
|
||||
|
||||
is-posix-bracket@^0.1.0:
|
||||
version "0.1.1"
|
||||
resolved "acket/-/is-posix-bracket-0.1.1.tgz#3334dc79774368e92f016e6fbc0a88f5cd6e6bc4"
|
||||
|
||||
is-primitive@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "is-primitive-2.0.0.tgz#207bab91638499c07b2adf240a41a87210034575"
|
||||
|
||||
isarray@1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "rray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
|
||||
|
||||
isobject@^2.0.0:
|
||||
version "2.1.0"
|
||||
resolved "bject-2.1.0.tgz#f065561096a3f1da2ef46272f815c840d87e0c89"
|
||||
dependencies:
|
||||
isarray "1.0.0"
|
||||
|
||||
js-tokens@^3.0.2:
|
||||
version "3.0.2"
|
||||
resolved "js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b"
|
||||
@@ -301,78 +148,18 @@ js-yaml@^3.7.0:
|
||||
argparse "^1.0.7"
|
||||
esprima "^4.0.0"
|
||||
|
||||
kind-of@^3.0.2:
|
||||
version "3.2.2"
|
||||
resolved "kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64"
|
||||
dependencies:
|
||||
is-buffer "^1.1.5"
|
||||
|
||||
kind-of@^6.0.0:
|
||||
version "6.0.2"
|
||||
resolved "kind-of-6.0.2.tgz#01146b36a6218e64e58f3a8d66de5d7fc6f6d051"
|
||||
|
||||
magic-string@^0.25.1:
|
||||
version "0.25.1"
|
||||
resolved "magic-string-0.25.1.tgz#b1c248b399cd7485da0fe7385c2fc7011843266e"
|
||||
dependencies:
|
||||
sourcemap-codec "^1.4.1"
|
||||
|
||||
math-random@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "math-random-1.0.1.tgz#8b3aac588b8a66e4975e3cdea67f7bb329601fac"
|
||||
|
||||
micromatch@^2.3.11:
|
||||
version "2.3.11"
|
||||
resolved "romatch-2.3.11.tgz#86677c97d1720b363431d04d0d15293bd38c1565"
|
||||
dependencies:
|
||||
arr-diff "^2.0.0"
|
||||
array-unique "^0.2.1"
|
||||
braces "^1.8.2"
|
||||
expand-brackets "^0.1.4"
|
||||
extglob "^0.3.1"
|
||||
filename-regex "^2.0.0"
|
||||
is-extglob "^1.0.0"
|
||||
is-glob "^2.0.1"
|
||||
kind-of "^3.0.2"
|
||||
normalize-path "^2.0.1"
|
||||
object.omit "^2.0.0"
|
||||
parse-glob "^3.0.4"
|
||||
regex-cache "^0.4.2"
|
||||
|
||||
minimatch@^3.0.4:
|
||||
version "3.0.4"
|
||||
resolved "minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083"
|
||||
dependencies:
|
||||
brace-expansion "^1.1.7"
|
||||
|
||||
normalize-path@^2.0.1:
|
||||
version "2.1.1"
|
||||
resolved "normalize-path-2.1.1.tgz#1ab28b556e198363a8c1a6f7e6fa20137fe6aed9"
|
||||
dependencies:
|
||||
remove-trailing-separator "^1.0.1"
|
||||
|
||||
object.omit@^2.0.0:
|
||||
version "2.0.1"
|
||||
resolved "object.omit-2.0.1.tgz#1a9c744829f39dbb858c76ca3579ae2a54ebd1fa"
|
||||
dependencies:
|
||||
for-own "^0.1.4"
|
||||
is-extendable "^0.1.1"
|
||||
|
||||
once@^1.3.0:
|
||||
version "1.4.0"
|
||||
resolved "once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
|
||||
dependencies:
|
||||
wrappy "1"
|
||||
|
||||
parse-glob@^3.0.4:
|
||||
version "3.0.4"
|
||||
resolved "parse-glob-3.0.4.tgz#b2c376cfb11f35513badd173ef0bb6e3a388391c"
|
||||
dependencies:
|
||||
glob-base "^0.3.0"
|
||||
is-dotfile "^1.0.0"
|
||||
is-extglob "^1.0.0"
|
||||
is-glob "^2.0.0"
|
||||
|
||||
path-is-absolute@^1.0.0:
|
||||
version "1.0.1"
|
||||
resolved "path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f"
|
||||
@@ -381,87 +168,16 @@ path-parse@^1.0.5:
|
||||
version "1.0.5"
|
||||
resolved "path-parse-1.0.5.tgz#3c1adf871ea9cd6c9431b6ea2bd74a0ff055c4c1"
|
||||
|
||||
preserve@^0.2.0:
|
||||
version "0.2.0"
|
||||
resolved "serve-0.2.0.tgz#815ed1f6ebc65926f865b310c0713bcb3315ce4b"
|
||||
|
||||
randomatic@^3.0.0:
|
||||
version "3.1.1"
|
||||
resolved "domatic-3.1.1.tgz#b776efc59375984e36c537b2f51a1f0aff0da1ed"
|
||||
dependencies:
|
||||
is-number "^4.0.0"
|
||||
kind-of "^6.0.0"
|
||||
math-random "^1.0.1"
|
||||
|
||||
regex-cache@^0.4.2:
|
||||
version "0.4.4"
|
||||
resolved "regex-cache-0.4.4.tgz#75bdc58a2a1496cec48a12835bc54c8d562336dd"
|
||||
dependencies:
|
||||
is-equal-shallow "^0.1.3"
|
||||
|
||||
remove-trailing-separator@^1.0.1:
|
||||
version "1.1.0"
|
||||
resolved "parator/-/remove-trailing-separator-1.1.0.tgz#c24bce2a283adad5bc3f58e0d48249b92379d8ef"
|
||||
|
||||
repeat-element@^1.1.2:
|
||||
version "1.1.3"
|
||||
resolved "repeat-element-1.1.3.tgz#782e0d825c0c5a3bb39731f84efee6b742e6b1ce"
|
||||
|
||||
repeat-string@^1.5.2:
|
||||
version "1.6.1"
|
||||
resolved "repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637"
|
||||
|
||||
resolve@^1.1.6, resolve@^1.8.1:
|
||||
version "1.8.1"
|
||||
resolved "olve-1.8.1.tgz#82f1ec19a423ac1fbd080b0bab06ba36e84a7a26"
|
||||
dependencies:
|
||||
path-parse "^1.0.5"
|
||||
|
||||
resolve@^1.3.2:
|
||||
version "1.5.0"
|
||||
resolved "resolve-1.5.0.tgz#1f09acce796c9a762579f31b2c1cc4c3cddf9f36"
|
||||
dependencies:
|
||||
path-parse "^1.0.5"
|
||||
|
||||
rollup-plugin-commonjs@^9.2.0:
|
||||
version "9.2.0"
|
||||
resolved "mmonjs/-/rollup-plugin-commonjs-9.2.0.tgz#4604e25069e0c78a09e08faa95dc32dec27f7c89"
|
||||
dependencies:
|
||||
estree-walker "^0.5.2"
|
||||
magic-string "^0.25.1"
|
||||
resolve "^1.8.1"
|
||||
rollup-pluginutils "^2.3.3"
|
||||
|
||||
rollup-plugin-node-resolve@^3.4.0:
|
||||
version "3.4.0"
|
||||
resolved "de-resolve/-/rollup-plugin-node-resolve-3.4.0.tgz#908585eda12e393caac7498715a01e08606abc89"
|
||||
dependencies:
|
||||
builtin-modules "^2.0.0"
|
||||
is-module "^1.0.0"
|
||||
resolve "^1.1.6"
|
||||
|
||||
rollup-pluginutils@^2.3.3:
|
||||
version "2.3.3"
|
||||
resolved "rollup-pluginutils-2.3.3.tgz#3aad9b1eb3e7fe8262820818840bf091e5ae6794"
|
||||
dependencies:
|
||||
estree-walker "^0.5.2"
|
||||
micromatch "^2.3.11"
|
||||
|
||||
rollup@^0.66.6:
|
||||
version "0.66.6"
|
||||
resolved "lup-0.66.6.tgz#ce7d6185beb7acea644ce220c25e71ae03275482"
|
||||
dependencies:
|
||||
"@types/estree" "0.0.39"
|
||||
"@types/node" "*"
|
||||
|
||||
semver@^5.3.0:
|
||||
version "5.5.0"
|
||||
resolved "semver-5.5.0.tgz#dc4bbc7a6ca9d916dee5d43516f0092b58f7b8ab"
|
||||
|
||||
sourcemap-codec@^1.4.1:
|
||||
version "1.4.3"
|
||||
resolved "sourcemap-codec-1.4.3.tgz#0ba615b73ec35112f63c2f2d9e7c3f87282b0e33"
|
||||
|
||||
sprintf-js@~1.0.2:
|
||||
version "1.0.3"
|
||||
resolved "sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c"
|
||||
@@ -509,9 +225,9 @@ tsutils@^2.12.1:
|
||||
dependencies:
|
||||
tslib "^1.8.1"
|
||||
|
||||
typescript@3.5.1:
|
||||
version "3.5.1"
|
||||
resolved "typescript-3.5.1.tgz#ba72a6a600b2158139c5dd8850f700e231464202"
|
||||
typescript@^3.7.2:
|
||||
version "3.7.2"
|
||||
resolved "typescript-3.7.2.tgz"
|
||||
|
||||
wrappy@1:
|
||||
version "1.0.2"
|
||||
|
||||
@@ -7,6 +7,7 @@ import com.semmle.js.ast.AssignmentExpression;
|
||||
import com.semmle.js.ast.BlockStatement;
|
||||
import com.semmle.js.ast.CallExpression;
|
||||
import com.semmle.js.ast.CatchClause;
|
||||
import com.semmle.js.ast.Chainable;
|
||||
import com.semmle.js.ast.ClassExpression;
|
||||
import com.semmle.js.ast.ComprehensionBlock;
|
||||
import com.semmle.js.ast.ComprehensionExpression;
|
||||
@@ -470,7 +471,8 @@ public class CustomParser extends FlowParser {
|
||||
|
||||
Expression property = this.parsePropertyIdentifierOrIdentifier();
|
||||
MemberExpression node =
|
||||
new MemberExpression(start, base, property, false, false, isOnOptionalChain(false, base));
|
||||
new MemberExpression(
|
||||
start, base, property, false, false, Chainable.isOnOptionalChain(false, base));
|
||||
return Pair.make(this.finishNode(node), true);
|
||||
} else if (this.eat(doubleDot)) {
|
||||
SourceLocation start = new SourceLocation(startLoc);
|
||||
|
||||
@@ -1520,10 +1520,6 @@ public class Parser {
|
||||
}
|
||||
}
|
||||
|
||||
protected boolean isOnOptionalChain(boolean optional, Expression base) {
|
||||
return optional || base instanceof Chainable && ((Chainable) base).isOnOptionalChain();
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a single subscript {@code s}; if more subscripts could follow, return {@code Pair.make(s,
|
||||
* true}, otherwise return {@code Pair.make(s, false)}.
|
||||
@@ -1544,7 +1540,7 @@ public class Parser {
|
||||
this.parseExpression(false, null),
|
||||
true,
|
||||
optional,
|
||||
isOnOptionalChain(optional, base));
|
||||
Chainable.isOnOptionalChain(optional, base));
|
||||
this.expect(TokenType.bracketR);
|
||||
return Pair.make(this.finishNode(node), true);
|
||||
} else if (!noCalls && this.eat(TokenType.parenL)) {
|
||||
@@ -1572,10 +1568,10 @@ public class Parser {
|
||||
new ArrayList<>(),
|
||||
exprList,
|
||||
optional,
|
||||
isOnOptionalChain(optional, base));
|
||||
Chainable.isOnOptionalChain(optional, base));
|
||||
return Pair.make(this.finishNode(node), true);
|
||||
} else if (this.type == TokenType.backQuote) {
|
||||
if (isOnOptionalChain(optional, base)) {
|
||||
if (Chainable.isOnOptionalChain(optional, base)) {
|
||||
this.raise(base, "An optional chain may not be used in a tagged template expression.");
|
||||
}
|
||||
TaggedTemplateExpression node =
|
||||
@@ -1590,7 +1586,7 @@ public class Parser {
|
||||
this.parseIdent(true),
|
||||
false,
|
||||
optional,
|
||||
isOnOptionalChain(optional, base));
|
||||
Chainable.isOnOptionalChain(optional, base));
|
||||
return Pair.make(this.finishNode(node), true);
|
||||
} else {
|
||||
return Pair.make(base, false);
|
||||
@@ -1832,7 +1828,7 @@ public class Parser {
|
||||
Expression callee =
|
||||
this.parseSubscripts(this.parseExprAtom(null), innerStartPos, innerStartLoc, true);
|
||||
|
||||
if (isOnOptionalChain(false, callee))
|
||||
if (Chainable.isOnOptionalChain(false, callee))
|
||||
this.raise(callee, "An optional chain may not be used in a `new` expression.");
|
||||
|
||||
return parseNewArguments(startLoc, callee);
|
||||
@@ -2314,7 +2310,7 @@ public class Parser {
|
||||
}
|
||||
|
||||
if (node instanceof MemberExpression) {
|
||||
if (isOnOptionalChain(false, (MemberExpression) node))
|
||||
if (Chainable.isOnOptionalChain(false, (MemberExpression) node))
|
||||
this.raise(node, "Invalid left-hand side in assignment");
|
||||
if (!isBinding) return node;
|
||||
}
|
||||
|
||||
@@ -7,4 +7,14 @@ public interface Chainable {
|
||||
|
||||
/** Is this on an optional chain? */
|
||||
abstract boolean isOnOptionalChain();
|
||||
|
||||
/**
|
||||
* Returns true if a chainable node is on an optional chain.
|
||||
*
|
||||
* @param optional true if the node in question is itself optional (has the ?. token)
|
||||
* @param base the calle or base of the optional access
|
||||
*/
|
||||
public static boolean isOnOptionalChain(boolean optional, Expression base) {
|
||||
return optional || base instanceof Chainable && ((Chainable) base).isOnOptionalChain();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,9 +17,10 @@ public class DeclarationFlags {
|
||||
public static final int protected_ = 1 << 6;
|
||||
public static final int optional = 1 << 7;
|
||||
public static final int definiteAssignmentAssertion = 1 << 8;
|
||||
public static final int declareKeyword = 1 << 9;
|
||||
|
||||
public static final int none = 0;
|
||||
public static final int numberOfFlags = 9;
|
||||
public static final int numberOfFlags = 10;
|
||||
|
||||
public static final List<String> names =
|
||||
Arrays.asList(
|
||||
@@ -31,7 +32,8 @@ public class DeclarationFlags {
|
||||
"private",
|
||||
"protected",
|
||||
"optional",
|
||||
"definiteAssignmentAssertion");
|
||||
"definiteAssignmentAssertion",
|
||||
"declare");
|
||||
|
||||
public static final List<String> relationNames =
|
||||
Arrays.asList(
|
||||
@@ -43,7 +45,8 @@ public class DeclarationFlags {
|
||||
"hasPrivateKeyword",
|
||||
"hasProtectedKeyword",
|
||||
"isOptionalMember",
|
||||
"hasDefiniteAssignmentAssertion");
|
||||
"hasDefiniteAssignmentAssertion",
|
||||
"hasDeclareKeyword");
|
||||
|
||||
public static boolean isComputed(int flags) {
|
||||
return (flags & computed) != 0;
|
||||
@@ -81,6 +84,10 @@ public class DeclarationFlags {
|
||||
return (flags & definiteAssignmentAssertion) != 0;
|
||||
}
|
||||
|
||||
public static boolean hasDeclareKeyword(int flags) {
|
||||
return (flags & declareKeyword) != 0;
|
||||
}
|
||||
|
||||
/** Returns a mask with the computed bit set to the value of <tt>enable</tt>. */
|
||||
public static int getComputed(boolean enable) {
|
||||
return enable ? computed : 0;
|
||||
@@ -128,6 +135,11 @@ public class DeclarationFlags {
|
||||
return enable ? definiteAssignmentAssertion : 0;
|
||||
}
|
||||
|
||||
/** Returns a mask with the declare keyword bit set to the value of <tt>enable</tt>. */
|
||||
public static int getDeclareKeyword(boolean enable) {
|
||||
return enable ? declareKeyword : 0;
|
||||
}
|
||||
|
||||
/** Returns true if the <tt>n</tt>th bit is set in <tt>flags</tt>. */
|
||||
public static boolean hasNthFlag(int flags, int n) {
|
||||
return (flags & (1 << n)) != 0;
|
||||
|
||||
@@ -34,13 +34,13 @@ import com.semmle.ts.ast.InferTypeExpr;
|
||||
import com.semmle.ts.ast.InterfaceDeclaration;
|
||||
import com.semmle.ts.ast.InterfaceTypeExpr;
|
||||
import com.semmle.ts.ast.IntersectionTypeExpr;
|
||||
import com.semmle.ts.ast.IsTypeExpr;
|
||||
import com.semmle.ts.ast.KeywordTypeExpr;
|
||||
import com.semmle.ts.ast.MappedTypeExpr;
|
||||
import com.semmle.ts.ast.NamespaceDeclaration;
|
||||
import com.semmle.ts.ast.NonNullAssertion;
|
||||
import com.semmle.ts.ast.OptionalTypeExpr;
|
||||
import com.semmle.ts.ast.ParenthesizedTypeExpr;
|
||||
import com.semmle.ts.ast.PredicateTypeExpr;
|
||||
import com.semmle.ts.ast.RestTypeExpr;
|
||||
import com.semmle.ts.ast.TupleTypeExpr;
|
||||
import com.semmle.ts.ast.TypeAliasDeclaration;
|
||||
@@ -634,7 +634,7 @@ public class DefaultVisitor<C, R> implements Visitor<C, R> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public R visit(IsTypeExpr nd, C c) {
|
||||
public R visit(PredicateTypeExpr nd, C c) {
|
||||
return visit((TypeExpression) nd, c);
|
||||
}
|
||||
|
||||
|
||||
@@ -75,6 +75,11 @@ public abstract class MemberDefinition<V extends Expression> extends Node {
|
||||
return DeclarationFlags.isReadonly(flags);
|
||||
}
|
||||
|
||||
/** Returns true if this has the <tt>declare</tt> modifier. */
|
||||
public boolean hasDeclareKeyword() {
|
||||
return DeclarationFlags.hasDeclareKeyword(flags);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the expression denoting the name of the member, or {@code null} if this is a
|
||||
* call/construct signature.
|
||||
|
||||
@@ -30,13 +30,13 @@ import com.semmle.ts.ast.InferTypeExpr;
|
||||
import com.semmle.ts.ast.InterfaceDeclaration;
|
||||
import com.semmle.ts.ast.InterfaceTypeExpr;
|
||||
import com.semmle.ts.ast.IntersectionTypeExpr;
|
||||
import com.semmle.ts.ast.IsTypeExpr;
|
||||
import com.semmle.ts.ast.KeywordTypeExpr;
|
||||
import com.semmle.ts.ast.MappedTypeExpr;
|
||||
import com.semmle.ts.ast.NamespaceDeclaration;
|
||||
import com.semmle.ts.ast.NonNullAssertion;
|
||||
import com.semmle.ts.ast.OptionalTypeExpr;
|
||||
import com.semmle.ts.ast.ParenthesizedTypeExpr;
|
||||
import com.semmle.ts.ast.PredicateTypeExpr;
|
||||
import com.semmle.ts.ast.RestTypeExpr;
|
||||
import com.semmle.ts.ast.TupleTypeExpr;
|
||||
import com.semmle.ts.ast.TypeAliasDeclaration;
|
||||
@@ -717,8 +717,12 @@ public class NodeCopier implements Visitor<Void, INode> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public INode visit(IsTypeExpr nd, Void c) {
|
||||
return new IsTypeExpr(visit(nd.getLoc()), copy(nd.getLeft()), copy(nd.getRight()));
|
||||
public INode visit(PredicateTypeExpr nd, Void c) {
|
||||
return new PredicateTypeExpr(
|
||||
visit(nd.getLoc()),
|
||||
copy(nd.getExpression()),
|
||||
copy(nd.getTypeExpr()),
|
||||
nd.hasAssertsKeyword());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -30,13 +30,13 @@ import com.semmle.ts.ast.InferTypeExpr;
|
||||
import com.semmle.ts.ast.InterfaceDeclaration;
|
||||
import com.semmle.ts.ast.InterfaceTypeExpr;
|
||||
import com.semmle.ts.ast.IntersectionTypeExpr;
|
||||
import com.semmle.ts.ast.IsTypeExpr;
|
||||
import com.semmle.ts.ast.KeywordTypeExpr;
|
||||
import com.semmle.ts.ast.MappedTypeExpr;
|
||||
import com.semmle.ts.ast.NamespaceDeclaration;
|
||||
import com.semmle.ts.ast.NonNullAssertion;
|
||||
import com.semmle.ts.ast.OptionalTypeExpr;
|
||||
import com.semmle.ts.ast.ParenthesizedTypeExpr;
|
||||
import com.semmle.ts.ast.PredicateTypeExpr;
|
||||
import com.semmle.ts.ast.RestTypeExpr;
|
||||
import com.semmle.ts.ast.TupleTypeExpr;
|
||||
import com.semmle.ts.ast.TypeAliasDeclaration;
|
||||
@@ -256,7 +256,7 @@ public interface Visitor<C, R> {
|
||||
|
||||
public R visit(TypeofTypeExpr nd, C c);
|
||||
|
||||
public R visit(IsTypeExpr nd, C c);
|
||||
public R visit(PredicateTypeExpr nd, C c);
|
||||
|
||||
public R visit(InterfaceTypeExpr nd, C c);
|
||||
|
||||
|
||||
@@ -125,13 +125,13 @@ import com.semmle.ts.ast.InferTypeExpr;
|
||||
import com.semmle.ts.ast.InterfaceDeclaration;
|
||||
import com.semmle.ts.ast.InterfaceTypeExpr;
|
||||
import com.semmle.ts.ast.IntersectionTypeExpr;
|
||||
import com.semmle.ts.ast.IsTypeExpr;
|
||||
import com.semmle.ts.ast.KeywordTypeExpr;
|
||||
import com.semmle.ts.ast.MappedTypeExpr;
|
||||
import com.semmle.ts.ast.NamespaceDeclaration;
|
||||
import com.semmle.ts.ast.NonNullAssertion;
|
||||
import com.semmle.ts.ast.OptionalTypeExpr;
|
||||
import com.semmle.ts.ast.ParenthesizedTypeExpr;
|
||||
import com.semmle.ts.ast.PredicateTypeExpr;
|
||||
import com.semmle.ts.ast.RestTypeExpr;
|
||||
import com.semmle.ts.ast.TupleTypeExpr;
|
||||
import com.semmle.ts.ast.TypeAliasDeclaration;
|
||||
@@ -1377,9 +1377,8 @@ public class ASTExtractor {
|
||||
trapwriter.addTuple("properties", methkey, c.parent, c.childIndex, kind, tostring);
|
||||
locationManager.emitNodeLocation(nd, methkey);
|
||||
visitAll(nd.getDecorators(), methkey, IdContext.varBind, -1, -1);
|
||||
visit(nd.getKey(), methkey, 0, nd.isComputed() ? IdContext.varBind : IdContext.label);
|
||||
|
||||
// the initialiser expression of an instance field is evaluated as part of
|
||||
// the name and initialiser expression of an instance field is evaluated as part of
|
||||
// the constructor, so we adjust our syntactic context to reflect this
|
||||
MethodDefinition ctor = null;
|
||||
if (nd instanceof FieldDefinition && !nd.isStatic() && !ctors.isEmpty()) ctor = ctors.peek();
|
||||
@@ -1388,6 +1387,7 @@ public class ASTExtractor {
|
||||
constructorKey = trapwriter.localID(ctor.getValue());
|
||||
contextManager.enterContainer(constructorKey);
|
||||
}
|
||||
visit(nd.getKey(), methkey, 0, nd.isComputed() ? IdContext.varBind : IdContext.label);
|
||||
visit(nd.getValue(), methkey, 1, c.idcontext);
|
||||
if (ctor != null) contextManager.leaveContainer();
|
||||
|
||||
@@ -1410,6 +1410,10 @@ public class ASTExtractor {
|
||||
}
|
||||
}
|
||||
|
||||
if (nd.hasDeclareKeyword()) {
|
||||
trapwriter.addTuple("hasDeclareKeyword", methkey);
|
||||
}
|
||||
|
||||
return methkey;
|
||||
}
|
||||
|
||||
@@ -1707,10 +1711,13 @@ public class ASTExtractor {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Label visit(IsTypeExpr nd, Context c) {
|
||||
public Label visit(PredicateTypeExpr nd, Context c) {
|
||||
Label key = super.visit(nd, c);
|
||||
visit(nd.getLeft(), key, 0, IdContext.varInTypeBind);
|
||||
visit(nd.getRight(), key, 1, IdContext.typeBind);
|
||||
visit(nd.getExpression(), key, 0, IdContext.varInTypeBind);
|
||||
visit(nd.getTypeExpr(), key, 1, IdContext.typeBind);
|
||||
if (nd.hasAssertsKeyword()) {
|
||||
trapwriter.addTuple("hasAssertsKeyword", key);
|
||||
}
|
||||
return key;
|
||||
}
|
||||
|
||||
|
||||
@@ -387,6 +387,10 @@ public class AutoBuild {
|
||||
patterns.add("-**/*.min.js");
|
||||
patterns.add("-**/*-min.js");
|
||||
|
||||
// exclude `node_modules` and `bower_components`
|
||||
patterns.add("-**/node_modules");
|
||||
patterns.add("-**/bower_components");
|
||||
|
||||
String base = LGTM_SRC.toString().replace('\\', '/');
|
||||
// process `$LGTM_INDEX_FILTERS`
|
||||
for (String pattern : Main.NEWLINE.split(getEnvVar("LGTM_INDEX_FILTERS", ""))) {
|
||||
|
||||
@@ -249,6 +249,7 @@ public class ExprKinds {
|
||||
@Override
|
||||
public Integer visit(MetaProperty nd, Void c) {
|
||||
if (nd.getMeta().getName().equals("new")) return 82; // @newtargetexpr
|
||||
if (nd.getMeta().getName().equals("import")) return 115; // @importmetaexpr
|
||||
return 93; // @functionsentexpr
|
||||
}
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ public class Main {
|
||||
* A version identifier that should be updated every time the extractor changes in such a way that
|
||||
* it may produce different tuples for the same file under the same {@link ExtractorConfig}.
|
||||
*/
|
||||
public static final String EXTRACTOR_VERSION = "2019-10-07";
|
||||
public static final String EXTRACTOR_VERSION = "2019-10-08";
|
||||
|
||||
public static final Pattern NEWLINE = Pattern.compile("\n");
|
||||
|
||||
|
||||
@@ -46,9 +46,9 @@ import com.semmle.ts.ast.InferTypeExpr;
|
||||
import com.semmle.ts.ast.InterfaceDeclaration;
|
||||
import com.semmle.ts.ast.InterfaceTypeExpr;
|
||||
import com.semmle.ts.ast.IntersectionTypeExpr;
|
||||
import com.semmle.ts.ast.IsTypeExpr;
|
||||
import com.semmle.ts.ast.NamespaceDeclaration;
|
||||
import com.semmle.ts.ast.ParenthesizedTypeExpr;
|
||||
import com.semmle.ts.ast.PredicateTypeExpr;
|
||||
import com.semmle.ts.ast.TupleTypeExpr;
|
||||
import com.semmle.ts.ast.TypeAliasDeclaration;
|
||||
import com.semmle.ts.ast.UnionTypeExpr;
|
||||
@@ -660,8 +660,8 @@ public class ScopeManager {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visit(IsTypeExpr nd, Void c) {
|
||||
return nd.getRight().accept(this, c);
|
||||
public Void visit(PredicateTypeExpr nd, Void c) {
|
||||
return nd.getTypeExpr().accept(this, c);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -16,11 +16,11 @@ import com.semmle.ts.ast.IndexedAccessTypeExpr;
|
||||
import com.semmle.ts.ast.InferTypeExpr;
|
||||
import com.semmle.ts.ast.InterfaceTypeExpr;
|
||||
import com.semmle.ts.ast.IntersectionTypeExpr;
|
||||
import com.semmle.ts.ast.IsTypeExpr;
|
||||
import com.semmle.ts.ast.KeywordTypeExpr;
|
||||
import com.semmle.ts.ast.MappedTypeExpr;
|
||||
import com.semmle.ts.ast.OptionalTypeExpr;
|
||||
import com.semmle.ts.ast.ParenthesizedTypeExpr;
|
||||
import com.semmle.ts.ast.PredicateTypeExpr;
|
||||
import com.semmle.ts.ast.RestTypeExpr;
|
||||
import com.semmle.ts.ast.TupleTypeExpr;
|
||||
import com.semmle.ts.ast.TypeParameter;
|
||||
@@ -159,7 +159,7 @@ public class TypeExprKinds {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer visit(IsTypeExpr nd, Void c) {
|
||||
public Integer visit(PredicateTypeExpr nd, Void c) {
|
||||
return isTypeExpr;
|
||||
}
|
||||
|
||||
|
||||
@@ -160,7 +160,7 @@ public class AutoBuildTests {
|
||||
addFile(false, LGTM_SRC, "tst.json");
|
||||
addFile(true, LGTM_SRC, "package.json");
|
||||
addFile(true, LGTM_SRC, ".eslintrc.yml");
|
||||
addFile(true, LGTM_SRC, "node_modules", "leftpad", "index.js");
|
||||
addFile(true, LGTM_SRC, "vendor", "leftpad", "index.js");
|
||||
runTest();
|
||||
}
|
||||
|
||||
@@ -184,72 +184,72 @@ public class AutoBuildTests {
|
||||
public void includeFile() throws IOException {
|
||||
envVars.put("LGTM_INDEX_INCLUDE", "tst.js");
|
||||
addFile(true, LGTM_SRC, "tst.js");
|
||||
addFile(false, LGTM_SRC, "node_modules", "leftpad", "index.js");
|
||||
addFile(false, LGTM_SRC, "vendor", "leftpad", "index.js");
|
||||
runTest();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void excludeFile() throws IOException {
|
||||
envVars.put("LGTM_INDEX_EXCLUDE", "node_modules/leftpad/index.js");
|
||||
envVars.put("LGTM_INDEX_EXCLUDE", "vendor/leftpad/index.js");
|
||||
addFile(true, LGTM_SRC, "tst.js");
|
||||
addFile(false, LGTM_SRC, "node_modules", "leftpad", "index.js");
|
||||
addFile(false, LGTM_SRC, "vendor", "leftpad", "index.js");
|
||||
runTest();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void excludeFolderByPattern() throws IOException {
|
||||
envVars.put("LGTM_INDEX_FILTERS", "exclude:**/node_modules");
|
||||
envVars.put("LGTM_INDEX_FILTERS", "exclude:**/vendor");
|
||||
addFile(true, LGTM_SRC, "tst.js");
|
||||
addFile(false, LGTM_SRC, "node_modules", "leftpad", "index.js");
|
||||
addFile(false, LGTM_SRC, "vendor", "leftpad", "index.js");
|
||||
runTest();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void excludeFolderByPattern2() throws IOException {
|
||||
envVars.put("LGTM_INDEX_FILTERS", "exclude:*/**/node_modules");
|
||||
envVars.put("LGTM_INDEX_FILTERS", "exclude:*/**/vendor");
|
||||
addFile(true, LGTM_SRC, "tst.js");
|
||||
addFile(true, LGTM_SRC, "node_modules", "dep", "index.js");
|
||||
addFile(false, LGTM_SRC, "node_modules", "dep", "node_modules", "depdep", "index.js");
|
||||
addFile(true, LGTM_SRC, "vendor", "dep", "index.js");
|
||||
addFile(false, LGTM_SRC, "vendor", "dep", "vendor", "depdep", "index.js");
|
||||
runTest();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void excludeFolderByPattern3() throws IOException {
|
||||
envVars.put("LGTM_INDEX_FILTERS", "exclude:**/node_modules\n");
|
||||
envVars.put("LGTM_INDEX_FILTERS", "exclude:**/vendor\n");
|
||||
addFile(true, LGTM_SRC, "tst.js");
|
||||
addFile(false, LGTM_SRC, "node_modules", "leftpad", "index.js");
|
||||
addFile(false, LGTM_SRC, "vendor", "leftpad", "index.js");
|
||||
runTest();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void excludeFolderByPatterns() throws IOException {
|
||||
envVars.put("LGTM_INDEX_FILTERS", "exclude:foo\nexclude:**/node_modules");
|
||||
envVars.put("LGTM_INDEX_FILTERS", "exclude:foo\nexclude:**/vendor");
|
||||
addFile(true, LGTM_SRC, "tst.js");
|
||||
addFile(false, LGTM_SRC, "node_modules", "leftpad", "index.js");
|
||||
addFile(false, LGTM_SRC, "vendor", "leftpad", "index.js");
|
||||
runTest();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void excludeFolderByName() throws IOException {
|
||||
envVars.put("LGTM_INDEX_EXCLUDE", "node_modules");
|
||||
envVars.put("LGTM_INDEX_EXCLUDE", "vendor");
|
||||
addFile(true, LGTM_SRC, "tst.js");
|
||||
addFile(false, LGTM_SRC, "node_modules", "leftpad", "index.js");
|
||||
addFile(false, LGTM_SRC, "vendor", "leftpad", "index.js");
|
||||
runTest();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void excludeFolderByName2() throws IOException {
|
||||
envVars.put("LGTM_INDEX_EXCLUDE", "node_modules\n");
|
||||
envVars.put("LGTM_INDEX_EXCLUDE", "vendor\n");
|
||||
addFile(true, LGTM_SRC, "tst.js");
|
||||
addFile(false, LGTM_SRC, "node_modules", "leftpad", "index.js");
|
||||
addFile(false, LGTM_SRC, "vendor", "leftpad", "index.js");
|
||||
runTest();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void excludeFolderByName3() throws IOException {
|
||||
envVars.put("LGTM_INDEX_EXCLUDE", "./node_modules\n");
|
||||
envVars.put("LGTM_INDEX_EXCLUDE", "./vendor\n");
|
||||
addFile(true, LGTM_SRC, "tst.js");
|
||||
addFile(false, LGTM_SRC, "node_modules", "leftpad", "index.js");
|
||||
addFile(false, LGTM_SRC, "vendor", "leftpad", "index.js");
|
||||
runTest();
|
||||
}
|
||||
|
||||
@@ -258,8 +258,8 @@ public class AutoBuildTests {
|
||||
envVars.put("LGTM_INDEX_FILTERS", "exclude:**/*.js");
|
||||
addFile(false, LGTM_SRC, "tst.js");
|
||||
addFile(true, LGTM_SRC, "tst.html");
|
||||
addFile(false, LGTM_SRC, "node_modules", "leftpad", "index.js");
|
||||
addFile(true, LGTM_SRC, "node_modules", "leftpad", "index.html");
|
||||
addFile(false, LGTM_SRC, "vendor", "leftpad", "index.js");
|
||||
addFile(true, LGTM_SRC, "vendor", "leftpad", "index.html");
|
||||
runTest();
|
||||
}
|
||||
|
||||
@@ -268,7 +268,7 @@ public class AutoBuildTests {
|
||||
envVars.put("LGTM_INDEX_FILTERS", "include:**/*.json");
|
||||
addFile(true, LGTM_SRC, "tst.js");
|
||||
addFile(true, LGTM_SRC, "tst.json");
|
||||
addFile(true, LGTM_SRC, "node_modules", "leftpad", "tst.json");
|
||||
addFile(true, LGTM_SRC, "vendor", "leftpad", "tst.json");
|
||||
runTest();
|
||||
}
|
||||
|
||||
@@ -277,16 +277,16 @@ public class AutoBuildTests {
|
||||
envVars.put("LGTM_INDEX_FILTERS", "include:*.json");
|
||||
addFile(true, LGTM_SRC, "tst.js");
|
||||
addFile(true, LGTM_SRC, "tst.json");
|
||||
addFile(false, LGTM_SRC, "node_modules", "leftpad", "tst.json");
|
||||
addFile(false, LGTM_SRC, "vendor", "leftpad", "tst.json");
|
||||
runTest();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void includeAndExclude() throws IOException {
|
||||
envVars.put("LGTM_INDEX_FILTERS", "include:**/*.json\n" + "exclude:**/node_modules");
|
||||
envVars.put("LGTM_INDEX_FILTERS", "include:**/*.json\n" + "exclude:**/vendor");
|
||||
addFile(true, LGTM_SRC, "tst.js");
|
||||
addFile(true, LGTM_SRC, "tst.json");
|
||||
addFile(false, LGTM_SRC, "node_modules", "leftpad", "tst.json");
|
||||
addFile(false, LGTM_SRC, "vendor", "leftpad", "tst.json");
|
||||
runTest();
|
||||
}
|
||||
|
||||
@@ -295,7 +295,7 @@ public class AutoBuildTests {
|
||||
Path repositoryFolders = Files.createFile(SEMMLE_DIST.resolve("repositoryFolders.csv"));
|
||||
List<String> csvLines = new ArrayList<>();
|
||||
csvLines.add("classification,path");
|
||||
csvLines.add("thirdparty," + LGTM_SRC.resolve("node_modules"));
|
||||
csvLines.add("thirdparty," + LGTM_SRC.resolve("vendor"));
|
||||
csvLines.add("external," + LGTM_SRC.resolve("foo").resolve("bar").toUri());
|
||||
csvLines.add("metadata," + LGTM_SRC.resolve(".git"));
|
||||
Files.write(repositoryFolders, csvLines, StandardCharsets.UTF_8);
|
||||
@@ -303,7 +303,7 @@ public class AutoBuildTests {
|
||||
addFile(true, LGTM_SRC, "tst.js");
|
||||
addFile(false, LGTM_SRC, "foo", "bar", "tst.js");
|
||||
addFile(false, LGTM_SRC, ".git", "tst.js");
|
||||
addFile(true, LGTM_SRC, "node_modules", "leftpad", "tst.js");
|
||||
addFile(true, LGTM_SRC, "vendor", "leftpad", "tst.js");
|
||||
runTest();
|
||||
}
|
||||
|
||||
@@ -324,7 +324,7 @@ public class AutoBuildTests {
|
||||
Path repositoryFolders = Files.createFile(SEMMLE_DIST.resolve("repositoryFolders.csv"));
|
||||
List<String> csvLines = new ArrayList<>();
|
||||
csvLines.add("classification,path");
|
||||
csvLines.add("thirdparty," + LGTM_SRC.resolve("node_modules"));
|
||||
csvLines.add("thirdparty," + LGTM_SRC.resolve("vendor"));
|
||||
csvLines.add("external," + LGTM_SRC.resolve("foo").resolve("bar"));
|
||||
csvLines.add("metadata," + LGTM_SRC.resolve(".git"));
|
||||
Files.write(repositoryFolders, csvLines, StandardCharsets.UTF_8);
|
||||
@@ -333,7 +333,7 @@ public class AutoBuildTests {
|
||||
addFile(true, LGTM_SRC, "tst.js");
|
||||
addFile(true, LGTM_SRC, "foo", "bar", "tst.js");
|
||||
addFile(false, LGTM_SRC, ".git", "tst.js");
|
||||
addFile(true, LGTM_SRC, "node_modules", "leftpad", "tst.js");
|
||||
addFile(true, LGTM_SRC, "vendor", "leftpad", "tst.js");
|
||||
runTest();
|
||||
}
|
||||
|
||||
@@ -453,13 +453,13 @@ public class AutoBuildTests {
|
||||
@Test
|
||||
public void includeNonExistentFile() throws IOException {
|
||||
envVars.put("LGTM_INDEX_INCLUDE", "tst.js");
|
||||
addFile(false, LGTM_SRC, "node_modules", "leftpad", "index.js");
|
||||
addFile(false, LGTM_SRC, "vendor", "leftpad", "index.js");
|
||||
runTest();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void excludeNonExistentFile() throws IOException {
|
||||
envVars.put("LGTM_INDEX_EXCLUDE", "node_modules/leftpad/index.js");
|
||||
envVars.put("LGTM_INDEX_EXCLUDE", "vendor/leftpad/index.js");
|
||||
addFile(true, LGTM_SRC, "tst.js");
|
||||
runTest();
|
||||
}
|
||||
@@ -483,6 +483,40 @@ public class AutoBuildTests {
|
||||
runTest();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void nodeModulesAreExcluded() throws IOException {
|
||||
addFile(true, LGTM_SRC, "index.js");
|
||||
addFile(false, LGTM_SRC, "node_modules", "dep", "main.js");
|
||||
addFile(false, LGTM_SRC, "node_modules", "dep", "node_modules", "leftpad", "index.js");
|
||||
runTest();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void nodeModulesCanBeReincluded() throws IOException {
|
||||
envVars.put("LGTM_INDEX_FILTERS", "include:**/node_modules");
|
||||
addFile(true, LGTM_SRC, "index.js");
|
||||
addFile(true, LGTM_SRC, "node_modules", "dep", "main.js");
|
||||
addFile(true, LGTM_SRC, "node_modules", "dep", "node_modules", "leftpad", "index.js");
|
||||
runTest();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void bowerComponentsAreExcluded() throws IOException {
|
||||
addFile(true, LGTM_SRC, "index.js");
|
||||
addFile(false, LGTM_SRC, "bower_components", "dep", "main.js");
|
||||
addFile(false, LGTM_SRC, "bower_components", "dep", "bower_components", "leftpad", "index.js");
|
||||
runTest();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void bowerComponentsCanBeReincluded() throws IOException {
|
||||
envVars.put("LGTM_INDEX_FILTERS", "include:**/bower_components");
|
||||
addFile(true, LGTM_SRC, "index.js");
|
||||
addFile(true, LGTM_SRC, "bower_components", "dep", "main.js");
|
||||
addFile(true, LGTM_SRC, "bower_components", "dep", "bower_components", "leftpad", "index.js");
|
||||
runTest();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void customExtensions() throws IOException {
|
||||
envVars.put("LGTM_INDEX_FILETYPES", ".jsm:js\n.soy:html");
|
||||
|
||||
@@ -17,6 +17,7 @@ import com.semmle.js.ast.BlockStatement;
|
||||
import com.semmle.js.ast.BreakStatement;
|
||||
import com.semmle.js.ast.CallExpression;
|
||||
import com.semmle.js.ast.CatchClause;
|
||||
import com.semmle.js.ast.Chainable;
|
||||
import com.semmle.js.ast.ClassBody;
|
||||
import com.semmle.js.ast.ClassDeclaration;
|
||||
import com.semmle.js.ast.ClassExpression;
|
||||
@@ -126,13 +127,13 @@ import com.semmle.ts.ast.InferTypeExpr;
|
||||
import com.semmle.ts.ast.InterfaceDeclaration;
|
||||
import com.semmle.ts.ast.InterfaceTypeExpr;
|
||||
import com.semmle.ts.ast.IntersectionTypeExpr;
|
||||
import com.semmle.ts.ast.IsTypeExpr;
|
||||
import com.semmle.ts.ast.KeywordTypeExpr;
|
||||
import com.semmle.ts.ast.MappedTypeExpr;
|
||||
import com.semmle.ts.ast.NamespaceDeclaration;
|
||||
import com.semmle.ts.ast.NonNullAssertion;
|
||||
import com.semmle.ts.ast.OptionalTypeExpr;
|
||||
import com.semmle.ts.ast.ParenthesizedTypeExpr;
|
||||
import com.semmle.ts.ast.PredicateTypeExpr;
|
||||
import com.semmle.ts.ast.RestTypeExpr;
|
||||
import com.semmle.ts.ast.TupleTypeExpr;
|
||||
import com.semmle.ts.ast.TypeAliasDeclaration;
|
||||
@@ -877,7 +878,10 @@ public class TypeScriptASTConverter {
|
||||
}
|
||||
Expression callee = convertChild(node, "expression");
|
||||
List<ITypeExpression> typeArguments = convertChildrenAsTypes(node, "typeArguments");
|
||||
CallExpression call = new CallExpression(loc, callee, typeArguments, arguments, false, false);
|
||||
boolean optional = node.has("questionDotToken");
|
||||
boolean onOptionalChain = Chainable.isOnOptionalChain(optional, callee);
|
||||
CallExpression call =
|
||||
new CallExpression(loc, callee, typeArguments, arguments, optional, onOptionalChain);
|
||||
attachResolvedSignature(call, node);
|
||||
return call;
|
||||
}
|
||||
@@ -942,9 +946,7 @@ public class TypeScriptASTConverter {
|
||||
SourceLocation bodyLoc = new SourceLocation(loc.getSource(), loc.getStart(), loc.getEnd());
|
||||
advance(bodyLoc, skip);
|
||||
ClassBody body = new ClassBody(bodyLoc, convertChildren(node, "members"));
|
||||
if ("ClassExpression".equals(kind) || id == null) {
|
||||
// Note that `export default class {}` is represented as a ClassDeclaration
|
||||
// in TypeScript but we treat this as a ClassExpression.
|
||||
if ("ClassExpression".equals(kind)) {
|
||||
ClassExpression classExpr =
|
||||
new ClassExpression(loc, id, typeParameters, superClass, superInterfaces, body);
|
||||
attachSymbolInformation(classExpr.getClassDef(), node);
|
||||
@@ -967,7 +969,13 @@ public class TypeScriptASTConverter {
|
||||
classDecl.addDecorators(convertChildren(node, "decorators"));
|
||||
advanceUntilAfter(loc, classDecl.getDecorators());
|
||||
}
|
||||
return fixExports(loc, classDecl);
|
||||
Node exportedDecl = fixExports(loc, classDecl);
|
||||
// Convert default-exported anonymous class declarations to class expressions.
|
||||
if (exportedDecl instanceof ExportDefaultDeclaration && !classDecl.getClassDef().hasId()) {
|
||||
return new ExportDefaultDeclaration(
|
||||
exportedDecl.getLoc(), new ClassExpression(classDecl.getLoc(), classDecl.getClassDef()));
|
||||
}
|
||||
return exportedDecl;
|
||||
}
|
||||
|
||||
private Node convertCommaListExpression(JsonObject node, SourceLocation loc) throws ParseError {
|
||||
@@ -1104,7 +1112,9 @@ public class TypeScriptASTConverter {
|
||||
throws ParseError {
|
||||
Expression object = convertChild(node, "expression");
|
||||
Expression property = convertChild(node, "argumentExpression");
|
||||
return new MemberExpression(loc, object, property, true, false, false);
|
||||
boolean optional = node.has("questionDotToken");
|
||||
boolean onOptionalChain = Chainable.isOnOptionalChain(optional, object);
|
||||
return new MemberExpression(loc, object, property, true, optional, onOptionalChain);
|
||||
}
|
||||
|
||||
private Node convertEmptyStatement(SourceLocation loc) {
|
||||
@@ -1580,10 +1590,16 @@ public class TypeScriptASTConverter {
|
||||
|
||||
private Node convertMetaProperty(JsonObject node, SourceLocation loc) throws ParseError {
|
||||
Position metaStart = loc.getStart();
|
||||
String keywordKind =
|
||||
syntaxKinds.get(node.getAsJsonPrimitive("keywordToken").getAsInt() + "").getAsString();
|
||||
String identifier = keywordKind.equals("ImportKeyword") ? "import" : "new";
|
||||
Position metaEnd =
|
||||
new Position(metaStart.getLine(), metaStart.getColumn() + 3, metaStart.getOffset() + 3);
|
||||
SourceLocation metaLoc = new SourceLocation("new", metaStart, metaEnd);
|
||||
Identifier meta = new Identifier(metaLoc, "new");
|
||||
new Position(
|
||||
metaStart.getLine(),
|
||||
metaStart.getColumn() + identifier.length(),
|
||||
metaStart.getOffset() + identifier.length());
|
||||
SourceLocation metaLoc = new SourceLocation(identifier, metaStart, metaEnd);
|
||||
Identifier meta = new Identifier(metaLoc, identifier);
|
||||
return new MetaProperty(loc, meta, convertChild(node, "name"));
|
||||
}
|
||||
|
||||
@@ -1961,8 +1977,11 @@ public class TypeScriptASTConverter {
|
||||
|
||||
private Node convertPropertyAccessExpression(JsonObject node, SourceLocation loc)
|
||||
throws ParseError {
|
||||
Expression base = convertChild(node, "expression");
|
||||
boolean optional = node.has("questionDotToken");
|
||||
boolean onOptionalChain = Chainable.isOnOptionalChain(optional, base);
|
||||
return new MemberExpression(
|
||||
loc, convertChild(node, "expression"), convertChild(node, "name"), false, false, false);
|
||||
loc, base, convertChild(node, "name"), false, optional, onOptionalChain);
|
||||
}
|
||||
|
||||
private Node convertPropertyAssignment(JsonObject node, SourceLocation loc) throws ParseError {
|
||||
@@ -1990,6 +2009,9 @@ public class TypeScriptASTConverter {
|
||||
if (node.get("exclamationToken") != null) {
|
||||
flags |= DeclarationFlags.definiteAssignmentAssertion;
|
||||
}
|
||||
if (hasModifier(node, "DeclareKeyword")) {
|
||||
flags |= DeclarationFlags.declareKeyword;
|
||||
}
|
||||
FieldDefinition fieldDefinition =
|
||||
new FieldDefinition(
|
||||
loc,
|
||||
@@ -2180,8 +2202,11 @@ public class TypeScriptASTConverter {
|
||||
}
|
||||
|
||||
private Node convertTypePredicate(JsonObject node, SourceLocation loc) throws ParseError {
|
||||
return new IsTypeExpr(
|
||||
loc, convertChildAsType(node, "parameterName"), convertChildAsType(node, "type"));
|
||||
return new PredicateTypeExpr(
|
||||
loc,
|
||||
convertChildAsType(node, "parameterName"),
|
||||
convertChildAsType(node, "type"),
|
||||
node.has("assertsModifier"));
|
||||
}
|
||||
|
||||
private Node convertTypeReference(JsonObject node, SourceLocation loc) throws ParseError {
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
package com.semmle.ts.ast;
|
||||
|
||||
import com.semmle.js.ast.SourceLocation;
|
||||
import com.semmle.js.ast.Visitor;
|
||||
|
||||
/**
|
||||
* A type of form <tt>E is T</tt> where <tt>E</tt> is a parameter name or <tt>this</tt> and
|
||||
* <tt>T</tt> is a type.
|
||||
*/
|
||||
public class IsTypeExpr extends TypeExpression {
|
||||
private final ITypeExpression left; // Always Identifier or KeywordTypeExpr (in case of 'this')
|
||||
private final ITypeExpression right;
|
||||
|
||||
public IsTypeExpr(SourceLocation loc, ITypeExpression left, ITypeExpression right) {
|
||||
super("IsTypeExpr", loc);
|
||||
this.left = left;
|
||||
this.right = right;
|
||||
}
|
||||
|
||||
public ITypeExpression getLeft() {
|
||||
return left;
|
||||
}
|
||||
|
||||
public ITypeExpression getRight() {
|
||||
return right;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <C, R> R accept(Visitor<C, R> v, C c) {
|
||||
return v.visit(this, c);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package com.semmle.ts.ast;
|
||||
|
||||
import com.semmle.js.ast.SourceLocation;
|
||||
import com.semmle.js.ast.Visitor;
|
||||
|
||||
/**
|
||||
* A type of form <tt>E is T</tt>, <tt>asserts E is T</tt> or <tt>asserts E</tt> where <tt>E</tt> is
|
||||
* a parameter name or <tt>this</tt> and <tt>T</tt> is a type.
|
||||
*/
|
||||
public class PredicateTypeExpr extends TypeExpression {
|
||||
private final ITypeExpression expression;
|
||||
private final ITypeExpression type;
|
||||
private final boolean hasAssertsKeyword;
|
||||
|
||||
public PredicateTypeExpr(
|
||||
SourceLocation loc,
|
||||
ITypeExpression expression,
|
||||
ITypeExpression type,
|
||||
boolean hasAssertsKeyword) {
|
||||
super("PredicateTypeExpr", loc);
|
||||
this.expression = expression;
|
||||
this.type = type;
|
||||
this.hasAssertsKeyword = hasAssertsKeyword;
|
||||
}
|
||||
|
||||
/** Returns the <tt>E</tt> in <tt>E is T</tt>. */
|
||||
public ITypeExpression getExpression() {
|
||||
return expression;
|
||||
}
|
||||
|
||||
/** Returns the <tt>T</tt> in <tt>E is T</tt>. */
|
||||
public ITypeExpression getTypeExpr() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public boolean hasAssertsKeyword() {
|
||||
return hasAssertsKeyword;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <C, R> R accept(Visitor<C, R> v, C c) {
|
||||
return v.visit(this, c);
|
||||
}
|
||||
}
|
||||
@@ -457,8 +457,7 @@ hasLocation(#20156,#20157)
|
||||
#20158=*
|
||||
exprs(#20158,0,#20156,0,"x")
|
||||
hasLocation(#20158,#20059)
|
||||
enclosingStmt(#20158,#20139)
|
||||
exprContainers(#20158,#20001)
|
||||
exprContainers(#20158,#20146)
|
||||
literals("x","x",#20158)
|
||||
#20159=*
|
||||
properties(#20159,#20139,4,8,"static y;")
|
||||
@@ -650,8 +649,7 @@ hasLocation(#20207,#20208)
|
||||
#20209=*
|
||||
exprs(#20209,0,#20207,0,"z")
|
||||
hasLocation(#20209,#20128)
|
||||
enclosingStmt(#20209,#20180)
|
||||
exprContainers(#20209,#20001)
|
||||
exprContainers(#20209,#20188)
|
||||
literals("z","z",#20209)
|
||||
#20210=*
|
||||
entry_cfg_node(#20210,#20001)
|
||||
|
||||
@@ -258,18 +258,17 @@ properties(#20090,#20086,2,8,"x = new.target;")
|
||||
locations_default(#20091,#10000,2,3,2,17)
|
||||
hasLocation(#20090,#20091)
|
||||
#20092=*
|
||||
exprs(#20092,0,#20090,0,"x")
|
||||
hasLocation(#20092,#20031)
|
||||
enclosingStmt(#20092,#20086)
|
||||
exprContainers(#20092,#20001)
|
||||
literals("x","x",#20092)
|
||||
#20093=*
|
||||
exprs(#20093,0,#20090,0,"x")
|
||||
hasLocation(#20093,#20031)
|
||||
exprContainers(#20093,#20092)
|
||||
literals("x","x",#20093)
|
||||
#20094=*
|
||||
exprs(#20094,82,#20090,1,"new.target")
|
||||
#20095=@"loc,{#10000},2,7,2,16"
|
||||
locations_default(#20095,#10000,2,7,2,16)
|
||||
hasLocation(#20094,#20095)
|
||||
exprContainers(#20094,#20093)
|
||||
exprContainers(#20094,#20092)
|
||||
#20096=*
|
||||
properties(#20096,#20086,3,0,"constructor() {}")
|
||||
#20097=@"loc,{#10000},1,9,1,8"
|
||||
@@ -281,21 +280,21 @@ hasLocation(#20098,#20097)
|
||||
enclosingStmt(#20098,#20086)
|
||||
exprContainers(#20098,#20001)
|
||||
literals("constructor","constructor",#20098)
|
||||
exprs(#20093,9,#20096,1,"() {}")
|
||||
hasLocation(#20093,#20097)
|
||||
enclosingStmt(#20093,#20086)
|
||||
exprContainers(#20093,#20001)
|
||||
exprs(#20092,9,#20096,1,"() {}")
|
||||
hasLocation(#20092,#20097)
|
||||
enclosingStmt(#20092,#20086)
|
||||
exprContainers(#20092,#20001)
|
||||
#20099=*
|
||||
scopes(#20099,1)
|
||||
scopenodes(#20093,#20099)
|
||||
scopenodes(#20092,#20099)
|
||||
scopenesting(#20099,#20089)
|
||||
#20100=@"var;{arguments};{#20099}"
|
||||
variables(#20100,"arguments",#20099)
|
||||
isArgumentsObject(#20100)
|
||||
#20101=*
|
||||
stmts(#20101,1,#20093,-2,"{}")
|
||||
stmts(#20101,1,#20092,-2,"{}")
|
||||
hasLocation(#20101,#20097)
|
||||
stmtContainers(#20101,#20093)
|
||||
stmtContainers(#20101,#20092)
|
||||
isMethod(#20096)
|
||||
#20102=*
|
||||
stmts(#20102,26,#20001,1,"class B ... er.x;\n}")
|
||||
@@ -328,26 +327,25 @@ properties(#20107,#20102,2,8,"y = super.x;")
|
||||
locations_default(#20108,#10000,6,3,6,14)
|
||||
hasLocation(#20107,#20108)
|
||||
#20109=*
|
||||
exprs(#20109,0,#20107,0,"y")
|
||||
hasLocation(#20109,#20054)
|
||||
enclosingStmt(#20109,#20102)
|
||||
exprContainers(#20109,#20001)
|
||||
literals("y","y",#20109)
|
||||
#20110=*
|
||||
exprs(#20110,0,#20107,0,"y")
|
||||
hasLocation(#20110,#20054)
|
||||
exprContainers(#20110,#20109)
|
||||
literals("y","y",#20110)
|
||||
#20111=*
|
||||
exprs(#20111,14,#20107,1,"super.x")
|
||||
#20112=@"loc,{#10000},6,7,6,13"
|
||||
locations_default(#20112,#10000,6,7,6,13)
|
||||
hasLocation(#20111,#20112)
|
||||
exprContainers(#20111,#20110)
|
||||
exprContainers(#20111,#20109)
|
||||
#20113=*
|
||||
exprs(#20113,81,#20111,0,"super")
|
||||
hasLocation(#20113,#20058)
|
||||
exprContainers(#20113,#20110)
|
||||
exprContainers(#20113,#20109)
|
||||
#20114=*
|
||||
exprs(#20114,0,#20111,1,"x")
|
||||
hasLocation(#20114,#20062)
|
||||
exprContainers(#20114,#20110)
|
||||
exprContainers(#20114,#20109)
|
||||
literals("x","x",#20114)
|
||||
#20115=*
|
||||
properties(#20115,#20102,3,0,"constru ... rgs); }")
|
||||
@@ -360,54 +358,54 @@ hasLocation(#20117,#20116)
|
||||
enclosingStmt(#20117,#20102)
|
||||
exprContainers(#20117,#20001)
|
||||
literals("constructor","constructor",#20117)
|
||||
exprs(#20110,9,#20115,1,"(...arg ... rgs); }")
|
||||
hasLocation(#20110,#20116)
|
||||
enclosingStmt(#20110,#20102)
|
||||
exprContainers(#20110,#20001)
|
||||
exprs(#20109,9,#20115,1,"(...arg ... rgs); }")
|
||||
hasLocation(#20109,#20116)
|
||||
enclosingStmt(#20109,#20102)
|
||||
exprContainers(#20109,#20001)
|
||||
#20118=*
|
||||
scopes(#20118,1)
|
||||
scopenodes(#20110,#20118)
|
||||
scopenodes(#20109,#20118)
|
||||
scopenesting(#20118,#20105)
|
||||
#20119=@"var;{args};{#20118}"
|
||||
variables(#20119,"args",#20118)
|
||||
#20120=*
|
||||
exprs(#20120,78,#20110,0,"args")
|
||||
exprs(#20120,78,#20109,0,"args")
|
||||
hasLocation(#20120,#20116)
|
||||
exprContainers(#20120,#20110)
|
||||
exprContainers(#20120,#20109)
|
||||
literals("args","args",#20120)
|
||||
decl(#20120,#20119)
|
||||
#20121=@"var;{arguments};{#20118}"
|
||||
variables(#20121,"arguments",#20118)
|
||||
isArgumentsObject(#20121)
|
||||
hasRestParameter(#20110)
|
||||
hasRestParameter(#20109)
|
||||
#20122=*
|
||||
stmts(#20122,1,#20110,-2,"{ super(...args); }")
|
||||
stmts(#20122,1,#20109,-2,"{ super(...args); }")
|
||||
hasLocation(#20122,#20116)
|
||||
stmtContainers(#20122,#20110)
|
||||
stmtContainers(#20122,#20109)
|
||||
#20123=*
|
||||
stmts(#20123,2,#20122,0,"super(...args);")
|
||||
hasLocation(#20123,#20116)
|
||||
stmtContainers(#20123,#20110)
|
||||
stmtContainers(#20123,#20109)
|
||||
#20124=*
|
||||
exprs(#20124,13,#20123,0,"super(...args)")
|
||||
hasLocation(#20124,#20116)
|
||||
enclosingStmt(#20124,#20123)
|
||||
exprContainers(#20124,#20110)
|
||||
exprContainers(#20124,#20109)
|
||||
#20125=*
|
||||
exprs(#20125,81,#20124,-1,"super")
|
||||
hasLocation(#20125,#20116)
|
||||
enclosingStmt(#20125,#20123)
|
||||
exprContainers(#20125,#20110)
|
||||
exprContainers(#20125,#20109)
|
||||
#20126=*
|
||||
exprs(#20126,66,#20124,0,"...args")
|
||||
hasLocation(#20126,#20116)
|
||||
enclosingStmt(#20126,#20123)
|
||||
exprContainers(#20126,#20110)
|
||||
exprContainers(#20126,#20109)
|
||||
#20127=*
|
||||
exprs(#20127,79,#20126,0,"args")
|
||||
hasLocation(#20127,#20116)
|
||||
enclosingStmt(#20127,#20123)
|
||||
exprContainers(#20127,#20110)
|
||||
exprContainers(#20127,#20109)
|
||||
literals("args","args",#20127)
|
||||
bind(#20127,#20119)
|
||||
isMethod(#20115)
|
||||
@@ -435,12 +433,11 @@ properties(#20132,#20128,2,8,"async;")
|
||||
locations_default(#20133,#10000,10,3,10,8)
|
||||
hasLocation(#20132,#20133)
|
||||
#20134=*
|
||||
exprs(#20134,0,#20132,0,"async")
|
||||
hasLocation(#20134,#20073)
|
||||
enclosingStmt(#20134,#20128)
|
||||
exprContainers(#20134,#20001)
|
||||
literals("async","async",#20134)
|
||||
#20135=*
|
||||
exprs(#20135,0,#20132,0,"async")
|
||||
hasLocation(#20135,#20073)
|
||||
exprContainers(#20135,#20134)
|
||||
literals("async","async",#20135)
|
||||
#20136=*
|
||||
properties(#20136,#20128,3,0,"constructor() {}")
|
||||
#20137=@"loc,{#10000},9,9,9,8"
|
||||
@@ -452,21 +449,21 @@ hasLocation(#20138,#20137)
|
||||
enclosingStmt(#20138,#20128)
|
||||
exprContainers(#20138,#20001)
|
||||
literals("constructor","constructor",#20138)
|
||||
exprs(#20135,9,#20136,1,"() {}")
|
||||
hasLocation(#20135,#20137)
|
||||
enclosingStmt(#20135,#20128)
|
||||
exprContainers(#20135,#20001)
|
||||
exprs(#20134,9,#20136,1,"() {}")
|
||||
hasLocation(#20134,#20137)
|
||||
enclosingStmt(#20134,#20128)
|
||||
exprContainers(#20134,#20001)
|
||||
#20139=*
|
||||
scopes(#20139,1)
|
||||
scopenodes(#20135,#20139)
|
||||
scopenodes(#20134,#20139)
|
||||
scopenesting(#20139,#20131)
|
||||
#20140=@"var;{arguments};{#20139}"
|
||||
variables(#20140,"arguments",#20139)
|
||||
isArgumentsObject(#20140)
|
||||
#20141=*
|
||||
stmts(#20141,1,#20135,-2,"{}")
|
||||
stmts(#20141,1,#20134,-2,"{}")
|
||||
hasLocation(#20141,#20137)
|
||||
stmtContainers(#20141,#20135)
|
||||
stmtContainers(#20141,#20134)
|
||||
isMethod(#20136)
|
||||
#20142=*
|
||||
entry_cfg_node(#20142,#20001)
|
||||
@@ -476,31 +473,31 @@ hasLocation(#20142,#20143)
|
||||
#20144=*
|
||||
exit_cfg_node(#20144,#20001)
|
||||
hasLocation(#20144,#20078)
|
||||
successor(#20134,#20132)
|
||||
successor(#20135,#20136)
|
||||
successor(#20135,#20132)
|
||||
successor(#20134,#20136)
|
||||
#20145=*
|
||||
entry_cfg_node(#20145,#20135)
|
||||
entry_cfg_node(#20145,#20134)
|
||||
hasLocation(#20145,#20137)
|
||||
successor(#20132,#20141)
|
||||
#20146=*
|
||||
exit_cfg_node(#20146,#20135)
|
||||
exit_cfg_node(#20146,#20134)
|
||||
hasLocation(#20146,#20137)
|
||||
successor(#20141,#20146)
|
||||
successor(#20145,#20134)
|
||||
successor(#20138,#20135)
|
||||
successor(#20145,#20135)
|
||||
successor(#20138,#20134)
|
||||
successor(#20136,#20128)
|
||||
successor(#20130,#20138)
|
||||
successor(#20128,#20144)
|
||||
successor(#20114,#20111)
|
||||
successor(#20113,#20114)
|
||||
successor(#20111,#20107)
|
||||
successor(#20109,#20113)
|
||||
successor(#20110,#20115)
|
||||
successor(#20110,#20113)
|
||||
successor(#20109,#20115)
|
||||
#20147=*
|
||||
entry_cfg_node(#20147,#20110)
|
||||
entry_cfg_node(#20147,#20109)
|
||||
hasLocation(#20147,#20116)
|
||||
#20148=*
|
||||
exit_cfg_node(#20148,#20110)
|
||||
exit_cfg_node(#20148,#20109)
|
||||
hasLocation(#20148,#20116)
|
||||
successor(#20122,#20123)
|
||||
successor(#20123,#20125)
|
||||
@@ -508,27 +505,27 @@ successor(#20127,#20126)
|
||||
successor(#20126,#20124)
|
||||
successor(#20125,#20127)
|
||||
successor(#20107,#20148)
|
||||
successor(#20124,#20109)
|
||||
successor(#20124,#20110)
|
||||
successor(#20120,#20122)
|
||||
successor(#20147,#20120)
|
||||
successor(#20117,#20110)
|
||||
successor(#20117,#20109)
|
||||
successor(#20115,#20102)
|
||||
successor(#20106,#20117)
|
||||
successor(#20104,#20106)
|
||||
successor(#20102,#20130)
|
||||
successor(#20094,#20090)
|
||||
successor(#20092,#20094)
|
||||
successor(#20093,#20096)
|
||||
successor(#20093,#20094)
|
||||
successor(#20092,#20096)
|
||||
#20149=*
|
||||
entry_cfg_node(#20149,#20093)
|
||||
entry_cfg_node(#20149,#20092)
|
||||
hasLocation(#20149,#20097)
|
||||
successor(#20090,#20101)
|
||||
#20150=*
|
||||
exit_cfg_node(#20150,#20093)
|
||||
exit_cfg_node(#20150,#20092)
|
||||
hasLocation(#20150,#20097)
|
||||
successor(#20101,#20150)
|
||||
successor(#20149,#20092)
|
||||
successor(#20098,#20093)
|
||||
successor(#20149,#20093)
|
||||
successor(#20098,#20092)
|
||||
successor(#20096,#20086)
|
||||
successor(#20088,#20098)
|
||||
successor(#20086,#20104)
|
||||
|
||||
@@ -542,12 +542,11 @@ properties(#20197,#20193,2,8,"x: T;")
|
||||
locations_default(#20198,#10000,7,3,7,7)
|
||||
hasLocation(#20197,#20198)
|
||||
#20199=*
|
||||
exprs(#20199,0,#20197,0,"x")
|
||||
hasLocation(#20199,#20082)
|
||||
enclosingStmt(#20199,#20193)
|
||||
exprContainers(#20199,#20001)
|
||||
literals("x","x",#20199)
|
||||
#20200=*
|
||||
exprs(#20200,0,#20197,0,"x")
|
||||
hasLocation(#20200,#20082)
|
||||
exprContainers(#20200,#20199)
|
||||
literals("x","x",#20200)
|
||||
#20201=*
|
||||
properties(#20201,#20193,3,0,"m() : Point {}")
|
||||
#20202=@"loc,{#10000},8,3,8,16"
|
||||
@@ -591,21 +590,21 @@ hasLocation(#20212,#20211)
|
||||
enclosingStmt(#20212,#20193)
|
||||
exprContainers(#20212,#20001)
|
||||
literals("constructor","constructor",#20212)
|
||||
exprs(#20200,9,#20210,1,"() {}")
|
||||
hasLocation(#20200,#20211)
|
||||
enclosingStmt(#20200,#20193)
|
||||
exprContainers(#20200,#20001)
|
||||
exprs(#20199,9,#20210,1,"() {}")
|
||||
hasLocation(#20199,#20211)
|
||||
enclosingStmt(#20199,#20193)
|
||||
exprContainers(#20199,#20001)
|
||||
#20213=*
|
||||
scopes(#20213,1)
|
||||
scopenodes(#20200,#20213)
|
||||
scopenodes(#20199,#20213)
|
||||
scopenesting(#20213,#20196)
|
||||
#20214=@"var;{arguments};{#20213}"
|
||||
variables(#20214,"arguments",#20213)
|
||||
isArgumentsObject(#20214)
|
||||
#20215=*
|
||||
stmts(#20215,1,#20200,-2,"{}")
|
||||
stmts(#20215,1,#20199,-2,"{}")
|
||||
hasLocation(#20215,#20211)
|
||||
stmtContainers(#20215,#20200)
|
||||
stmtContainers(#20215,#20199)
|
||||
isMethod(#20210)
|
||||
#20216=*
|
||||
stmts(#20216,17,#20001,1,"functio ... :\n }\n}")
|
||||
@@ -692,18 +691,18 @@ successor(#20227,#20229)
|
||||
successor(#20229,#20231)
|
||||
successor(#20231,#20237)
|
||||
successor(#20235,#20221)
|
||||
successor(#20199,#20197)
|
||||
successor(#20200,#20210)
|
||||
successor(#20200,#20197)
|
||||
successor(#20199,#20210)
|
||||
#20239=*
|
||||
entry_cfg_node(#20239,#20200)
|
||||
entry_cfg_node(#20239,#20199)
|
||||
hasLocation(#20239,#20211)
|
||||
successor(#20197,#20215)
|
||||
#20240=*
|
||||
exit_cfg_node(#20240,#20200)
|
||||
exit_cfg_node(#20240,#20199)
|
||||
hasLocation(#20240,#20211)
|
||||
successor(#20215,#20240)
|
||||
successor(#20239,#20199)
|
||||
successor(#20212,#20200)
|
||||
successor(#20239,#20200)
|
||||
successor(#20212,#20199)
|
||||
successor(#20210,#20193)
|
||||
successor(#20204,#20201)
|
||||
#20241=*
|
||||
|
||||
@@ -194,12 +194,11 @@ properties(#20065,#20061,2,8,"+x: int")
|
||||
locations_default(#20066,#10000,6,3,6,9)
|
||||
hasLocation(#20065,#20066)
|
||||
#20067=*
|
||||
exprs(#20067,0,#20065,0,"x")
|
||||
hasLocation(#20067,#20049)
|
||||
enclosingStmt(#20067,#20061)
|
||||
exprContainers(#20067,#20001)
|
||||
literals("x","x",#20067)
|
||||
#20068=*
|
||||
exprs(#20068,0,#20065,0,"x")
|
||||
hasLocation(#20068,#20049)
|
||||
exprContainers(#20068,#20067)
|
||||
literals("x","x",#20068)
|
||||
#20069=*
|
||||
properties(#20069,#20061,3,0,"constructor() {}")
|
||||
#20070=@"loc,{#10000},5,11,5,10"
|
||||
@@ -211,21 +210,21 @@ hasLocation(#20071,#20070)
|
||||
enclosingStmt(#20071,#20061)
|
||||
exprContainers(#20071,#20001)
|
||||
literals("constructor","constructor",#20071)
|
||||
exprs(#20068,9,#20069,1,"() {}")
|
||||
hasLocation(#20068,#20070)
|
||||
enclosingStmt(#20068,#20061)
|
||||
exprContainers(#20068,#20001)
|
||||
exprs(#20067,9,#20069,1,"() {}")
|
||||
hasLocation(#20067,#20070)
|
||||
enclosingStmt(#20067,#20061)
|
||||
exprContainers(#20067,#20001)
|
||||
#20072=*
|
||||
scopes(#20072,1)
|
||||
scopenodes(#20068,#20072)
|
||||
scopenodes(#20067,#20072)
|
||||
scopenesting(#20072,#20064)
|
||||
#20073=@"var;{arguments};{#20072}"
|
||||
variables(#20073,"arguments",#20072)
|
||||
isArgumentsObject(#20073)
|
||||
#20074=*
|
||||
stmts(#20074,1,#20068,-2,"{}")
|
||||
stmts(#20074,1,#20067,-2,"{}")
|
||||
hasLocation(#20074,#20070)
|
||||
stmtContainers(#20074,#20068)
|
||||
stmtContainers(#20074,#20067)
|
||||
isMethod(#20069)
|
||||
#20075=*
|
||||
entry_cfg_node(#20075,#20001)
|
||||
@@ -235,18 +234,18 @@ hasLocation(#20075,#20076)
|
||||
#20077=*
|
||||
exit_cfg_node(#20077,#20001)
|
||||
hasLocation(#20077,#20056)
|
||||
successor(#20067,#20065)
|
||||
successor(#20068,#20069)
|
||||
successor(#20068,#20065)
|
||||
successor(#20067,#20069)
|
||||
#20078=*
|
||||
entry_cfg_node(#20078,#20068)
|
||||
entry_cfg_node(#20078,#20067)
|
||||
hasLocation(#20078,#20070)
|
||||
successor(#20065,#20074)
|
||||
#20079=*
|
||||
exit_cfg_node(#20079,#20068)
|
||||
exit_cfg_node(#20079,#20067)
|
||||
hasLocation(#20079,#20070)
|
||||
successor(#20074,#20079)
|
||||
successor(#20078,#20067)
|
||||
successor(#20071,#20068)
|
||||
successor(#20078,#20068)
|
||||
successor(#20071,#20067)
|
||||
successor(#20069,#20061)
|
||||
successor(#20063,#20071)
|
||||
successor(#20061,#20077)
|
||||
|
||||
1
javascript/extractor/tests/ts/input/exportclass.ts
Normal file
1
javascript/extractor/tests/ts/input/exportclass.ts
Normal file
@@ -0,0 +1 @@
|
||||
export class {}
|
||||
3
javascript/extractor/tests/ts/input/optionalChaining.ts
Normal file
3
javascript/extractor/tests/ts/input/optionalChaining.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
base?.x.y;
|
||||
base?.(x).y;
|
||||
base?.[z].y;
|
||||
@@ -190,16 +190,15 @@ properties(#20065,#20062,2,8,"y = 42;")
|
||||
locations_default(#20066,#10000,2,3,2,9)
|
||||
hasLocation(#20065,#20066)
|
||||
#20067=*
|
||||
exprs(#20067,0,#20065,0,"y")
|
||||
hasLocation(#20067,#20019)
|
||||
enclosingStmt(#20067,#20062)
|
||||
exprContainers(#20067,#20001)
|
||||
literals("y","y",#20067)
|
||||
#20068=*
|
||||
exprs(#20068,0,#20065,0,"y")
|
||||
hasLocation(#20068,#20019)
|
||||
exprContainers(#20068,#20067)
|
||||
literals("y","y",#20068)
|
||||
#20069=*
|
||||
exprs(#20069,3,#20065,1,"42")
|
||||
hasLocation(#20069,#20023)
|
||||
exprContainers(#20069,#20068)
|
||||
exprContainers(#20069,#20067)
|
||||
literals("42","42",#20069)
|
||||
#20070=*
|
||||
properties(#20070,#20062,3,0,"constru ... tring);")
|
||||
@@ -212,29 +211,29 @@ hasLocation(#20072,#20071)
|
||||
enclosingStmt(#20072,#20062)
|
||||
exprContainers(#20072,#20001)
|
||||
literals("constructor","constructor",#20072)
|
||||
exprs(#20068,9,#20070,1,"constru ... tring);")
|
||||
hasLocation(#20068,#20071)
|
||||
enclosingStmt(#20068,#20062)
|
||||
exprContainers(#20068,#20001)
|
||||
exprs(#20067,9,#20070,1,"constru ... tring);")
|
||||
hasLocation(#20067,#20071)
|
||||
enclosingStmt(#20067,#20062)
|
||||
exprContainers(#20067,#20001)
|
||||
#20073=*
|
||||
scopes(#20073,1)
|
||||
scopenodes(#20068,#20073)
|
||||
scopenodes(#20067,#20073)
|
||||
scopenesting(#20073,#20064)
|
||||
#20074=@"var;{x};{#20073}"
|
||||
variables(#20074,"x",#20073)
|
||||
#20075=*
|
||||
exprs(#20075,78,#20068,0,"x")
|
||||
exprs(#20075,78,#20067,0,"x")
|
||||
hasLocation(#20075,#20031)
|
||||
exprContainers(#20075,#20068)
|
||||
exprContainers(#20075,#20067)
|
||||
literals("x","x",#20075)
|
||||
decl(#20075,#20074)
|
||||
#20076=@"var;{arguments};{#20073}"
|
||||
variables(#20076,"arguments",#20073)
|
||||
isArgumentsObject(#20076)
|
||||
#20077=*
|
||||
typeexprs(#20077,2,#20068,-6,"string")
|
||||
typeexprs(#20077,2,#20067,-6,"string")
|
||||
hasLocation(#20077,#20035)
|
||||
exprContainers(#20077,#20068)
|
||||
exprContainers(#20077,#20067)
|
||||
literals("string","string",#20077)
|
||||
isMethod(#20070)
|
||||
#20078=*
|
||||
@@ -289,7 +288,7 @@ hasLocation(#20089,#20090)
|
||||
exit_cfg_node(#20091,#20001)
|
||||
hasLocation(#20091,#20058)
|
||||
successor(#20069,#20065)
|
||||
successor(#20067,#20069)
|
||||
successor(#20068,#20069)
|
||||
successor(#20081,#20078)
|
||||
#20092=*
|
||||
entry_cfg_node(#20092,#20081)
|
||||
@@ -303,7 +302,7 @@ exit_cfg_node(#20094,#20081)
|
||||
locations_default(#20095,#10000,4,24,4,23)
|
||||
hasLocation(#20094,#20095)
|
||||
successor(#20087,#20094)
|
||||
successor(#20084,#20067)
|
||||
successor(#20084,#20068)
|
||||
successor(#20092,#20084)
|
||||
successor(#20080,#20081)
|
||||
successor(#20078,#20062)
|
||||
|
||||
@@ -450,12 +450,11 @@ literals("fieldDecorator","fieldDecorator",#20148)
|
||||
variables(#20149,"fieldDecorator",#20000)
|
||||
bind(#20148,#20149)
|
||||
#20150=*
|
||||
exprs(#20150,0,#20144,0,"field")
|
||||
hasLocation(#20150,#20071)
|
||||
enclosingStmt(#20150,#20118)
|
||||
exprContainers(#20150,#20001)
|
||||
literals("field","field",#20150)
|
||||
#20151=*
|
||||
exprs(#20151,0,#20144,0,"field")
|
||||
hasLocation(#20151,#20071)
|
||||
exprContainers(#20151,#20150)
|
||||
literals("field","field",#20151)
|
||||
#20152=*
|
||||
typeexprs(#20152,2,#20144,2,"number")
|
||||
hasLocation(#20152,#20075)
|
||||
@@ -473,21 +472,21 @@ hasLocation(#20155,#20154)
|
||||
enclosingStmt(#20155,#20118)
|
||||
exprContainers(#20155,#20001)
|
||||
literals("constructor","constructor",#20155)
|
||||
exprs(#20151,9,#20153,1,"() {}")
|
||||
hasLocation(#20151,#20154)
|
||||
enclosingStmt(#20151,#20118)
|
||||
exprContainers(#20151,#20001)
|
||||
exprs(#20150,9,#20153,1,"() {}")
|
||||
hasLocation(#20150,#20154)
|
||||
enclosingStmt(#20150,#20118)
|
||||
exprContainers(#20150,#20001)
|
||||
#20156=*
|
||||
scopes(#20156,1)
|
||||
scopenodes(#20151,#20156)
|
||||
scopenodes(#20150,#20156)
|
||||
scopenesting(#20156,#20124)
|
||||
#20157=@"var;{arguments};{#20156}"
|
||||
variables(#20157,"arguments",#20156)
|
||||
isArgumentsObject(#20157)
|
||||
#20158=*
|
||||
stmts(#20158,1,#20151,-2,"{}")
|
||||
stmts(#20158,1,#20150,-2,"{}")
|
||||
hasLocation(#20158,#20154)
|
||||
stmtContainers(#20158,#20151)
|
||||
stmtContainers(#20158,#20150)
|
||||
isMethod(#20153)
|
||||
#20159=*
|
||||
stmts(#20159,17,#20001,1,"@functi ... un() {}")
|
||||
@@ -615,18 +614,18 @@ locations_default(#20189,#10000,11,18,11,17)
|
||||
hasLocation(#20188,#20189)
|
||||
successor(#20164,#20188)
|
||||
successor(#20186,#20164)
|
||||
successor(#20150,#20144)
|
||||
successor(#20151,#20153)
|
||||
successor(#20151,#20144)
|
||||
successor(#20150,#20153)
|
||||
#20190=*
|
||||
entry_cfg_node(#20190,#20151)
|
||||
entry_cfg_node(#20190,#20150)
|
||||
hasLocation(#20190,#20154)
|
||||
successor(#20144,#20158)
|
||||
#20191=*
|
||||
exit_cfg_node(#20191,#20151)
|
||||
exit_cfg_node(#20191,#20150)
|
||||
hasLocation(#20191,#20154)
|
||||
successor(#20158,#20191)
|
||||
successor(#20190,#20150)
|
||||
successor(#20155,#20151)
|
||||
successor(#20190,#20151)
|
||||
successor(#20155,#20150)
|
||||
successor(#20153,#20118)
|
||||
successor(#20132,#20125)
|
||||
#20192=*
|
||||
|
||||
114
javascript/extractor/tests/ts/output/trap/exportclass.ts.trap
Normal file
114
javascript/extractor/tests/ts/output/trap/exportclass.ts.trap
Normal file
@@ -0,0 +1,114 @@
|
||||
#10000=@"/exportclass.ts;sourcefile"
|
||||
files(#10000,"/exportclass.ts","exportclass","ts",0)
|
||||
#10001=@"/;folder"
|
||||
folders(#10001,"/","")
|
||||
containerparent(#10001,#10000)
|
||||
#10002=@"loc,{#10000},0,0,0,0"
|
||||
locations_default(#10002,#10000,0,0,0,0)
|
||||
hasLocation(#10000,#10002)
|
||||
#20000=@"global_scope"
|
||||
scopes(#20000,0)
|
||||
#20001=@"script;{#10000},1,1"
|
||||
#20002=*
|
||||
lines(#20002,#20001,"export class {}","
|
||||
")
|
||||
#20003=@"loc,{#10000},1,1,1,15"
|
||||
locations_default(#20003,#10000,1,1,1,15)
|
||||
hasLocation(#20002,#20003)
|
||||
numlines(#20001,1,1,0)
|
||||
#20004=*
|
||||
tokeninfo(#20004,7,#20001,0,"export")
|
||||
#20005=@"loc,{#10000},1,1,1,6"
|
||||
locations_default(#20005,#10000,1,1,1,6)
|
||||
hasLocation(#20004,#20005)
|
||||
#20006=*
|
||||
tokeninfo(#20006,7,#20001,1,"class")
|
||||
#20007=@"loc,{#10000},1,8,1,12"
|
||||
locations_default(#20007,#10000,1,8,1,12)
|
||||
hasLocation(#20006,#20007)
|
||||
#20008=*
|
||||
tokeninfo(#20008,8,#20001,2,"{")
|
||||
#20009=@"loc,{#10000},1,14,1,14"
|
||||
locations_default(#20009,#10000,1,14,1,14)
|
||||
hasLocation(#20008,#20009)
|
||||
#20010=*
|
||||
tokeninfo(#20010,8,#20001,3,"}")
|
||||
#20011=@"loc,{#10000},1,15,1,15"
|
||||
locations_default(#20011,#10000,1,15,1,15)
|
||||
hasLocation(#20010,#20011)
|
||||
#20012=*
|
||||
tokeninfo(#20012,0,#20001,4,"")
|
||||
#20013=@"loc,{#10000},2,1,2,0"
|
||||
locations_default(#20013,#10000,2,1,2,0)
|
||||
hasLocation(#20012,#20013)
|
||||
toplevels(#20001,0)
|
||||
#20014=@"loc,{#10000},1,1,2,0"
|
||||
locations_default(#20014,#10000,1,1,2,0)
|
||||
hasLocation(#20001,#20014)
|
||||
#20015=@"module;{#10000},1,1"
|
||||
scopes(#20015,3)
|
||||
scopenodes(#20001,#20015)
|
||||
scopenesting(#20015,#20000)
|
||||
isModule(#20001)
|
||||
isES2015Module(#20001)
|
||||
#20016=*
|
||||
stmts(#20016,30,#20001,0,"export class {}")
|
||||
hasLocation(#20016,#20003)
|
||||
stmtContainers(#20016,#20001)
|
||||
#20017=*
|
||||
stmts(#20017,26,#20016,-1,"class {}")
|
||||
#20018=@"loc,{#10000},1,8,1,15"
|
||||
locations_default(#20018,#10000,1,8,1,15)
|
||||
hasLocation(#20017,#20018)
|
||||
stmtContainers(#20017,#20001)
|
||||
#20019=*
|
||||
properties(#20019,#20017,2,0,"constructor() {}")
|
||||
#20020=@"loc,{#10000},1,6,1,5"
|
||||
locations_default(#20020,#10000,1,6,1,5)
|
||||
hasLocation(#20019,#20020)
|
||||
#20021=*
|
||||
exprs(#20021,0,#20019,0,"constructor")
|
||||
hasLocation(#20021,#20020)
|
||||
enclosingStmt(#20021,#20017)
|
||||
exprContainers(#20021,#20001)
|
||||
literals("constructor","constructor",#20021)
|
||||
#20022=*
|
||||
exprs(#20022,9,#20019,1,"() {}")
|
||||
hasLocation(#20022,#20020)
|
||||
enclosingStmt(#20022,#20017)
|
||||
exprContainers(#20022,#20001)
|
||||
#20023=*
|
||||
scopes(#20023,1)
|
||||
scopenodes(#20022,#20023)
|
||||
scopenesting(#20023,#20015)
|
||||
#20024=@"var;{arguments};{#20023}"
|
||||
variables(#20024,"arguments",#20023)
|
||||
isArgumentsObject(#20024)
|
||||
#20025=*
|
||||
stmts(#20025,1,#20022,-2,"{}")
|
||||
hasLocation(#20025,#20020)
|
||||
stmtContainers(#20025,#20022)
|
||||
isMethod(#20019)
|
||||
#20026=*
|
||||
entry_cfg_node(#20026,#20001)
|
||||
#20027=@"loc,{#10000},1,1,1,0"
|
||||
locations_default(#20027,#10000,1,1,1,0)
|
||||
hasLocation(#20026,#20027)
|
||||
#20028=*
|
||||
exit_cfg_node(#20028,#20001)
|
||||
hasLocation(#20028,#20013)
|
||||
successor(#20022,#20019)
|
||||
#20029=*
|
||||
entry_cfg_node(#20029,#20022)
|
||||
hasLocation(#20029,#20020)
|
||||
#20030=*
|
||||
exit_cfg_node(#20030,#20022)
|
||||
hasLocation(#20030,#20020)
|
||||
successor(#20025,#20030)
|
||||
successor(#20029,#20025)
|
||||
successor(#20021,#20022)
|
||||
successor(#20019,#20017)
|
||||
successor(#20017,#20028)
|
||||
successor(#20026,#20016)
|
||||
numlines(#10000,1,1,0)
|
||||
filetype(#10000,"typescript")
|
||||
@@ -895,12 +895,11 @@ properties(#20301,#20260,6,8,"abstract x: number;")
|
||||
locations_default(#20302,#10000,14,3,14,21)
|
||||
hasLocation(#20301,#20302)
|
||||
#20303=*
|
||||
exprs(#20303,0,#20301,0,"x")
|
||||
hasLocation(#20303,#20159)
|
||||
enclosingStmt(#20303,#20260)
|
||||
exprContainers(#20303,#20001)
|
||||
literals("x","x",#20303)
|
||||
#20304=*
|
||||
exprs(#20304,0,#20301,0,"x")
|
||||
hasLocation(#20304,#20159)
|
||||
exprContainers(#20304,#20303)
|
||||
literals("x","x",#20304)
|
||||
isAbstractMember(#20301)
|
||||
#20305=*
|
||||
typeexprs(#20305,2,#20301,2,"number")
|
||||
@@ -919,21 +918,21 @@ hasLocation(#20308,#20307)
|
||||
enclosingStmt(#20308,#20260)
|
||||
exprContainers(#20308,#20001)
|
||||
literals("constructor","constructor",#20308)
|
||||
exprs(#20304,9,#20306,1,"() {}")
|
||||
hasLocation(#20304,#20307)
|
||||
enclosingStmt(#20304,#20260)
|
||||
exprContainers(#20304,#20001)
|
||||
exprs(#20303,9,#20306,1,"() {}")
|
||||
hasLocation(#20303,#20307)
|
||||
enclosingStmt(#20303,#20260)
|
||||
exprContainers(#20303,#20001)
|
||||
#20309=*
|
||||
scopes(#20309,1)
|
||||
scopenodes(#20304,#20309)
|
||||
scopenodes(#20303,#20309)
|
||||
scopenesting(#20309,#20263)
|
||||
#20310=@"var;{arguments};{#20309}"
|
||||
variables(#20310,"arguments",#20309)
|
||||
isArgumentsObject(#20310)
|
||||
#20311=*
|
||||
stmts(#20311,1,#20304,-2,"{}")
|
||||
stmts(#20311,1,#20303,-2,"{}")
|
||||
hasLocation(#20311,#20307)
|
||||
stmtContainers(#20311,#20304)
|
||||
stmtContainers(#20311,#20303)
|
||||
isMethod(#20306)
|
||||
#20312=*
|
||||
stmts(#20312,26,#20001,2,"declare ... mber;\n}")
|
||||
@@ -1114,12 +1113,11 @@ properties(#20354,#20312,6,8,"abstract x: number;")
|
||||
locations_default(#20355,#10000,28,3,28,21)
|
||||
hasLocation(#20354,#20355)
|
||||
#20356=*
|
||||
exprs(#20356,0,#20354,0,"x")
|
||||
hasLocation(#20356,#20242)
|
||||
enclosingStmt(#20356,#20312)
|
||||
exprContainers(#20356,#20001)
|
||||
literals("x","x",#20356)
|
||||
#20357=*
|
||||
exprs(#20357,0,#20354,0,"x")
|
||||
hasLocation(#20357,#20242)
|
||||
exprContainers(#20357,#20356)
|
||||
literals("x","x",#20357)
|
||||
isAbstractMember(#20354)
|
||||
#20358=*
|
||||
typeexprs(#20358,2,#20354,2,"number")
|
||||
@@ -1138,21 +1136,21 @@ hasLocation(#20361,#20360)
|
||||
enclosingStmt(#20361,#20312)
|
||||
exprContainers(#20361,#20001)
|
||||
literals("constructor","constructor",#20361)
|
||||
exprs(#20357,9,#20359,1,"() {}")
|
||||
hasLocation(#20357,#20360)
|
||||
enclosingStmt(#20357,#20312)
|
||||
exprContainers(#20357,#20001)
|
||||
exprs(#20356,9,#20359,1,"() {}")
|
||||
hasLocation(#20356,#20360)
|
||||
enclosingStmt(#20356,#20312)
|
||||
exprContainers(#20356,#20001)
|
||||
#20362=*
|
||||
scopes(#20362,1)
|
||||
scopenodes(#20357,#20362)
|
||||
scopenodes(#20356,#20362)
|
||||
scopenesting(#20362,#20316)
|
||||
#20363=@"var;{arguments};{#20362}"
|
||||
variables(#20363,"arguments",#20362)
|
||||
isArgumentsObject(#20363)
|
||||
#20364=*
|
||||
stmts(#20364,1,#20357,-2,"{}")
|
||||
stmts(#20364,1,#20356,-2,"{}")
|
||||
hasLocation(#20364,#20360)
|
||||
stmtContainers(#20364,#20357)
|
||||
stmtContainers(#20364,#20356)
|
||||
isMethod(#20359)
|
||||
#20365=*
|
||||
entry_cfg_node(#20365,#20001)
|
||||
@@ -1163,16 +1161,16 @@ hasLocation(#20365,#20366)
|
||||
exit_cfg_node(#20367,#20001)
|
||||
hasLocation(#20367,#20251)
|
||||
successor(#20312,#20367)
|
||||
successor(#20304,#20306)
|
||||
successor(#20303,#20306)
|
||||
#20368=*
|
||||
entry_cfg_node(#20368,#20304)
|
||||
entry_cfg_node(#20368,#20303)
|
||||
hasLocation(#20368,#20307)
|
||||
#20369=*
|
||||
exit_cfg_node(#20369,#20304)
|
||||
exit_cfg_node(#20369,#20303)
|
||||
hasLocation(#20369,#20307)
|
||||
successor(#20311,#20369)
|
||||
successor(#20368,#20311)
|
||||
successor(#20308,#20304)
|
||||
successor(#20308,#20303)
|
||||
successor(#20306,#20260)
|
||||
successor(#20293,#20290)
|
||||
#20370=*
|
||||
|
||||
@@ -0,0 +1,303 @@
|
||||
#10000=@"/optionalChaining.ts;sourcefile"
|
||||
files(#10000,"/optionalChaining.ts","optionalChaining","ts",0)
|
||||
#10001=@"/;folder"
|
||||
folders(#10001,"/","")
|
||||
containerparent(#10001,#10000)
|
||||
#10002=@"loc,{#10000},0,0,0,0"
|
||||
locations_default(#10002,#10000,0,0,0,0)
|
||||
hasLocation(#10000,#10002)
|
||||
#20000=@"global_scope"
|
||||
scopes(#20000,0)
|
||||
#20001=@"script;{#10000},1,1"
|
||||
#20002=*
|
||||
lines(#20002,#20001,"base?.x.y;","
|
||||
")
|
||||
#20003=@"loc,{#10000},1,1,1,10"
|
||||
locations_default(#20003,#10000,1,1,1,10)
|
||||
hasLocation(#20002,#20003)
|
||||
#20004=*
|
||||
lines(#20004,#20001,"base?.(x).y;","
|
||||
")
|
||||
#20005=@"loc,{#10000},2,1,2,12"
|
||||
locations_default(#20005,#10000,2,1,2,12)
|
||||
hasLocation(#20004,#20005)
|
||||
#20006=*
|
||||
lines(#20006,#20001,"base?.[z].y;","
|
||||
")
|
||||
#20007=@"loc,{#10000},3,1,3,12"
|
||||
locations_default(#20007,#10000,3,1,3,12)
|
||||
hasLocation(#20006,#20007)
|
||||
numlines(#20001,3,3,0)
|
||||
#20008=*
|
||||
tokeninfo(#20008,6,#20001,0,"base")
|
||||
#20009=@"loc,{#10000},1,1,1,4"
|
||||
locations_default(#20009,#10000,1,1,1,4)
|
||||
hasLocation(#20008,#20009)
|
||||
#20010=*
|
||||
tokeninfo(#20010,8,#20001,1,"?.")
|
||||
#20011=@"loc,{#10000},1,5,1,6"
|
||||
locations_default(#20011,#10000,1,5,1,6)
|
||||
hasLocation(#20010,#20011)
|
||||
#20012=*
|
||||
tokeninfo(#20012,6,#20001,2,"x")
|
||||
#20013=@"loc,{#10000},1,7,1,7"
|
||||
locations_default(#20013,#10000,1,7,1,7)
|
||||
hasLocation(#20012,#20013)
|
||||
#20014=*
|
||||
tokeninfo(#20014,8,#20001,3,".")
|
||||
#20015=@"loc,{#10000},1,8,1,8"
|
||||
locations_default(#20015,#10000,1,8,1,8)
|
||||
hasLocation(#20014,#20015)
|
||||
#20016=*
|
||||
tokeninfo(#20016,6,#20001,4,"y")
|
||||
#20017=@"loc,{#10000},1,9,1,9"
|
||||
locations_default(#20017,#10000,1,9,1,9)
|
||||
hasLocation(#20016,#20017)
|
||||
#20018=*
|
||||
tokeninfo(#20018,8,#20001,5,";")
|
||||
#20019=@"loc,{#10000},1,10,1,10"
|
||||
locations_default(#20019,#10000,1,10,1,10)
|
||||
hasLocation(#20018,#20019)
|
||||
#20020=*
|
||||
tokeninfo(#20020,6,#20001,6,"base")
|
||||
#20021=@"loc,{#10000},2,1,2,4"
|
||||
locations_default(#20021,#10000,2,1,2,4)
|
||||
hasLocation(#20020,#20021)
|
||||
#20022=*
|
||||
tokeninfo(#20022,8,#20001,7,"?.")
|
||||
#20023=@"loc,{#10000},2,5,2,6"
|
||||
locations_default(#20023,#10000,2,5,2,6)
|
||||
hasLocation(#20022,#20023)
|
||||
#20024=*
|
||||
tokeninfo(#20024,8,#20001,8,"(")
|
||||
#20025=@"loc,{#10000},2,7,2,7"
|
||||
locations_default(#20025,#10000,2,7,2,7)
|
||||
hasLocation(#20024,#20025)
|
||||
#20026=*
|
||||
tokeninfo(#20026,6,#20001,9,"x")
|
||||
#20027=@"loc,{#10000},2,8,2,8"
|
||||
locations_default(#20027,#10000,2,8,2,8)
|
||||
hasLocation(#20026,#20027)
|
||||
#20028=*
|
||||
tokeninfo(#20028,8,#20001,10,")")
|
||||
#20029=@"loc,{#10000},2,9,2,9"
|
||||
locations_default(#20029,#10000,2,9,2,9)
|
||||
hasLocation(#20028,#20029)
|
||||
#20030=*
|
||||
tokeninfo(#20030,8,#20001,11,".")
|
||||
#20031=@"loc,{#10000},2,10,2,10"
|
||||
locations_default(#20031,#10000,2,10,2,10)
|
||||
hasLocation(#20030,#20031)
|
||||
#20032=*
|
||||
tokeninfo(#20032,6,#20001,12,"y")
|
||||
#20033=@"loc,{#10000},2,11,2,11"
|
||||
locations_default(#20033,#10000,2,11,2,11)
|
||||
hasLocation(#20032,#20033)
|
||||
#20034=*
|
||||
tokeninfo(#20034,8,#20001,13,";")
|
||||
#20035=@"loc,{#10000},2,12,2,12"
|
||||
locations_default(#20035,#10000,2,12,2,12)
|
||||
hasLocation(#20034,#20035)
|
||||
#20036=*
|
||||
tokeninfo(#20036,6,#20001,14,"base")
|
||||
#20037=@"loc,{#10000},3,1,3,4"
|
||||
locations_default(#20037,#10000,3,1,3,4)
|
||||
hasLocation(#20036,#20037)
|
||||
#20038=*
|
||||
tokeninfo(#20038,8,#20001,15,"?.")
|
||||
#20039=@"loc,{#10000},3,5,3,6"
|
||||
locations_default(#20039,#10000,3,5,3,6)
|
||||
hasLocation(#20038,#20039)
|
||||
#20040=*
|
||||
tokeninfo(#20040,8,#20001,16,"[")
|
||||
#20041=@"loc,{#10000},3,7,3,7"
|
||||
locations_default(#20041,#10000,3,7,3,7)
|
||||
hasLocation(#20040,#20041)
|
||||
#20042=*
|
||||
tokeninfo(#20042,6,#20001,17,"z")
|
||||
#20043=@"loc,{#10000},3,8,3,8"
|
||||
locations_default(#20043,#10000,3,8,3,8)
|
||||
hasLocation(#20042,#20043)
|
||||
#20044=*
|
||||
tokeninfo(#20044,8,#20001,18,"]")
|
||||
#20045=@"loc,{#10000},3,9,3,9"
|
||||
locations_default(#20045,#10000,3,9,3,9)
|
||||
hasLocation(#20044,#20045)
|
||||
#20046=*
|
||||
tokeninfo(#20046,8,#20001,19,".")
|
||||
#20047=@"loc,{#10000},3,10,3,10"
|
||||
locations_default(#20047,#10000,3,10,3,10)
|
||||
hasLocation(#20046,#20047)
|
||||
#20048=*
|
||||
tokeninfo(#20048,6,#20001,20,"y")
|
||||
#20049=@"loc,{#10000},3,11,3,11"
|
||||
locations_default(#20049,#10000,3,11,3,11)
|
||||
hasLocation(#20048,#20049)
|
||||
#20050=*
|
||||
tokeninfo(#20050,8,#20001,21,";")
|
||||
#20051=@"loc,{#10000},3,12,3,12"
|
||||
locations_default(#20051,#10000,3,12,3,12)
|
||||
hasLocation(#20050,#20051)
|
||||
#20052=*
|
||||
tokeninfo(#20052,0,#20001,22,"")
|
||||
#20053=@"loc,{#10000},4,1,4,0"
|
||||
locations_default(#20053,#10000,4,1,4,0)
|
||||
hasLocation(#20052,#20053)
|
||||
toplevels(#20001,0)
|
||||
#20054=@"loc,{#10000},1,1,4,0"
|
||||
locations_default(#20054,#10000,1,1,4,0)
|
||||
hasLocation(#20001,#20054)
|
||||
#20055=*
|
||||
stmts(#20055,2,#20001,0,"base?.x.y;")
|
||||
hasLocation(#20055,#20003)
|
||||
stmtContainers(#20055,#20001)
|
||||
#20056=*
|
||||
exprs(#20056,14,#20055,0,"base?.x.y")
|
||||
#20057=@"loc,{#10000},1,1,1,9"
|
||||
locations_default(#20057,#10000,1,1,1,9)
|
||||
hasLocation(#20056,#20057)
|
||||
enclosingStmt(#20056,#20055)
|
||||
exprContainers(#20056,#20001)
|
||||
#20058=*
|
||||
exprs(#20058,14,#20056,0,"base?.x")
|
||||
#20059=@"loc,{#10000},1,1,1,7"
|
||||
locations_default(#20059,#10000,1,1,1,7)
|
||||
hasLocation(#20058,#20059)
|
||||
enclosingStmt(#20058,#20055)
|
||||
exprContainers(#20058,#20001)
|
||||
#20060=*
|
||||
exprs(#20060,79,#20058,0,"base")
|
||||
hasLocation(#20060,#20009)
|
||||
enclosingStmt(#20060,#20055)
|
||||
exprContainers(#20060,#20001)
|
||||
literals("base","base",#20060)
|
||||
#20061=@"var;{base};{#20000}"
|
||||
variables(#20061,"base",#20000)
|
||||
bind(#20060,#20061)
|
||||
#20062=*
|
||||
exprs(#20062,0,#20058,1,"x")
|
||||
hasLocation(#20062,#20013)
|
||||
enclosingStmt(#20062,#20055)
|
||||
exprContainers(#20062,#20001)
|
||||
literals("x","x",#20062)
|
||||
isOptionalChaining(#20058)
|
||||
#20063=*
|
||||
exprs(#20063,0,#20056,1,"y")
|
||||
hasLocation(#20063,#20017)
|
||||
enclosingStmt(#20063,#20055)
|
||||
exprContainers(#20063,#20001)
|
||||
literals("y","y",#20063)
|
||||
#20064=*
|
||||
stmts(#20064,2,#20001,1,"base?.(x).y;")
|
||||
hasLocation(#20064,#20005)
|
||||
stmtContainers(#20064,#20001)
|
||||
#20065=*
|
||||
exprs(#20065,14,#20064,0,"base?.(x).y")
|
||||
#20066=@"loc,{#10000},2,1,2,11"
|
||||
locations_default(#20066,#10000,2,1,2,11)
|
||||
hasLocation(#20065,#20066)
|
||||
enclosingStmt(#20065,#20064)
|
||||
exprContainers(#20065,#20001)
|
||||
#20067=*
|
||||
exprs(#20067,13,#20065,0,"base?.(x)")
|
||||
#20068=@"loc,{#10000},2,1,2,9"
|
||||
locations_default(#20068,#10000,2,1,2,9)
|
||||
hasLocation(#20067,#20068)
|
||||
enclosingStmt(#20067,#20064)
|
||||
exprContainers(#20067,#20001)
|
||||
#20069=*
|
||||
exprs(#20069,79,#20067,-1,"base")
|
||||
hasLocation(#20069,#20021)
|
||||
enclosingStmt(#20069,#20064)
|
||||
exprContainers(#20069,#20001)
|
||||
literals("base","base",#20069)
|
||||
bind(#20069,#20061)
|
||||
#20070=*
|
||||
exprs(#20070,79,#20067,0,"x")
|
||||
hasLocation(#20070,#20027)
|
||||
enclosingStmt(#20070,#20064)
|
||||
exprContainers(#20070,#20001)
|
||||
literals("x","x",#20070)
|
||||
#20071=@"var;{x};{#20000}"
|
||||
variables(#20071,"x",#20000)
|
||||
bind(#20070,#20071)
|
||||
isOptionalChaining(#20067)
|
||||
#20072=*
|
||||
exprs(#20072,0,#20065,1,"y")
|
||||
hasLocation(#20072,#20033)
|
||||
enclosingStmt(#20072,#20064)
|
||||
exprContainers(#20072,#20001)
|
||||
literals("y","y",#20072)
|
||||
#20073=*
|
||||
stmts(#20073,2,#20001,2,"base?.[z].y;")
|
||||
hasLocation(#20073,#20007)
|
||||
stmtContainers(#20073,#20001)
|
||||
#20074=*
|
||||
exprs(#20074,14,#20073,0,"base?.[z].y")
|
||||
#20075=@"loc,{#10000},3,1,3,11"
|
||||
locations_default(#20075,#10000,3,1,3,11)
|
||||
hasLocation(#20074,#20075)
|
||||
enclosingStmt(#20074,#20073)
|
||||
exprContainers(#20074,#20001)
|
||||
#20076=*
|
||||
exprs(#20076,15,#20074,0,"base?.[z]")
|
||||
#20077=@"loc,{#10000},3,1,3,9"
|
||||
locations_default(#20077,#10000,3,1,3,9)
|
||||
hasLocation(#20076,#20077)
|
||||
enclosingStmt(#20076,#20073)
|
||||
exprContainers(#20076,#20001)
|
||||
#20078=*
|
||||
exprs(#20078,79,#20076,0,"base")
|
||||
hasLocation(#20078,#20037)
|
||||
enclosingStmt(#20078,#20073)
|
||||
exprContainers(#20078,#20001)
|
||||
literals("base","base",#20078)
|
||||
bind(#20078,#20061)
|
||||
#20079=*
|
||||
exprs(#20079,79,#20076,1,"z")
|
||||
hasLocation(#20079,#20043)
|
||||
enclosingStmt(#20079,#20073)
|
||||
exprContainers(#20079,#20001)
|
||||
literals("z","z",#20079)
|
||||
#20080=@"var;{z};{#20000}"
|
||||
variables(#20080,"z",#20000)
|
||||
bind(#20079,#20080)
|
||||
isOptionalChaining(#20076)
|
||||
#20081=*
|
||||
exprs(#20081,0,#20074,1,"y")
|
||||
hasLocation(#20081,#20049)
|
||||
enclosingStmt(#20081,#20073)
|
||||
exprContainers(#20081,#20001)
|
||||
literals("y","y",#20081)
|
||||
#20082=*
|
||||
entry_cfg_node(#20082,#20001)
|
||||
#20083=@"loc,{#10000},1,1,1,0"
|
||||
locations_default(#20083,#10000,1,1,1,0)
|
||||
hasLocation(#20082,#20083)
|
||||
#20084=*
|
||||
exit_cfg_node(#20084,#20001)
|
||||
hasLocation(#20084,#20053)
|
||||
successor(#20073,#20078)
|
||||
successor(#20081,#20074)
|
||||
successor(#20079,#20076)
|
||||
successor(#20078,#20079)
|
||||
successor(#20076,#20081)
|
||||
successor(#20078,#20084)
|
||||
successor(#20074,#20084)
|
||||
successor(#20064,#20069)
|
||||
successor(#20072,#20065)
|
||||
successor(#20070,#20067)
|
||||
successor(#20069,#20070)
|
||||
successor(#20067,#20072)
|
||||
successor(#20069,#20073)
|
||||
successor(#20065,#20073)
|
||||
successor(#20055,#20060)
|
||||
successor(#20063,#20056)
|
||||
successor(#20062,#20058)
|
||||
successor(#20060,#20062)
|
||||
successor(#20058,#20063)
|
||||
successor(#20060,#20064)
|
||||
successor(#20056,#20064)
|
||||
successor(#20082,#20055)
|
||||
numlines(#10000,3,3,0)
|
||||
filetype(#10000,"typescript")
|
||||
@@ -4229,12 +4229,11 @@ properties(#21361,#21350,2,8,"field: T")
|
||||
locations_default(#21362,#10000,55,3,55,10)
|
||||
hasLocation(#21361,#21362)
|
||||
#21363=*
|
||||
exprs(#21363,0,#21361,0,"field")
|
||||
hasLocation(#21363,#20821)
|
||||
enclosingStmt(#21363,#21350)
|
||||
exprContainers(#21363,#20001)
|
||||
literals("field","field",#21363)
|
||||
#21364=*
|
||||
exprs(#21364,0,#21361,0,"field")
|
||||
hasLocation(#21364,#20821)
|
||||
exprContainers(#21364,#21363)
|
||||
literals("field","field",#21364)
|
||||
#21365=*
|
||||
typeexprs(#21365,0,#21361,2,"T")
|
||||
hasLocation(#21365,#20825)
|
||||
@@ -4298,21 +4297,21 @@ hasLocation(#21378,#21377)
|
||||
enclosingStmt(#21378,#21350)
|
||||
exprContainers(#21378,#20001)
|
||||
literals("constructor","constructor",#21378)
|
||||
exprs(#21364,9,#21376,1,"() {}")
|
||||
hasLocation(#21364,#21377)
|
||||
enclosingStmt(#21364,#21350)
|
||||
exprContainers(#21364,#20001)
|
||||
exprs(#21363,9,#21376,1,"() {}")
|
||||
hasLocation(#21363,#21377)
|
||||
enclosingStmt(#21363,#21350)
|
||||
exprContainers(#21363,#20001)
|
||||
#21379=*
|
||||
scopes(#21379,1)
|
||||
scopenodes(#21364,#21379)
|
||||
scopenodes(#21363,#21379)
|
||||
scopenesting(#21379,#21353)
|
||||
#21380=@"var;{arguments};{#21379}"
|
||||
variables(#21380,"arguments",#21379)
|
||||
isArgumentsObject(#21380)
|
||||
#21381=*
|
||||
stmts(#21381,1,#21364,-2,"{}")
|
||||
stmts(#21381,1,#21363,-2,"{}")
|
||||
hasLocation(#21381,#21377)
|
||||
stmtContainers(#21381,#21364)
|
||||
stmtContainers(#21381,#21363)
|
||||
isMethod(#21376)
|
||||
#21382=*
|
||||
stmts(#21382,26,#20001,45,"class Fish {}")
|
||||
@@ -4653,18 +4652,18 @@ successor(#21387,#21388)
|
||||
successor(#21385,#21382)
|
||||
successor(#21383,#21387)
|
||||
successor(#21382,#21393)
|
||||
successor(#21363,#21361)
|
||||
successor(#21364,#21376)
|
||||
successor(#21364,#21361)
|
||||
successor(#21363,#21376)
|
||||
#21461=*
|
||||
entry_cfg_node(#21461,#21364)
|
||||
entry_cfg_node(#21461,#21363)
|
||||
hasLocation(#21461,#21377)
|
||||
successor(#21361,#21381)
|
||||
#21462=*
|
||||
exit_cfg_node(#21462,#21364)
|
||||
exit_cfg_node(#21462,#21363)
|
||||
hasLocation(#21462,#21377)
|
||||
successor(#21381,#21462)
|
||||
successor(#21461,#21363)
|
||||
successor(#21378,#21364)
|
||||
successor(#21461,#21364)
|
||||
successor(#21378,#21363)
|
||||
successor(#21376,#21350)
|
||||
successor(#21366,#21378)
|
||||
successor(#21352,#21366)
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
<!DOCTYPE qhelp PUBLIC
|
||||
"-//Semmle//qhelp//EN"
|
||||
"qhelp.dtd">
|
||||
<qhelp>
|
||||
<overview>
|
||||
<p>
|
||||
Builtin functions and objects defined in the JavaScript standard library can be shadowed or redefined in user code.
|
||||
This is confusing and makes code hard to understand, so it should be avoided.
|
||||
</p>
|
||||
|
||||
</overview>
|
||||
<recommendation>
|
||||
|
||||
<p>
|
||||
Refactor the code to avoid shadowing or redefinition. For example, if a local variable has the same name as a standard
|
||||
library builtin, it should be renamed.
|
||||
</p>
|
||||
|
||||
</recommendation>
|
||||
<example>
|
||||
|
||||
<p>
|
||||
In the following example, the user-defined function <code>eval</code> shadows the builtin function <code>eval</code>
|
||||
defined in the standard library. It could be renamed <code>evaluate</code> to avoid confusion.
|
||||
</p>
|
||||
|
||||
<sample src="examples/BuiltinRedefined.js" />
|
||||
|
||||
</example>
|
||||
<references>
|
||||
|
||||
|
||||
<li>Ecma International, <i>ECMAScript Language Definition</i>, 5.1 Edition, Section 15. ECMA, 2011.</li>
|
||||
|
||||
|
||||
</references>
|
||||
</qhelp>
|
||||
@@ -1,50 +0,0 @@
|
||||
/**
|
||||
* @name Builtin redefined
|
||||
* @description Standard library functions can be redefined, but this should be avoided
|
||||
* since it makes code hard to read and maintain.
|
||||
* @kind problem
|
||||
* @problem.severity recommendation
|
||||
* @id js/builtin-redefinition
|
||||
* @tags maintainability
|
||||
* @precision medium
|
||||
* @deprecated This query is prone to false positives. Deprecated since 1.17.
|
||||
*/
|
||||
|
||||
import javascript
|
||||
import Definitions
|
||||
|
||||
/**
|
||||
* Holds if `id` is a redefinition of a standard library function that is considered
|
||||
* acceptable since it merely introduces a local alias to the standard function of
|
||||
* the same name.
|
||||
*/
|
||||
predicate acceptableRedefinition(Identifier id) {
|
||||
// function(x, y, undefined) { ... }(23, 42)
|
||||
id.getName() = "undefined" and
|
||||
exists(ImmediatelyInvokedFunctionExpr iife |
|
||||
id = iife.getParameter(iife.getInvocation().getNumArgument())
|
||||
)
|
||||
or
|
||||
// Date = global.Date
|
||||
exists(AssignExpr assgn |
|
||||
id = assgn.getTarget() and
|
||||
id.getName() = assgn.getRhs().getUnderlyingValue().(PropAccess).getPropertyName()
|
||||
)
|
||||
or
|
||||
// var Date = global.Date
|
||||
exists(VariableDeclarator decl |
|
||||
id = decl.getBindingPattern() and
|
||||
id.getName() = decl.getInit().getUnderlyingValue().(PropAccess).getPropertyName()
|
||||
)
|
||||
}
|
||||
|
||||
from DefiningIdentifier id, string name
|
||||
where
|
||||
not id.inExternsFile() and
|
||||
name = id.getName() and
|
||||
name
|
||||
.regexpMatch("Object|Function|Array|String|Boolean|Number|Math|Date|RegExp|Error|" +
|
||||
"NaN|Infinity|undefined|eval|parseInt|parseFloat|isNaN|isFinite|" +
|
||||
"decodeURI|decodeURIComponent|encodeURI|encodeURIComponent") and
|
||||
not acceptableRedefinition(id)
|
||||
select id, "Redefinition of " + name + "."
|
||||
@@ -1,46 +0,0 @@
|
||||
<!DOCTYPE qhelp PUBLIC
|
||||
"-//Semmle//qhelp//EN"
|
||||
"qhelp.dtd">
|
||||
<qhelp>
|
||||
<overview>
|
||||
<p>
|
||||
Defining a method by assigning a closure to a property of the receiver object in the constructor
|
||||
is inefficient, since a new closure is created for every instance. This wastes heap space and may
|
||||
interfere with JIT compilation.
|
||||
</p>
|
||||
|
||||
</overview>
|
||||
<recommendation>
|
||||
|
||||
<p>
|
||||
Assign the function to a property of the prototype object instead. That way, all instances share
|
||||
the same closure.
|
||||
</p>
|
||||
|
||||
</recommendation>
|
||||
<example>
|
||||
|
||||
<p>
|
||||
In the following example, constructor <code>Point</code> defines method <code>move</code> by creating
|
||||
a new closure and storing it in the <code>move</code> property of each new instance. Consequently,
|
||||
<code>p.move</code> and <code>q.move</code> are different methods.
|
||||
</p>
|
||||
|
||||
<sample src="examples/InefficientMethodDefinition.js" />
|
||||
|
||||
<p>
|
||||
It is better to instead define <code>move</code> on the prototype object <code>Point.prototype</code>
|
||||
like this:
|
||||
</p>
|
||||
|
||||
<sample src="examples/InefficientMethodDefinitionGood.js" />
|
||||
|
||||
</example>
|
||||
<references>
|
||||
|
||||
|
||||
<li>Mozilla Developer Network: <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Inheritance_and_the_prototype_chain">Inheritance and the prototype chain</a>.</li>
|
||||
|
||||
|
||||
</references>
|
||||
</qhelp>
|
||||
@@ -1,41 +0,0 @@
|
||||
/**
|
||||
* @name Inefficient method definition
|
||||
* @description Defining methods in the constructor (as opposed to adding them to the
|
||||
* prototype object) is inefficient.
|
||||
* @kind problem
|
||||
* @problem.severity recommendation
|
||||
* @id js/method-definition-in-constructor
|
||||
* @tags efficiency
|
||||
* maintainability
|
||||
* @precision medium
|
||||
* @deprecated This query is prone to false positives. Deprecated since 1.17.
|
||||
*/
|
||||
|
||||
import javascript
|
||||
import semmle.javascript.RestrictedLocations
|
||||
|
||||
/**
|
||||
* Holds if `stmt` is of the form `this.<name> = <method>;`.
|
||||
*/
|
||||
predicate methodDefinition(ExprStmt stmt, string name, Function method) {
|
||||
exists(AssignExpr assgn, PropAccess pacc |
|
||||
assgn = stmt.getExpr() and
|
||||
pacc = assgn.getLhs() and
|
||||
pacc.getBase() instanceof ThisExpr and
|
||||
name = pacc.getPropertyName() and
|
||||
method = assgn.getRhs()
|
||||
)
|
||||
}
|
||||
|
||||
from Function ctor, ExprStmt defn, string name, Function method
|
||||
where
|
||||
not ctor instanceof ImmediatelyInvokedFunctionExpr and
|
||||
defn = ctor.getABodyStmt() and
|
||||
methodDefinition(defn, name, method) and
|
||||
// if the method captures a local variable of the constructor, it cannot
|
||||
// easily be moved to the constructor object
|
||||
not exists(Variable v | v.getScope() = ctor.getScope() |
|
||||
v.getAnAccess().getContainer().getEnclosingContainer*() = method
|
||||
)
|
||||
select defn.(FirstLineOf),
|
||||
name + " should be added to the prototype object rather than to each instance."
|
||||
@@ -0,0 +1,67 @@
|
||||
<!DOCTYPE qhelp PUBLIC
|
||||
"-//Semmle//qhelp//EN"
|
||||
"qhelp.dtd">
|
||||
<qhelp>
|
||||
<overview>
|
||||
<p>
|
||||
The TypeScript compiler has to choose which specific overload is called
|
||||
when a method with multiple overloads is called.
|
||||
The compiler will always choose the textually first overload that does
|
||||
not give rise to any type errors with the arguments provided at the
|
||||
function call.
|
||||
</p>
|
||||
<p>
|
||||
This behavior can be unintuitive for programmers unfamiliar with the
|
||||
type system in TypeScript, and can in some instances lead to situations
|
||||
where a programmer writes an overloaded method where only the first
|
||||
overload can ever be used.
|
||||
</p>
|
||||
</overview>
|
||||
<recommendation>
|
||||
<p>
|
||||
Either reorder the method overloads if an overload with more type
|
||||
parameters is placed before a similar overload with fewer parameters.
|
||||
Alternatively, collapse multiple overloads with identical parameter types by
|
||||
creating a single overload that returns a union of the return types
|
||||
from the multiple overloads.
|
||||
</p>
|
||||
</recommendation>
|
||||
<example>
|
||||
<p>
|
||||
In the example below, a programmer has tried to express that a method
|
||||
can return multiple possible values by creating multiple overloads
|
||||
with identical parameter types. However, only the first overload
|
||||
will ever be selected by the TypeScript compiler.
|
||||
</p>
|
||||
<sample src="examples/UnreachableMethodOverloads.ts" />
|
||||
<p>
|
||||
The error can be fixed by merging the overloads into a single method
|
||||
signature that returns a union of the previous return types.
|
||||
</p>
|
||||
<sample src="examples/UnreachableMethodOverloadsGood.ts" />
|
||||
|
||||
<p>
|
||||
In the example below, an interface <code>Foo</code> declares a method
|
||||
<code>create()</code> with two overloads. The only difference between
|
||||
the two overloads is the type parameter <code>T</code> in the first
|
||||
overload. The TypeScript compiler will always use the first overload
|
||||
when <code>create()</code> is called, as a default type will be used
|
||||
for the type parameter <code>T</code> if none is provided.
|
||||
This default type is <code>unknown</code> in TypeScript 3.5+, and
|
||||
<code>{}</code> in earlier versions.
|
||||
</p>
|
||||
<sample src="examples/UnreachableMethodOverloadsTypeParameters.ts" />
|
||||
<p>
|
||||
In this example, the error has been fixed by switching the order of the two
|
||||
overloads. In this fixed version, if the <code>create()</code> method
|
||||
is called with an explicit type argument the second overload will be
|
||||
used, as the first overload would give rise to a type error.
|
||||
</p>
|
||||
<sample src="examples/UnreachableMethodOverloadsTypeParametersGood.ts" />
|
||||
</example>
|
||||
<references>
|
||||
|
||||
<li>TypeScript specification: <a href="https://github.com/microsoft/TypeScript/blob/7be7cba050799bc11c9411babd31f44c9ec087f0/doc/spec.md#4.15.1">Overload Resolution</a></li>
|
||||
|
||||
</references>
|
||||
</qhelp>
|
||||
140
javascript/ql/src/Declarations/UnreachableMethodOverloads.ql
Normal file
140
javascript/ql/src/Declarations/UnreachableMethodOverloads.ql
Normal file
@@ -0,0 +1,140 @@
|
||||
/**
|
||||
* @name Unreachable method overloads
|
||||
* @description Having multiple overloads with the same parameter types in TypeScript
|
||||
* makes all overloads except the first one unreachable, as the compiler
|
||||
* always resolves calls to the textually first matching overload.
|
||||
* @kind problem
|
||||
* @problem.severity warning
|
||||
* @id js/unreachable-method-overloads
|
||||
* @precision high
|
||||
* @tags correctness
|
||||
* typescript
|
||||
*/
|
||||
|
||||
import javascript
|
||||
|
||||
/**
|
||||
* Gets the `i`th parameter from the method signature.
|
||||
*/
|
||||
SimpleParameter getParameter(MethodSignature sig, int i) { result = sig.getBody().getParameter(i) }
|
||||
|
||||
/**
|
||||
* Gets a string-representation of the type-annotation from the `i`th parameter in the method signature.
|
||||
*/
|
||||
string getParameterTypeAnnotation(MethodSignature sig, int i) {
|
||||
result = getParameter(sig, i).getTypeAnnotation().toString()
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the other overloads for an overloaded method signature.
|
||||
*/
|
||||
MethodSignature getOtherMatchingSignatures(MethodSignature sig) {
|
||||
signaturesMatch(result, sig) and
|
||||
result != sig
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the kind of the member-declaration. Either "static" or "instance".
|
||||
*/
|
||||
string getKind(MemberDeclaration m) {
|
||||
if m.isStatic() then result = "static" else result = "instance"
|
||||
}
|
||||
|
||||
/**
|
||||
* A call-signature that originates from a MethodSignature in the AST.
|
||||
*/
|
||||
private class MethodCallSig extends CallSignatureType {
|
||||
string name;
|
||||
|
||||
MethodCallSig() {
|
||||
exists(MethodSignature sig |
|
||||
this = sig.getBody().getCallSignature() and
|
||||
name = sig.getName()
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the name of any member that has this signature.
|
||||
*/
|
||||
string getName() {
|
||||
result = name
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if the two call signatures could be overloads of each other and have the same parameter types.
|
||||
*/
|
||||
predicate matchingCallSignature(MethodCallSig method, MethodCallSig other) {
|
||||
method.getName() = other.getName() and
|
||||
|
||||
method.getNumOptionalParameter() = other.getNumOptionalParameter() and
|
||||
method.getNumParameter() = other.getNumParameter() and
|
||||
method.getNumRequiredParameter() = other.getNumRequiredParameter() and
|
||||
// purposely not looking at number of type arguments.
|
||||
|
||||
method.getKind() = other.getKind() and
|
||||
|
||||
|
||||
forall(int i | i in [0 .. -1 + method.getNumParameter()] |
|
||||
method.getParameter(i) = other.getParameter(i) // This is sometimes imprecise, so it is still a good idea to compare type annotations.
|
||||
) and
|
||||
|
||||
// shared type parameters are equal.
|
||||
forall(int i | i in [0 .. -1 + min(int num | num = method.getNumTypeParameter() or num = other.getNumTypeParameter())] |
|
||||
method.getTypeParameterBound(i) = other.getTypeParameterBound(i)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets which overload index the MethodSignature has among the overloads of the same name.
|
||||
*/
|
||||
int getOverloadIndex(MethodSignature sig) {
|
||||
sig.getDeclaringType().getMethodOverload(sig.getName(), result) = sig
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if the two method signatures are overloads of each other and have the same parameter types.
|
||||
*/
|
||||
predicate signaturesMatch(MethodSignature method, MethodSignature other) {
|
||||
// declared in the same interface/class.
|
||||
method.getDeclaringType() = other.getDeclaringType() and
|
||||
// same static modifier.
|
||||
getKind(method) = getKind(other) and
|
||||
|
||||
// same name.
|
||||
method.getName() = other.getName() and
|
||||
|
||||
// same number of parameters.
|
||||
method.getBody().getNumParameter() = other.getBody().getNumParameter() and
|
||||
|
||||
// The types are compared in matchingCallSignature. This is sanity-check that the textual representation of the type-annotations are somewhat similar.
|
||||
forall(int i | i in [0 .. -1 + method.getBody().getNumParameter()] |
|
||||
getParameterTypeAnnotation(method, i) = getParameterTypeAnnotation(other, i)
|
||||
) and
|
||||
|
||||
matchingCallSignature(method.getBody().getCallSignature(), other.getBody().getCallSignature())
|
||||
}
|
||||
|
||||
from ClassOrInterface decl, string name, MethodSignature previous, MethodSignature unreachable
|
||||
where
|
||||
previous = decl.getMethod(name) and
|
||||
unreachable = getOtherMatchingSignatures(previous) and
|
||||
|
||||
// If the method is part of inheritance between classes/interfaces, then there can sometimes be reasons for having this pattern.
|
||||
not exists(decl.getASuperTypeDeclaration().getMethod(name)) and
|
||||
not exists(ClassOrInterface sub |
|
||||
decl = sub.getASuperTypeDeclaration() and
|
||||
exists(sub.getMethod(name))
|
||||
) and
|
||||
|
||||
|
||||
// If a later method overload has more type parameters, then that overload can be selected by explicitly declaring the type arguments at the callsite.
|
||||
// This comparison removes those cases.
|
||||
unreachable.getBody().getNumTypeParameter() <= previous.getBody().getNumTypeParameter() and
|
||||
|
||||
// We always select the first of the overloaded methods.
|
||||
not exists(MethodSignature later | later = getOtherMatchingSignatures(previous) |
|
||||
getOverloadIndex(later) < getOverloadIndex(previous)
|
||||
)
|
||||
select unreachable,
|
||||
"This overload of " + name + "() is unreachable, the $@ overload will always be selected.", previous, "previous"
|
||||
@@ -0,0 +1,5 @@
|
||||
interface Foo {
|
||||
getParsedThing(id: string): string[];
|
||||
getParsedThing(id: string): number[];
|
||||
getParsedThing(id: string): object[];
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
interface Foo {
|
||||
getParsedThing(id: string): object[] | number[] | string[];
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
interface Foo {
|
||||
create<T>(a: string): MyObject<T>;
|
||||
create(a: string): MyObject<any>;
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
interface Foo {
|
||||
create(a: string): Array<any>;
|
||||
create<T>(a: string): Array<T>;
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
<!DOCTYPE qhelp PUBLIC
|
||||
"-//Semmle//qhelp//EN"
|
||||
"qhelp.dtd">
|
||||
<qhelp>
|
||||
<overview>
|
||||
<p>
|
||||
Avoid using <code>x % 2 === 1</code> or <code>x % 2 > 0</code> to check whether a number
|
||||
<code>x</code> is odd, or <code>x % 2 !== 1</code> to check whether it is even.
|
||||
Such code does not work for negative numbers: for example, <code>-5 % 2</code> equals
|
||||
<code>-1</code>, not <code>1</code>.
|
||||
</p>
|
||||
|
||||
</overview>
|
||||
<recommendation>
|
||||
|
||||
<p>
|
||||
Consider using <code>x % 2 !== 0</code> to check for odd parity and <code>x % 2 === 0</code>
|
||||
to check for even parity.
|
||||
</p>
|
||||
|
||||
</recommendation>
|
||||
<example>
|
||||
|
||||
<p>
|
||||
The following code snippet does not detect -9 as an odd number because <code>-9 % 2</code>
|
||||
is <code>-1</code>, not <code>1</code>.</p>
|
||||
|
||||
<sample src="examples/BadParityCheck.js" />
|
||||
|
||||
<p>
|
||||
The check should be rewritten as follows:
|
||||
</p>
|
||||
|
||||
<sample src="examples/BadParityCheckGood.js" />
|
||||
|
||||
</example>
|
||||
<references>
|
||||
|
||||
|
||||
<li>J. Bloch and N. Gafter, <em>Java Puzzlers: Traps, Pitfalls, and Corner Cases</em>, Puzzle 1. Addison-Wesley, 2005.</li>
|
||||
<li>Ecma International, <i>ECMAScript Language Definition</i>, 5.1 Edition, Section 11.5.3. ECMA, 2011.</li>
|
||||
|
||||
|
||||
</references>
|
||||
</qhelp>
|
||||
@@ -1,105 +0,0 @@
|
||||
/**
|
||||
* @name Bad parity check
|
||||
* @description Ensure that parity checks take negative numbers into account.
|
||||
* @kind problem
|
||||
* @problem.severity recommendation
|
||||
* @id js/incomplete-parity-check
|
||||
* @tags reliability
|
||||
* correctness
|
||||
* external/cwe/cwe-480
|
||||
* @precision low
|
||||
* @deprecated This query is prone to false positives. Deprecated since 1.17.
|
||||
*/
|
||||
|
||||
import javascript
|
||||
|
||||
/*
|
||||
* The following predicates implement a simple analysis for identifying
|
||||
* expressions that are guaranteed to only evaluate to non-negative numbers:
|
||||
*
|
||||
* - non-negative number literals
|
||||
* - applications of (), ++, + to expressions known to be non-negative
|
||||
* - references to local variables that are only assigned non-negative values,
|
||||
* never decremented, and never subjected to any compound assignments except
|
||||
* += where the rhs is known to be non-negative
|
||||
*
|
||||
* This is a greatest-fixpoint problem: if we have `x = 0`, `y = x`, `x = y`,
|
||||
* we want to conclude that both `x` and `y` are non-negative. Hence we have
|
||||
* to implement the analysis the other way around, as a conservative check
|
||||
* for negativity.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Holds if `e` is an expression that is relevant for the maybe-negative analysis.
|
||||
*/
|
||||
predicate relevant(Expr e) {
|
||||
// base case: left operands of `%`
|
||||
exists(ModExpr me | e = me.getLeftOperand())
|
||||
or
|
||||
// first inductive case: downward AST traversal
|
||||
relevant(e.getParentExpr())
|
||||
or
|
||||
// second inductive case: following variable assignments
|
||||
exists(Variable v | relevant(v.getAnAccess()) | e = v.getAnAssignedExpr())
|
||||
}
|
||||
|
||||
/** Holds if `e` could evaluate to a negative number. */
|
||||
predicate maybeNegative(Expr e) {
|
||||
relevant(e) and
|
||||
if exists(e.getIntValue())
|
||||
then e.getIntValue() < 0
|
||||
else
|
||||
if e instanceof ParExpr
|
||||
then maybeNegative(e.(ParExpr).getExpression())
|
||||
else
|
||||
if e instanceof IncExpr
|
||||
then maybeNegative(e.(IncExpr).getOperand())
|
||||
else
|
||||
if e instanceof VarAccess
|
||||
then maybeNegativeVar(e.(VarAccess).getVariable())
|
||||
else
|
||||
if e instanceof AddExpr
|
||||
then maybeNegative(e.(AddExpr).getAnOperand())
|
||||
else
|
||||
// anything else is considered to possibly be negative
|
||||
any()
|
||||
}
|
||||
|
||||
/** Holds if `v` could be assigned a negative number. */
|
||||
predicate maybeNegativeVar(Variable v) {
|
||||
v.isGlobal()
|
||||
or
|
||||
v.isParameter()
|
||||
or
|
||||
// is v ever assigned a potentially negative value?
|
||||
maybeNegative(v.getAnAssignedExpr())
|
||||
or
|
||||
// is v ever decremented?
|
||||
exists(DecExpr dec | dec.getOperand().getUnderlyingReference() = v.getAnAccess())
|
||||
or
|
||||
// is v ever subject to a compound assignment other than +=, or to
|
||||
// += with potentially negative rhs?
|
||||
exists(CompoundAssignExpr assgn | assgn.getTarget() = v.getAnAccess() |
|
||||
not assgn instanceof AssignAddExpr or
|
||||
maybeNegative(assgn.getRhs())
|
||||
)
|
||||
}
|
||||
|
||||
from Comparison cmp, ModExpr me, int num, string parity
|
||||
where
|
||||
cmp.getAnOperand().stripParens() = me and
|
||||
cmp.getAnOperand().getIntValue() = num and
|
||||
me.getRightOperand().getIntValue() = 2 and
|
||||
maybeNegative(me.getLeftOperand()) and
|
||||
(
|
||||
(cmp instanceof EqExpr or cmp instanceof StrictEqExpr) and
|
||||
num = 1 and
|
||||
parity = "oddness"
|
||||
or
|
||||
(cmp instanceof NEqExpr or cmp instanceof StrictNEqExpr) and
|
||||
num = 1 and
|
||||
parity = "evenness"
|
||||
or
|
||||
cmp instanceof GTExpr and num = 0 and parity = "oddness"
|
||||
)
|
||||
select cmp, "Test for " + parity + " does not take negative numbers into account."
|
||||
@@ -13,122 +13,10 @@
|
||||
*/
|
||||
|
||||
import javascript
|
||||
import DOMProperties
|
||||
import semmle.javascript.frameworks.xUnit
|
||||
import semmle.javascript.RestrictedLocations
|
||||
import ExprHasNoEffect
|
||||
import semmle.javascript.RestrictedLocations
|
||||
|
||||
/**
|
||||
* Holds if `e` is of the form `x;` or `e.p;` and has a JSDoc comment containing a tag.
|
||||
* In that case, it is probably meant as a declaration and shouldn't be flagged by this query.
|
||||
*
|
||||
* This will still flag cases where the JSDoc comment contains no tag at all (and hence carries
|
||||
* no semantic information), and expression statements with an ordinary (non-JSDoc) comment
|
||||
* attached to them.
|
||||
*/
|
||||
predicate isDeclaration(Expr e) {
|
||||
(e instanceof VarAccess or e instanceof PropAccess) and
|
||||
exists(e.getParent().(ExprStmt).getDocumentation().getATag())
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if there exists a getter for a property called `name` anywhere in the program.
|
||||
*/
|
||||
predicate isGetterProperty(string name) {
|
||||
// there is a call of the form `Object.defineProperty(..., name, descriptor)` ...
|
||||
exists(CallToObjectDefineProperty defProp | name = defProp.getPropertyName() |
|
||||
// ... where `descriptor` defines a getter
|
||||
defProp.hasPropertyAttributeWrite("get", _)
|
||||
or
|
||||
// ... where `descriptor` may define a getter
|
||||
exists(DataFlow::SourceNode descriptor | descriptor.flowsTo(defProp.getPropertyDescriptor()) |
|
||||
descriptor.isIncomplete(_)
|
||||
or
|
||||
// minimal escape analysis for the descriptor
|
||||
exists(DataFlow::InvokeNode invk |
|
||||
not invk = defProp and
|
||||
descriptor.flowsTo(invk.getAnArgument())
|
||||
)
|
||||
)
|
||||
)
|
||||
or
|
||||
// there is an object expression with a getter property `name`
|
||||
exists(ObjectExpr obj | obj.getPropertyByName(name) instanceof PropertyGetter)
|
||||
}
|
||||
|
||||
/**
|
||||
* A property access that may invoke a getter.
|
||||
*/
|
||||
class GetterPropertyAccess extends PropAccess {
|
||||
override predicate isImpure() { isGetterProperty(getPropertyName()) }
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `c` is an indirect eval call of the form `(dummy, eval)(...)`, where
|
||||
* `dummy` is some expression whose value is discarded, and which simply
|
||||
* exists to prevent the call from being interpreted as a direct eval.
|
||||
*/
|
||||
predicate isIndirectEval(CallExpr c, Expr dummy) {
|
||||
exists(SeqExpr seq | seq = c.getCallee().stripParens() |
|
||||
dummy = seq.getOperand(0) and
|
||||
seq.getOperand(1).(GlobalVarAccess).getName() = "eval" and
|
||||
seq.getNumOperands() = 2
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `c` is a call of the form `(dummy, e[p])(...)`, where `dummy` is
|
||||
* some expression whose value is discarded, and which simply exists
|
||||
* to prevent the call from being interpreted as a method call.
|
||||
*/
|
||||
predicate isReceiverSuppressingCall(CallExpr c, Expr dummy, PropAccess callee) {
|
||||
exists(SeqExpr seq | seq = c.getCallee().stripParens() |
|
||||
dummy = seq.getOperand(0) and
|
||||
seq.getOperand(1) = callee and
|
||||
seq.getNumOperands() = 2
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if evaluating `e` has no side effects (except potentially allocating
|
||||
* and initializing a new object).
|
||||
*
|
||||
* For calls, we do not check whether their arguments have any side effects:
|
||||
* even if they do, the call itself is useless and should be flagged by this
|
||||
* query.
|
||||
*/
|
||||
predicate noSideEffects(Expr e) {
|
||||
e.isPure()
|
||||
or
|
||||
// `new Error(...)`, `new SyntaxError(...)`, etc.
|
||||
forex(Function f | f = e.flow().(DataFlow::NewNode).getACallee() |
|
||||
f.(ExternalType).getASupertype*().getName() = "Error"
|
||||
)
|
||||
}
|
||||
|
||||
from Expr e
|
||||
where
|
||||
noSideEffects(e) and
|
||||
inVoidContext(e) and
|
||||
// disregard pure expressions wrapped in a void(...)
|
||||
not e instanceof VoidExpr and
|
||||
// filter out directives (unknown directives are handled by UnknownDirective.ql)
|
||||
not exists(Directive d | e = d.getExpr()) and
|
||||
// or about externs
|
||||
not e.inExternsFile() and
|
||||
// don't complain about declarations
|
||||
not isDeclaration(e) and
|
||||
// exclude DOM properties, which sometimes have magical auto-update properties
|
||||
not isDOMProperty(e.(PropAccess).getPropertyName()) and
|
||||
// exclude xUnit.js annotations
|
||||
not e instanceof XUnitAnnotation and
|
||||
// exclude common patterns that are most likely intentional
|
||||
not isIndirectEval(_, e) and
|
||||
not isReceiverSuppressingCall(_, e, _) and
|
||||
// exclude anonymous function expressions as statements; these can only arise
|
||||
// from a syntax error we already flag
|
||||
not exists(FunctionExpr fe, ExprStmt es | fe = e |
|
||||
fe = es.getExpr() and
|
||||
not exists(fe.getName())
|
||||
)
|
||||
where hasNoEffect(e)
|
||||
select e.(FirstLineOf), "This expression has no effect."
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
*/
|
||||
|
||||
import javascript
|
||||
import DOMProperties
|
||||
import semmle.javascript.frameworks.xUnit
|
||||
|
||||
/**
|
||||
* Holds if `e` appears in a syntactic context where its value is discarded.
|
||||
@@ -37,3 +39,121 @@ predicate inVoidContext(Expr e) {
|
||||
or
|
||||
exists(LogicalBinaryExpr logical | e = logical.getRightOperand() and inVoidContext(logical))
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Holds if `e` is of the form `x;` or `e.p;` and has a JSDoc comment containing a tag.
|
||||
* In that case, it is probably meant as a declaration and shouldn't be flagged by this query.
|
||||
*
|
||||
* This will still flag cases where the JSDoc comment contains no tag at all (and hence carries
|
||||
* no semantic information), and expression statements with an ordinary (non-JSDoc) comment
|
||||
* attached to them.
|
||||
*/
|
||||
predicate isDeclaration(Expr e) {
|
||||
(e instanceof VarAccess or e instanceof PropAccess) and
|
||||
exists(e.getParent().(ExprStmt).getDocumentation().getATag())
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if there exists a getter for a property called `name` anywhere in the program.
|
||||
*/
|
||||
predicate isGetterProperty(string name) {
|
||||
// there is a call of the form `Object.defineProperty(..., name, descriptor)` ...
|
||||
exists(CallToObjectDefineProperty defProp | name = defProp.getPropertyName() |
|
||||
// ... where `descriptor` defines a getter
|
||||
defProp.hasPropertyAttributeWrite("get", _)
|
||||
or
|
||||
// ... where `descriptor` may define a getter
|
||||
exists(DataFlow::SourceNode descriptor | descriptor.flowsTo(defProp.getPropertyDescriptor()) |
|
||||
descriptor.isIncomplete(_)
|
||||
or
|
||||
// minimal escape analysis for the descriptor
|
||||
exists(DataFlow::InvokeNode invk |
|
||||
not invk = defProp and
|
||||
descriptor.flowsTo(invk.getAnArgument())
|
||||
)
|
||||
)
|
||||
)
|
||||
or
|
||||
// there is an object expression with a getter property `name`
|
||||
exists(ObjectExpr obj | obj.getPropertyByName(name) instanceof PropertyGetter)
|
||||
}
|
||||
|
||||
/**
|
||||
* A property access that may invoke a getter.
|
||||
*/
|
||||
class GetterPropertyAccess extends PropAccess {
|
||||
override predicate isImpure() { isGetterProperty(getPropertyName()) }
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `c` is an indirect eval call of the form `(dummy, eval)(...)`, where
|
||||
* `dummy` is some expression whose value is discarded, and which simply
|
||||
* exists to prevent the call from being interpreted as a direct eval.
|
||||
*/
|
||||
predicate isIndirectEval(CallExpr c, Expr dummy) {
|
||||
exists(SeqExpr seq | seq = c.getCallee().stripParens() |
|
||||
dummy = seq.getOperand(0) and
|
||||
seq.getOperand(1).(GlobalVarAccess).getName() = "eval" and
|
||||
seq.getNumOperands() = 2
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `c` is a call of the form `(dummy, e[p])(...)`, where `dummy` is
|
||||
* some expression whose value is discarded, and which simply exists
|
||||
* to prevent the call from being interpreted as a method call.
|
||||
*/
|
||||
predicate isReceiverSuppressingCall(CallExpr c, Expr dummy, PropAccess callee) {
|
||||
exists(SeqExpr seq | seq = c.getCallee().stripParens() |
|
||||
dummy = seq.getOperand(0) and
|
||||
seq.getOperand(1) = callee and
|
||||
seq.getNumOperands() = 2
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if evaluating `e` has no side effects (except potentially allocating
|
||||
* and initializing a new object).
|
||||
*
|
||||
* For calls, we do not check whether their arguments have any side effects:
|
||||
* even if they do, the call itself is useless and should be flagged by this
|
||||
* query.
|
||||
*/
|
||||
predicate noSideEffects(Expr e) {
|
||||
e.isPure()
|
||||
or
|
||||
// `new Error(...)`, `new SyntaxError(...)`, etc.
|
||||
forex(Function f | f = e.flow().(DataFlow::NewNode).getACallee() |
|
||||
f.(ExternalType).getASupertype*().getName() = "Error"
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if the expression `e` should be reported as having no effect.
|
||||
*/
|
||||
predicate hasNoEffect(Expr e) {
|
||||
noSideEffects(e) and
|
||||
inVoidContext(e) and
|
||||
// disregard pure expressions wrapped in a void(...)
|
||||
not e instanceof VoidExpr and
|
||||
// filter out directives (unknown directives are handled by UnknownDirective.ql)
|
||||
not exists(Directive d | e = d.getExpr()) and
|
||||
// or about externs
|
||||
not e.inExternsFile() and
|
||||
// don't complain about declarations
|
||||
not isDeclaration(e) and
|
||||
// exclude DOM properties, which sometimes have magical auto-update properties
|
||||
not isDOMProperty(e.(PropAccess).getPropertyName()) and
|
||||
// exclude xUnit.js annotations
|
||||
not e instanceof XUnitAnnotation and
|
||||
// exclude common patterns that are most likely intentional
|
||||
not isIndirectEval(_, e) and
|
||||
not isReceiverSuppressingCall(_, e, _) and
|
||||
// exclude anonymous function expressions as statements; these can only arise
|
||||
// from a syntax error we already flag
|
||||
not exists(FunctionExpr fe, ExprStmt es | fe = e |
|
||||
fe = es.getExpr() and
|
||||
not exists(fe.getName())
|
||||
)
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
<!DOCTYPE qhelp PUBLIC
|
||||
"-//Semmle//qhelp//EN"
|
||||
"qhelp.dtd">
|
||||
<qhelp>
|
||||
<overview>
|
||||
<p>
|
||||
In JavaScript, properties of objects do not have to be declared and can be dynamically added
|
||||
and removed at runtime. Thus, if a property name is misspelled, this is not detected by the
|
||||
compiler, and may lead to an error at runtime. The same problem occurs with misspelled
|
||||
global variables.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
This rule flags property names and global variables that are mentioned only once, but where
|
||||
a different capitalization of the same name is used in multiple other places, suggesting a typo.
|
||||
</p>
|
||||
|
||||
</overview>
|
||||
<recommendation>
|
||||
|
||||
<p>
|
||||
Check whether the name has been misspelled. If the name is correct, consider using
|
||||
a <a href="http://www.jslint.com/help.html#properties">JSLint-style</a>
|
||||
<code>/*property ...*/</code> directive to document the existence of this property,
|
||||
or provide an externs file declaring the property.
|
||||
</p>
|
||||
|
||||
</recommendation>
|
||||
<example>
|
||||
|
||||
<p>
|
||||
The following code snippet contains two uses of the <code>log</code> method, but only
|
||||
one use of the <code>Log</code> method. This suggests that <code>Log</code> may be a typo
|
||||
for <code>log</code>.
|
||||
</p>
|
||||
|
||||
<sample src="examples/HapaxLegomenon.js" />
|
||||
|
||||
<p>
|
||||
If the use of <code>Log</code> is, in fact, a typo, it should be corrected. Otherwise, a
|
||||
<code>properties</code> directive can be introduced to document the fact that both
|
||||
<code>log</code> and <code>Log</code> properties exist:
|
||||
</p>
|
||||
|
||||
<sample src="examples/HapaxLegomenonGood.js" />
|
||||
|
||||
</example>
|
||||
<references>
|
||||
|
||||
|
||||
<li>JSLint: <a href="http://www.jslint.com/help.html#properties">Property</a>.</li>
|
||||
<li>Google Closure Tools: <a href="https://developers.google.com/closure/compiler/docs/api-tutorial3?csw=1#externs">Declaring externs</a>.</li>
|
||||
|
||||
|
||||
</references>
|
||||
</qhelp>
|
||||
@@ -1,91 +0,0 @@
|
||||
/**
|
||||
* @name Potentially misspelled property or variable name
|
||||
* @description A property or variable is only mentioned once, but there is one with the same name
|
||||
* in different capitalization that is mentioned more than once, suggesting that this
|
||||
* may be a typo.
|
||||
* @kind problem
|
||||
* @problem.severity warning
|
||||
* @id js/wrong-capitalization
|
||||
* @tags reliability
|
||||
* @precision low
|
||||
* @deprecated This query is prone to false positives. Deprecated since 1.17.
|
||||
*/
|
||||
|
||||
import javascript
|
||||
|
||||
/** Gets the number of identifiers and string literals that refer to `name`. */
|
||||
int countOccurrences(string name) {
|
||||
(
|
||||
exists(PropAccess pacc | name = pacc.getPropertyName()) or
|
||||
exists(VarAccess acc | name = acc.getName())
|
||||
) and
|
||||
result = strictcount(Expr id |
|
||||
id.(Identifier).getName() = name
|
||||
or
|
||||
// count string literals as well to capture meta-programming
|
||||
id.getStringValue() = name
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* An access to an undeclared variable or property that is only referenced
|
||||
* once in the entire program.
|
||||
*/
|
||||
abstract class Hapax extends @expr {
|
||||
/** Gets the name of the accessed variable or property. */
|
||||
abstract string getName();
|
||||
|
||||
/** Gets a textual representation of this element. */
|
||||
string toString() { result = this.(Expr).toString() }
|
||||
}
|
||||
|
||||
/**
|
||||
* An access to a property that is covered neither by a JSLint property declaration
|
||||
* nor by an externs declaration, and that is only mentioned once in the entire program.
|
||||
*/
|
||||
class UndeclaredPropertyAccess extends Hapax, @dotexpr {
|
||||
UndeclaredPropertyAccess() {
|
||||
exists(string name | name = this.(DotExpr).getPropertyName() |
|
||||
countOccurrences(name) = 1 and
|
||||
not exists(JSLintProperties jslpd | jslpd.appliesTo(this) and jslpd.getAProperty() = name) and
|
||||
not exists(ExternalMemberDecl emd | emd.getProperty() = this)
|
||||
)
|
||||
}
|
||||
|
||||
override string getName() { result = this.(DotExpr).getPropertyName() }
|
||||
}
|
||||
|
||||
/**
|
||||
* An access to a global variable that is neither declared nor covered by a linter
|
||||
* directive, and that is only mentioned once in the entire program.
|
||||
*/
|
||||
class UndeclaredGlobal extends Hapax, @varaccess {
|
||||
UndeclaredGlobal() {
|
||||
exists(GlobalVariable gv, string name | this = gv.getAnAccess() and name = gv.getName() |
|
||||
countOccurrences(name) = 1 and
|
||||
not exists(Linting::GlobalDeclaration glob | glob.declaresGlobalForAccess(this)) and
|
||||
not exists(gv.getADeclaration())
|
||||
)
|
||||
}
|
||||
|
||||
override string getName() { result = this.(VarAccess).getName() }
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the number of occurrences of `m`, which is the same as `hapax`
|
||||
* except for capitalization, ensuring that it occurs at least twice.
|
||||
*/
|
||||
int candidateSpellingCount(Hapax hapax, string m) {
|
||||
exists(string n | n = hapax.getName() |
|
||||
m.toLowerCase() = n.toLowerCase() and
|
||||
m != n and
|
||||
result = countOccurrences(m) and
|
||||
result > 1
|
||||
)
|
||||
}
|
||||
|
||||
from Hapax hapax, string n, string m
|
||||
where
|
||||
n = hapax.getName() and
|
||||
candidateSpellingCount(hapax, m) = max(candidateSpellingCount(hapax, _))
|
||||
select hapax.(Expr), "'" + n + "' is mentioned only once; it may be a typo for '" + m + "'."
|
||||
@@ -22,14 +22,51 @@ Expr leftChild(Expr e) {
|
||||
result = e.(AddExpr).getLeftOperand()
|
||||
}
|
||||
|
||||
class LiteralOrTemplate extends Expr {
|
||||
LiteralOrTemplate() {
|
||||
this instanceof TemplateLiteral or
|
||||
this instanceof Literal
|
||||
predicate isInConcat(Expr e) {
|
||||
exists(ParExpr par | isInConcat(par) and par.getExpression() = e)
|
||||
or
|
||||
exists(AddExpr a | a.getAnOperand() = e)
|
||||
}
|
||||
|
||||
class ConcatenationLiteral extends Expr {
|
||||
ConcatenationLiteral() {
|
||||
(
|
||||
this instanceof TemplateLiteral
|
||||
or
|
||||
this instanceof Literal
|
||||
)
|
||||
and isInConcat(this)
|
||||
}
|
||||
}
|
||||
|
||||
from AddExpr e, LiteralOrTemplate l, LiteralOrTemplate r, string word
|
||||
Expr getConcatChild(Expr e) {
|
||||
result = rightChild(e) or
|
||||
result = leftChild(e)
|
||||
}
|
||||
|
||||
Expr getConcatParent(Expr e) {
|
||||
e = getConcatChild(result)
|
||||
}
|
||||
|
||||
predicate isWordLike(ConcatenationLiteral lit) {
|
||||
lit.getStringValue().regexpMatch("(?i).*[a-z]{3,}.*")
|
||||
}
|
||||
|
||||
class ConcatRoot extends AddExpr {
|
||||
ConcatRoot() {
|
||||
not isInConcat(this)
|
||||
}
|
||||
}
|
||||
|
||||
ConcatRoot getAddRoot(AddExpr e) {
|
||||
result = getConcatParent*(e)
|
||||
}
|
||||
|
||||
predicate hasWordLikeFragment(AddExpr e) {
|
||||
isWordLike(getConcatChild*(getAddRoot(e)))
|
||||
}
|
||||
|
||||
from AddExpr e, ConcatenationLiteral l, ConcatenationLiteral r, string word
|
||||
where
|
||||
// l and r are appended together
|
||||
l = rightChild*(e.getLeftOperand()) and
|
||||
@@ -41,5 +78,8 @@ where
|
||||
// needed, and intra-identifier punctuation in, for example, a qualified name.
|
||||
word = l.getStringValue().regexpCapture(".* (([-A-Za-z/'\\.:,]*[a-zA-Z]|[0-9]+)[\\.:,!?']*)", 1) and
|
||||
r.getStringValue().regexpMatch("[a-zA-Z].*") and
|
||||
not word.regexpMatch(".*[,\\.:].*[a-zA-Z].*[^a-zA-Z]")
|
||||
not word.regexpMatch(".*[,\\.:].*[a-zA-Z].*[^a-zA-Z]") and
|
||||
|
||||
// There must be a constant-string in the concatenation that looks like a word.
|
||||
hasWordLikeFragment(e)
|
||||
select l, "This string appears to be missing a space after '" + word + "'."
|
||||
|
||||
@@ -13,6 +13,8 @@ import javascript
|
||||
from Directive d
|
||||
where
|
||||
not d instanceof KnownDirective and
|
||||
// ignore ":" pseudo-directive sometimes seen in dual-use shell/node.js scripts
|
||||
not d.getExpr().getStringValue() = ":" and
|
||||
// but exclude attribute top-levels: `<a href="javascript:'some-attribute-string'">`
|
||||
not d.getParent() instanceof CodeInAttribute
|
||||
select d, "Unknown directive: '" + truncate(d.getDirectiveText(), 20, " ... (truncated)") + "'."
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
<!DOCTYPE qhelp PUBLIC
|
||||
"-//Semmle//qhelp//EN"
|
||||
"qhelp.dtd">
|
||||
<qhelp>
|
||||
<overview>
|
||||
<p>
|
||||
Non-standard JSDoc tags are undesirable, since JSDoc-processing tools will either ignore
|
||||
them or treat them as plain text.
|
||||
</p>
|
||||
|
||||
</overview>
|
||||
<recommendation>
|
||||
|
||||
<p>
|
||||
Check whether the tag name is misspelled, or consult the JSDoc documentation to find out
|
||||
what standard tags are available.
|
||||
</p>
|
||||
|
||||
</recommendation>
|
||||
<example>
|
||||
|
||||
<p>
|
||||
In the following example, the constructor <code>Message</code> has a JSDoc comment describing
|
||||
its parameters, but the second <code>@param</code> tag has been misspelled as
|
||||
<code>@parma</code>.
|
||||
</p>
|
||||
|
||||
<sample src="examples/UnknownTagType.js" />
|
||||
|
||||
</example>
|
||||
<references>
|
||||
|
||||
|
||||
<li>Use JSDoc: <a href="http://usejsdoc.org/index.html">Tag Dictionary</a>.</li>
|
||||
|
||||
|
||||
</references>
|
||||
</qhelp>
|
||||
@@ -1,139 +0,0 @@
|
||||
/**
|
||||
* @name Unknown JSDoc tag
|
||||
* @description A JSDoc tag with a non-standard tag type will either be ignored or treated as plain
|
||||
* text by JSDoc-processing tools.
|
||||
* @kind problem
|
||||
* @problem.severity recommendation
|
||||
* @id js/jsdoc/unknown-tag-type
|
||||
* @tags maintainability
|
||||
* readability
|
||||
* documentation
|
||||
* @precision low
|
||||
* @deprecated This query is prone to false positives. Deprecated since 1.17.
|
||||
*/
|
||||
|
||||
import javascript
|
||||
|
||||
/** Holds if `tp` is a standard tag type. */
|
||||
predicate knownTagType(string tp) {
|
||||
tp = "abstract" or
|
||||
tp = "access" or
|
||||
tp = "alias" or
|
||||
tp = "api" or
|
||||
tp = "arg" or
|
||||
tp = "argument" or
|
||||
tp = "augments" or
|
||||
tp = "author" or
|
||||
tp = "borrows" or
|
||||
tp = "bug" or
|
||||
tp = "callback" or
|
||||
tp = "category" or
|
||||
tp = "class" or
|
||||
tp = "classdesc" or
|
||||
tp = "const" or
|
||||
tp = "constant" or
|
||||
tp = "constructor" or
|
||||
tp = "constructs" or
|
||||
tp = "copyright" or
|
||||
tp = "default" or
|
||||
tp = "defaultvalue" or
|
||||
tp = "define" or
|
||||
tp = "depend" or
|
||||
tp = "depends" or
|
||||
tp = "deprecated" or
|
||||
tp = "desc" or
|
||||
tp = "description" or
|
||||
tp = "dict" or
|
||||
tp = "emits" or
|
||||
tp = "enum" or
|
||||
tp = "event" or
|
||||
tp = "example" or
|
||||
tp = "exception" or
|
||||
tp = "export" or
|
||||
tp = "exports" or
|
||||
tp = "expose" or
|
||||
tp = "extends" or
|
||||
tp = "external" or
|
||||
tp = "externs" or
|
||||
tp = "field" or
|
||||
tp = "file" or
|
||||
tp = "fileoverview" or
|
||||
tp = "final" or
|
||||
tp = "fires" or
|
||||
tp = "flow" or
|
||||
tp = "func" or
|
||||
tp = "function" or
|
||||
tp = "global" or
|
||||
tp = "host" or
|
||||
tp = "ignore" or
|
||||
tp = "implements" or
|
||||
tp = "implicitCast" or
|
||||
tp = "inheritDoc" or
|
||||
tp = "inner" or
|
||||
tp = "interface" or
|
||||
tp = "internal" or
|
||||
tp = "instance" or
|
||||
tp = "kind" or
|
||||
tp = "lends" or
|
||||
tp = "license" or
|
||||
tp = "link" or
|
||||
tp = "member" or
|
||||
tp = "memberof" or
|
||||
tp = "memberOf" or
|
||||
tp = "method" or
|
||||
tp = "mixes" or
|
||||
tp = "mixin" or
|
||||
tp = "modifies" or
|
||||
tp = "module" or
|
||||
tp = "modName" or
|
||||
tp = "mods" or
|
||||
tp = "name" or
|
||||
tp = "namespace" or
|
||||
tp = "ngInject" or
|
||||
tp = "noalias" or
|
||||
tp = "nocompile" or
|
||||
tp = "nosideeffects" or
|
||||
tp = "note" or
|
||||
tp = "override" or
|
||||
tp = "overview" or
|
||||
tp = "owner" or
|
||||
tp = "package" or
|
||||
tp = "param" or
|
||||
tp = "preserve" or
|
||||
tp = "preserveTry" or
|
||||
tp = "private" or
|
||||
tp = "prop" or
|
||||
tp = "property" or
|
||||
tp = "protected" or
|
||||
tp = "providesModule" or
|
||||
tp = "public" or
|
||||
tp = "readonly" or
|
||||
tp = "requires" or
|
||||
tp = "returns" or
|
||||
tp = "return" or
|
||||
tp = "see" or
|
||||
tp = "since" or
|
||||
tp = "static" or
|
||||
tp = "struct" or
|
||||
tp = "summary" or
|
||||
tp = "supported" or
|
||||
tp = "suppress" or
|
||||
tp = "template" or
|
||||
tp = "this" or
|
||||
tp = "throws" or
|
||||
tp = "todo" or
|
||||
tp = "tutorial" or
|
||||
tp = "type" or
|
||||
tp = "typedef" or
|
||||
tp = "var" or
|
||||
tp = "variation" or
|
||||
tp = "version" or
|
||||
tp = "virtual" or
|
||||
tp = "visibility" or
|
||||
tp = "wizaction" or
|
||||
tp = "wizmodule"
|
||||
}
|
||||
|
||||
from JSDocTag tag
|
||||
where not knownTagType(tag.getTitle())
|
||||
select tag, "Unknown tag type '" + tag.getTitle() + "'."
|
||||
@@ -1,26 +0,0 @@
|
||||
<!DOCTYPE qhelp PUBLIC
|
||||
"-//Semmle//qhelp//EN"
|
||||
"qhelp.dtd">
|
||||
<qhelp>
|
||||
<overview>
|
||||
<p>
|
||||
JSLint directives must not start with a space. For example, <code>/* global window*/</code>
|
||||
is not a valid directive, and will not be recognized by JSLint.
|
||||
</p>
|
||||
|
||||
</overview>
|
||||
<recommendation>
|
||||
|
||||
<p>
|
||||
Remove the space: <code>/*global window*/</code>.
|
||||
</p>
|
||||
|
||||
</recommendation>
|
||||
<references>
|
||||
|
||||
|
||||
<li>JSLint: <a href="http://www.jslint.com/help.html">JSLint Help</a>.</li>
|
||||
|
||||
|
||||
</references>
|
||||
</qhelp>
|
||||
@@ -1,21 +0,0 @@
|
||||
/**
|
||||
* @name Invalid JSLint directive
|
||||
* @description A JSLint directive that has whitespace characters before the
|
||||
* directive name is not recognized by JSLint.
|
||||
* @kind problem
|
||||
* @problem.severity recommendation
|
||||
* @id js/jslint/invalid-directive
|
||||
* @tags maintainability
|
||||
* @precision medium
|
||||
* @deprecated JSLint is rarely used any more. Deprecated since 1.17.
|
||||
*/
|
||||
|
||||
import javascript
|
||||
|
||||
from SlashStarComment c
|
||||
where
|
||||
// use possessive quantifiers '*+' and '++' to avoid backtracking
|
||||
c
|
||||
.getText()
|
||||
.regexpMatch("\\s+(global|properties|property|jslint)\\s(\\s*+[a-zA-Z$_][a-zA-Z0-9$_]*+(\\s*+:\\s*+\\w++)?\\s*+,?)++\\s*")
|
||||
select c, "JSLint directives must not have whitespace characters before the directive name."
|
||||
@@ -1,49 +0,0 @@
|
||||
<!DOCTYPE qhelp PUBLIC
|
||||
"-//Semmle//qhelp//EN"
|
||||
"qhelp.dtd">
|
||||
<qhelp>
|
||||
<overview>
|
||||
<p>
|
||||
JSLint directives must consist of a comma-separated list of flags, where each flag
|
||||
can optionally be followed by a colon and a value. The value may either be a number
|
||||
or a Boolean (that is, 'true' or 'false'). Directives must not contain other
|
||||
characters such as '*', which some editors may automatically insert after every line
|
||||
break when editing a block comment.
|
||||
</p>
|
||||
|
||||
</overview>
|
||||
<recommendation>
|
||||
|
||||
<p>
|
||||
Insert commas where necessary and remove stray characters.
|
||||
</p>
|
||||
|
||||
</recommendation>
|
||||
<example>
|
||||
|
||||
<p>
|
||||
For example, <code>/*jslint nomen:true vars:true*/</code> is not a well-formed
|
||||
JSLint directive; it should be replaced by <code>/*jslint nomen:true, vars:true*/</code>.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
This is another example of a malformed JSLint directive:
|
||||
</p>
|
||||
|
||||
<sample src="examples/MalformedJSLintDirective.js" />
|
||||
|
||||
<p>
|
||||
It should be fixed as follows:
|
||||
</p>
|
||||
|
||||
<sample src="examples/MalformedJSLintDirectiveGood.js" />
|
||||
|
||||
</example>
|
||||
<references>
|
||||
|
||||
|
||||
<li>JSLint: <a href="http://www.jslint.com/help.html">JSLint Help</a>.</li>
|
||||
|
||||
|
||||
</references>
|
||||
</qhelp>
|
||||
@@ -1,26 +0,0 @@
|
||||
/**
|
||||
* @name Malformed JSLint directive
|
||||
* @description A malformed JSLint directive will be rejected by JSLint, and may be either
|
||||
* rejected or ignored by other tools.
|
||||
* @kind problem
|
||||
* @problem.severity recommendation
|
||||
* @id js/jslint/malformed-directive
|
||||
* @tags maintainability
|
||||
* @precision medium
|
||||
* @deprecated JSLint is rarely used any more. Deprecated since 1.17.
|
||||
*/
|
||||
|
||||
import javascript
|
||||
|
||||
from JSLintDirective dir, string flag, string flags, string directive
|
||||
where
|
||||
// a flag, optionally followed by a colon and a value, where the value may be
|
||||
// a Boolean or a number
|
||||
flag = "[a-zA-Z$_][a-zA-Z0-9$_]*(\\s*:\\s*(true|false|\\d+))?" and
|
||||
// a non-empty, comma-separated list of flags
|
||||
flags = "(" + flag + "\\s*,\\s*)*" + flag and
|
||||
// a word (which is the directive's name), followed by a possibly empty list of flags
|
||||
// note that there may be trailing whitespace, but no leading whitespace
|
||||
directive = "\\s*\\w+\\s+(" + flags + ")?\\s*" and
|
||||
not dir.getText().regexpMatch(directive)
|
||||
select dir, "Malformed JSLint directive."
|
||||
@@ -1,42 +0,0 @@
|
||||
<!DOCTYPE qhelp PUBLIC
|
||||
"-//Semmle//qhelp//EN"
|
||||
"qhelp.dtd">
|
||||
<qhelp>
|
||||
<overview>
|
||||
<p>
|
||||
HTML comments are a technique for hiding JavaScript code from browsers that do not interpret <code>script</code>
|
||||
tags. Since all popular browsers have supported <code>script</code> tags for many years, this precaution is
|
||||
not needed any more.
|
||||
</p>
|
||||
|
||||
</overview>
|
||||
<recommendation>
|
||||
|
||||
<p>
|
||||
Remove all HTML comments.
|
||||
</p>
|
||||
|
||||
</recommendation>
|
||||
<example>
|
||||
|
||||
<p>
|
||||
The following code block uses HTML comments to hide the <code>script</code> block from ancient browsers.
|
||||
</p>
|
||||
|
||||
<sample src="examples/HTMLComments.js" />
|
||||
|
||||
<p>
|
||||
Since such browsers are no longer widely used, the comments should be removed:
|
||||
</p>
|
||||
|
||||
<sample src="examples/HTMLCommentsGood.js" />
|
||||
|
||||
</example>
|
||||
<references>
|
||||
|
||||
|
||||
<li>JavaScript Toolbox: <a href="http://www.javascripttoolbox.com/bestpractices/#comments">JavaScript Best Practices</a>.</li>
|
||||
|
||||
|
||||
</references>
|
||||
</qhelp>
|
||||
@@ -1,18 +0,0 @@
|
||||
/**
|
||||
* @name Use of HTML comments
|
||||
* @description HTML-style comments are not a standard ECMAScript feature and should be avoided.
|
||||
* @kind problem
|
||||
* @problem.severity recommendation
|
||||
* @id js/html-comment
|
||||
* @tags maintainability
|
||||
* language-features
|
||||
* external/cwe/cwe-758
|
||||
* @precision low
|
||||
* @deprecated HTML comments are recognized in the standard as an additional feature supported by
|
||||
* web browsers. Deprecated since 1.17.
|
||||
*/
|
||||
|
||||
import javascript
|
||||
|
||||
from HtmlLineComment c
|
||||
select c, "Do not use HTML comments."
|
||||
@@ -61,5 +61,7 @@ where
|
||||
forex(DataFlow::InvokeNode cs2, Function otherCallee |
|
||||
cs2.getInvokeExpr() = cs.getInvokeExpr() and otherCallee = cs2.getACallee() |
|
||||
illegalInvocation(cs, otherCallee, _, _)
|
||||
)
|
||||
) and
|
||||
// require that all callees are known
|
||||
not cs.isIncomplete()
|
||||
select cs, "Illegal invocation of $@ " + how + ".", callee, calleeDesc
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
<!DOCTYPE qhelp PUBLIC
|
||||
"-//Semmle//qhelp//EN"
|
||||
"qhelp.dtd">
|
||||
<qhelp>
|
||||
<overview>
|
||||
<p>
|
||||
Multi-line string literals are not supported on all platforms, and thus should be avoided.
|
||||
</p>
|
||||
|
||||
</overview>
|
||||
<recommendation>
|
||||
|
||||
<p>
|
||||
Replace multi-line string literals by multiple strings concatenated with the <code>+</code> operator.
|
||||
</p>
|
||||
|
||||
</recommendation>
|
||||
<example>
|
||||
|
||||
<p>
|
||||
The following example contains a string literal spanning three lines:
|
||||
</p>
|
||||
|
||||
<sample src="examples/MultilineStringLiteral.js" />
|
||||
|
||||
<p>
|
||||
It should be rewritten like this:
|
||||
</p>
|
||||
|
||||
<sample src="examples/MultilineStringLiteralGood.js" />
|
||||
|
||||
</example>
|
||||
<references>
|
||||
|
||||
|
||||
<li>Ecma International, <i>ECMAScript Language Definition</i>, 5.1 Edition, Section 7.8.4. ECMA, 2011.</li>
|
||||
|
||||
|
||||
</references>
|
||||
</qhelp>
|
||||
@@ -1,19 +0,0 @@
|
||||
/**
|
||||
* @name Multi-line string literal
|
||||
* @description Multi-line string literals are non-standard and hard to read, and should be avoided.
|
||||
* @kind problem
|
||||
* @problem.severity recommendation
|
||||
* @id js/multi-line-string
|
||||
* @tags maintainability
|
||||
* external/cwe/cwe-758
|
||||
* @precision low
|
||||
* @deprecated Multi-line string literals are now a standard language feature. Deprecated since 1.17.
|
||||
*/
|
||||
|
||||
import javascript
|
||||
|
||||
from StringLiteral sl, Location l
|
||||
where
|
||||
l = sl.getLocation() and
|
||||
l.getStartLine() != l.getEndLine()
|
||||
select sl, "Avoid multi-line string literals."
|
||||
@@ -1,40 +0,0 @@
|
||||
<!DOCTYPE qhelp PUBLIC
|
||||
"-//Semmle//qhelp//EN"
|
||||
"qhelp.dtd">
|
||||
<qhelp>
|
||||
<overview>
|
||||
<p>
|
||||
Integer literals starting with the digit <code>0</code> may be interpreted as octal numbers by some platforms
|
||||
but not others, and thus should be avoided. This does not make a difference for the literal <code>0</code>
|
||||
itself.
|
||||
</p>
|
||||
|
||||
</overview>
|
||||
<recommendation>
|
||||
|
||||
<p>
|
||||
If the literal was meant to be octal, convert it to a decimal or hexadecimal number. Otherwise, remove
|
||||
the leading zero.
|
||||
</p>
|
||||
|
||||
</recommendation>
|
||||
<example>
|
||||
|
||||
<p>
|
||||
The following example uses the literal <code>012</code>, which some platforms will interpret as an octal
|
||||
encoding of the decimal number <code>10</code>, while others will interpret it as the decimal number
|
||||
<code>12</code>. Depending on the desired interpretation, it should be replaced with either <code>10</code>
|
||||
or <code>12</code>.
|
||||
</p>
|
||||
|
||||
<sample src="examples/OctalLiteral.js" />
|
||||
|
||||
</example>
|
||||
<references>
|
||||
|
||||
|
||||
<li>Ecma International, <i>ECMAScript Language Definition</i>, 5.1 Edition, Annex B.1.1. ECMA, 2011.</li>
|
||||
|
||||
|
||||
</references>
|
||||
</qhelp>
|
||||
@@ -1,17 +0,0 @@
|
||||
/**
|
||||
* @name Octal literal
|
||||
* @description Octal numeric literals are a platform-specific extension and should not be used.
|
||||
* @kind problem
|
||||
* @problem.severity recommendation
|
||||
* @id js/octal-literal
|
||||
* @tags portability
|
||||
* external/cwe/cwe-758
|
||||
* @precision low
|
||||
* @deprecated This query is prone to false positives. Deprecated since 1.17.
|
||||
*/
|
||||
|
||||
import javascript
|
||||
|
||||
from NumberLiteral nl
|
||||
where nl.getRawValue().regexpMatch("0\\d+")
|
||||
select nl, "Do not use octal literals."
|
||||
@@ -1,37 +0,0 @@
|
||||
<!DOCTYPE qhelp PUBLIC
|
||||
"-//Semmle//qhelp//EN"
|
||||
"qhelp.dtd">
|
||||
<qhelp>
|
||||
<overview>
|
||||
<p>
|
||||
The ECMAScript standard defines a list of future keywords that should not be used as identifiers.
|
||||
While they may be accepted by current implementations, they may no longer be supported in the future,
|
||||
so it is best not to rely on them.
|
||||
</p>
|
||||
|
||||
</overview>
|
||||
<recommendation>
|
||||
|
||||
<p>
|
||||
Rename the identifier in question.
|
||||
</p>
|
||||
|
||||
</recommendation>
|
||||
<example>
|
||||
|
||||
<p>
|
||||
In the following code snippet, <code>package</code> is used as a variable name. Since <code>package</code>
|
||||
is a future reserved word, the variable should be renamed, for instance to <code>pkg</code>.
|
||||
</p>
|
||||
|
||||
<sample src="examples/ReservedWords.js" />
|
||||
|
||||
</example>
|
||||
<references>
|
||||
|
||||
|
||||
<li>Ecma International, <i>ECMAScript Language Definition</i>, 5.1 Edition, Section 7.6.1.2. ECMA, 2011.</li>
|
||||
|
||||
|
||||
</references>
|
||||
</qhelp>
|
||||
@@ -1,24 +0,0 @@
|
||||
/**
|
||||
* @name Reserved word used as variable name
|
||||
* @description Future reserved words should not be used as variable names.
|
||||
* @kind problem
|
||||
* @problem.severity recommendation
|
||||
* @id js/use-of-reserved-word
|
||||
* @tags maintainability
|
||||
* language-features
|
||||
* @precision very-high
|
||||
* @deprecated This is no longer a problem with modern browsers. Deprecated since 1.17.
|
||||
*/
|
||||
|
||||
import javascript
|
||||
|
||||
from Identifier id
|
||||
where
|
||||
id
|
||||
.getName()
|
||||
.regexpMatch("class|const|enum|export|extends|import|super|implements|interface|let|package|private|protected|public|static|yield") and
|
||||
not exists(DotExpr de | id = de.getProperty()) and
|
||||
not exists(Property prop | id = prop.getNameExpr()) and
|
||||
// exclude JSX attribute names
|
||||
not exists(JSXElement e | id = e.getAnAttribute().getNameExpr())
|
||||
select id, "Identifier name is a reserved word."
|
||||
@@ -1,37 +0,0 @@
|
||||
<!DOCTYPE qhelp PUBLIC
|
||||
"-//Semmle//qhelp//EN"
|
||||
"qhelp.dtd">
|
||||
<qhelp>
|
||||
<overview>
|
||||
<p>
|
||||
The ECMAScript standard allows trailing commas in array and object literals which are ignored. However,
|
||||
older versions of Internet Explorer do not recognize this syntax. Moreover, it can lead to confusion
|
||||
when used in array literals, since spurious commas other than the last one are not ignored but give rise
|
||||
to additional undefined array elements. For these reasons, trailing commas should always be avoided.
|
||||
</p>
|
||||
|
||||
</overview>
|
||||
<recommendation>
|
||||
|
||||
<p>
|
||||
Remove the trailing comma.
|
||||
</p>
|
||||
|
||||
</recommendation>
|
||||
<example>
|
||||
|
||||
<p>
|
||||
The following code snippet shows an object literal with a trailing comma, which should be removed.
|
||||
</p>
|
||||
|
||||
<sample src="examples/TrailingComma.js" />
|
||||
|
||||
</example>
|
||||
<references>
|
||||
|
||||
|
||||
<li>Ecma International, <i>ECMAScript Language Definition</i>, 5.1 Edition, Sections 11.1.4 and 11.1.5. ECMA, 2011.</li>
|
||||
|
||||
|
||||
</references>
|
||||
</qhelp>
|
||||
@@ -1,37 +0,0 @@
|
||||
/**
|
||||
* @name Trailing comma in array or object expressions
|
||||
* @description Trailing commas in array and object expressions are interpreted differently
|
||||
* by different browsers and should be avoided.
|
||||
* @kind problem
|
||||
* @problem.severity recommendation
|
||||
* @id js/trailing-comma-in-array-or-object
|
||||
* @tags portability
|
||||
* external/cwe/cwe-758
|
||||
* @precision low
|
||||
* @deprecated This is no longer a problem with modern browsers. Deprecated since 1.17.
|
||||
*/
|
||||
|
||||
import javascript
|
||||
|
||||
/** An array or object expression. */
|
||||
class ArrayOrObjectExpr extends Expr {
|
||||
ArrayOrObjectExpr() {
|
||||
this instanceof ArrayExpr or
|
||||
this instanceof ObjectExpr
|
||||
}
|
||||
|
||||
/** Holds if this array or object expression has a trailing comma. */
|
||||
predicate hasTrailingComma() {
|
||||
this.(ArrayExpr).hasTrailingComma() or
|
||||
this.(ObjectExpr).hasTrailingComma()
|
||||
}
|
||||
|
||||
/** Gets a short description of this expression. */
|
||||
string getShortName() {
|
||||
if this instanceof ArrayExpr then result = "array expression" else result = "object expression"
|
||||
}
|
||||
}
|
||||
|
||||
from ArrayOrObjectExpr e
|
||||
where e.hasTrailingComma()
|
||||
select e.getLastToken().getPreviousToken(), "Trailing comma in " + e.getShortName() + "."
|
||||
@@ -11,6 +11,7 @@
|
||||
*/
|
||||
|
||||
import javascript
|
||||
import semmle.javascript.CharacterEscapes
|
||||
|
||||
/**
|
||||
* Holds if `pattern` is a regular expression pattern for URLs with a host matched by `hostPart`,
|
||||
@@ -40,7 +41,9 @@ where
|
||||
)
|
||||
) and
|
||||
// ignore patterns with capture groups after the TLD
|
||||
not pattern.regexpMatch("(?i).*[.](" + RegExpPatterns::commonTLD() + ").*[(][?]:.*[)].*")
|
||||
not pattern.regexpMatch("(?i).*[.](" + RegExpPatterns::commonTLD() + ").*[(][?]:.*[)].*") and
|
||||
// avoid double reporting
|
||||
not CharacterEscapes::hasALikelyRegExpPatternMistake(re)
|
||||
select re,
|
||||
"This " + kind + " has an unescaped '.' before '" + hostPart +
|
||||
"', so it might match more hosts than expected.", aux, "here"
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
<!DOCTYPE qhelp PUBLIC
|
||||
"-//Semmle//qhelp//EN"
|
||||
"qhelp.dtd">
|
||||
<qhelp>
|
||||
|
||||
<overview>
|
||||
<p>
|
||||
|
||||
When a character in a string literal or regular expression
|
||||
literal is preceded by a backslash, it is interpreted as part of an
|
||||
escape sequence. For example, the escape sequence <code>\n</code> in a
|
||||
string literal corresponds to a single <code>newline</code> character,
|
||||
and not the <code>\</code> and <code>n</code> characters.
|
||||
|
||||
However, not all characters change meaning when used in an
|
||||
escape sequence. In this case, the backslash just makes the character
|
||||
appear to mean something else, and the backslash actually has no
|
||||
effect. For example, the escape sequence <code>\k</code> in a string
|
||||
literal just means <code>k</code>.
|
||||
|
||||
Such superfluous escape sequences are usually benign, and
|
||||
do not change the behavior of the program.
|
||||
|
||||
</p>
|
||||
|
||||
<p>
|
||||
|
||||
The set of characters that change meaning when in escape
|
||||
sequences is different for regular expression literals and string
|
||||
literals.
|
||||
|
||||
This can be problematic when a regular expression literal
|
||||
is turned into a regular expression that is built from one or more
|
||||
string literals. The problem occurs when a regular expression escape
|
||||
sequence loses its special meaning in a string literal.
|
||||
|
||||
</p>
|
||||
|
||||
</overview>
|
||||
|
||||
<recommendation>
|
||||
<p>
|
||||
|
||||
Ensure that the right amount of backslashes is used when
|
||||
escaping characters in strings, template literals and regular
|
||||
expressions.
|
||||
|
||||
Pay special attention to the number of backslashes when
|
||||
rewriting a regular expression as a string literal.
|
||||
|
||||
</p>
|
||||
</recommendation>
|
||||
|
||||
<example>
|
||||
|
||||
<p>
|
||||
|
||||
The following example code checks that a string is
|
||||
<code>"my-marker"</code>, possibly surrounded by white space:
|
||||
|
||||
</p>
|
||||
|
||||
<sample src="examples/UselessRegExpCharacterEscape_bad_1.js"/>
|
||||
|
||||
<p>
|
||||
|
||||
However, the check does not work properly for white space
|
||||
as the two <code>\s</code> occurrences are semantically equivalent to
|
||||
just <code>s</code>, meaning that the check will succeed for strings
|
||||
like <code>"smy-markers"</code> instead of <code>" my-marker
|
||||
"</code>.
|
||||
|
||||
Address these shortcomings by either using a regular
|
||||
expression literal (<code>/(^\s*)my-marker(\s*$)/</code>), or by
|
||||
adding extra backslashes
|
||||
(<code>'(^\\s*)my-marker(\\s*$)'</code>).
|
||||
|
||||
</p>
|
||||
|
||||
</example>
|
||||
|
||||
<references>
|
||||
<li>MDN: <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#Escaping">Regular expression escape notation</a></li>
|
||||
<li>MDN: <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String#Escape_notation">String escape notation</a></li>
|
||||
</references>
|
||||
</qhelp>
|
||||
@@ -0,0 +1,156 @@
|
||||
/**
|
||||
* @name Useless regular-expression character escape
|
||||
* @description Prepending a backslash to an ordinary character in a string
|
||||
* does not have any effect, and may make regular expressions constructed from this string
|
||||
* behave unexpectedly.
|
||||
* @kind problem
|
||||
* @problem.severity error
|
||||
* @precision high
|
||||
* @id js/useless-regexp-character-escape
|
||||
* @tags correctness
|
||||
* security
|
||||
* external/cwe/cwe-20
|
||||
*/
|
||||
|
||||
import javascript
|
||||
import semmle.javascript.CharacterEscapes::CharacterEscapes
|
||||
|
||||
newtype TRegExpPatternMistake =
|
||||
/**
|
||||
* A character escape mistake in regular expression string `src`
|
||||
* for the character `char` at `index` in `rawStringNode`, explained
|
||||
* by `mistake`.
|
||||
*/
|
||||
TIdentityEscapeInStringMistake(
|
||||
RegExpPatternSource src, string char, string mistake, ASTNode rawStringNode, int index
|
||||
) {
|
||||
char = getALikelyRegExpPatternMistake(src, mistake, rawStringNode, index)
|
||||
} or
|
||||
/**
|
||||
* A backslash-escaped 'b' at `index` of `rawStringNode` in the
|
||||
* regular expression string `src`, indicating intent to use the
|
||||
* word-boundary assertion '\b'.
|
||||
*/
|
||||
TBackspaceInStringMistake(RegExpPatternSource src, ASTNode rawStringNode, int index) {
|
||||
exists(string raw, string cooked |
|
||||
exists(StringLiteral lit | lit = rawStringNode |
|
||||
rawStringNode = src.asExpr() and
|
||||
raw = lit.getRawValue() and
|
||||
cooked = lit.getStringValue()
|
||||
)
|
||||
or
|
||||
exists(TemplateElement elem | elem = rawStringNode |
|
||||
rawStringNode = src.asExpr().(TemplateLiteral).getAnElement() and
|
||||
raw = elem.getRawValue() and
|
||||
cooked = elem.getStringValue()
|
||||
)
|
||||
|
|
||||
"b" = getAnEscapedCharacter(raw, index) and
|
||||
// except if the string is exactly \b
|
||||
cooked.length() != 1
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* A character escape mistake in a regular expression string.
|
||||
*
|
||||
* Implementation note: the main purpose of this class is to associate an
|
||||
* exact character location with an alert message, in the name of
|
||||
* user-friendly alerts. The implementation can be simplified
|
||||
* significantly by only using the enclosing string location as the alert
|
||||
* location.
|
||||
*/
|
||||
class RegExpPatternMistake extends TRegExpPatternMistake {
|
||||
/**
|
||||
* Holds if this element is at the specified location.
|
||||
* The location spans column `startcolumn` of line `startline` to
|
||||
* column `endcolumn` of line `endline` in file `filepath`.
|
||||
* For more information, see
|
||||
* [Locations](https://help.semmle.com/QL/learn-ql/ql/locations.html).
|
||||
*/
|
||||
predicate hasLocationInfo(
|
||||
string filepath, int startline, int startcolumn, int endline, int endcolumn
|
||||
) {
|
||||
exists(int srcStartcolumn, int srcEndcolumn, int index |
|
||||
index = getIndex() and
|
||||
getRawStringNode()
|
||||
.getLocation()
|
||||
.hasLocationInfo(filepath, startline, srcStartcolumn, endline, srcEndcolumn)
|
||||
|
|
||||
(
|
||||
if startline = endline
|
||||
then startcolumn = srcStartcolumn + index - 1 and endcolumn = srcStartcolumn + index
|
||||
else (
|
||||
startcolumn = srcStartcolumn and endcolumn = srcEndcolumn
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/** Gets a textual representation of this element. */
|
||||
string toString() { result = getMessage() }
|
||||
|
||||
abstract ASTNode getRawStringNode();
|
||||
|
||||
abstract RegExpPatternSource getSrc();
|
||||
|
||||
abstract int getIndex();
|
||||
|
||||
abstract string getMessage();
|
||||
}
|
||||
|
||||
/**
|
||||
* An identity-escaped character that indicates programmer intent to
|
||||
* do something special in a regular expression.
|
||||
*/
|
||||
class IdentityEscapeInStringMistake extends RegExpPatternMistake, TIdentityEscapeInStringMistake {
|
||||
RegExpPatternSource src;
|
||||
|
||||
string char;
|
||||
|
||||
string mistake;
|
||||
|
||||
int index;
|
||||
|
||||
ASTNode rawStringNode;
|
||||
|
||||
IdentityEscapeInStringMistake() {
|
||||
this = TIdentityEscapeInStringMistake(src, char, mistake, rawStringNode, index)
|
||||
}
|
||||
|
||||
override string getMessage() {
|
||||
result = "'\\" + char + "' is equivalent to just '" + char + "', so the sequence " + mistake
|
||||
}
|
||||
|
||||
override int getIndex() { result = index }
|
||||
|
||||
override RegExpPatternSource getSrc() { result = src }
|
||||
|
||||
override ASTNode getRawStringNode() { result = rawStringNode }
|
||||
}
|
||||
|
||||
/**
|
||||
* A backspace as '\b' in a regular expression string, indicating
|
||||
* programmer intent to use the word-boundary assertion '\b'.
|
||||
*/
|
||||
class BackspaceInStringMistake extends RegExpPatternMistake, TBackspaceInStringMistake {
|
||||
RegExpPatternSource src;
|
||||
|
||||
int index;
|
||||
|
||||
ASTNode rawStringNode;
|
||||
|
||||
BackspaceInStringMistake() { this = TBackspaceInStringMistake(src, rawStringNode, index) }
|
||||
|
||||
override string getMessage() { result = "'\\b' is a backspace, and not a word-boundary assertion" }
|
||||
|
||||
override int getIndex() { result = index }
|
||||
|
||||
override RegExpPatternSource getSrc() { result = src }
|
||||
|
||||
override ASTNode getRawStringNode() { result = rawStringNode }
|
||||
}
|
||||
|
||||
from RegExpPatternMistake mistake
|
||||
select mistake, "The escape sequence " + mistake.getMessage() + " when it is used in a $@.",
|
||||
mistake.getSrc().getAParse(), "regular expression"
|
||||
@@ -0,0 +1,2 @@
|
||||
let regex = new RegExp('(^\s*)my-marker(\s*$)'),
|
||||
isMyMarkerText = regex.test(text);
|
||||
@@ -0,0 +1,103 @@
|
||||
<!DOCTYPE qhelp PUBLIC
|
||||
"-//Semmle//qhelp//EN"
|
||||
"qhelp.dtd">
|
||||
<qhelp>
|
||||
<overview>
|
||||
<p>
|
||||
|
||||
Dynamically constructing a shell command with values from the
|
||||
local environment, such as file paths, may inadvertently
|
||||
change the meaning of the shell command.
|
||||
|
||||
Such changes can occur when an environment value contains
|
||||
characters that the shell interprets in a special way, for instance
|
||||
quotes and spaces.
|
||||
|
||||
This can result in the shell command misbehaving, or even
|
||||
allowing a malicious user to execute arbitrary commands on the system.
|
||||
|
||||
</p>
|
||||
|
||||
|
||||
</overview>
|
||||
<recommendation>
|
||||
|
||||
<p>
|
||||
|
||||
If possible, use hard-coded string literals to specify the
|
||||
shell command to run, and provide the dynamic arguments to the shell
|
||||
command separately to avoid interpretation by the shell.
|
||||
|
||||
</p>
|
||||
|
||||
<p>
|
||||
|
||||
Alternatively, if the shell command must be constructed
|
||||
dynamically, then add code to ensure that special characters in
|
||||
environment values do not alter the shell command unexpectedly.
|
||||
|
||||
</p>
|
||||
|
||||
</recommendation>
|
||||
<example>
|
||||
|
||||
<p>
|
||||
|
||||
The following example shows a dynamically constructed shell
|
||||
command that recursively removes a temporary directory that is located
|
||||
next to the currently executing JavaScript file. Such utilities are
|
||||
often found in custom build scripts.
|
||||
|
||||
</p>
|
||||
|
||||
<sample src="examples/shell-command-injection-from-environment.js" />
|
||||
|
||||
<p>
|
||||
|
||||
The shell command will, however, fail to work as intended if the
|
||||
absolute path of the script's directory contains spaces. In that
|
||||
case, the shell command will interpret the absolute path as multiple
|
||||
paths, instead of a single path.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
|
||||
For instance, if the absolute path of
|
||||
the temporary directory is <code>/home/username/important
|
||||
project/temp</code>, then the shell command will recursively delete
|
||||
<code>/home/username/important</code> and <code>project/temp</code>,
|
||||
where the latter path gets resolved relative to the working directory
|
||||
of the JavaScript process.
|
||||
|
||||
</p>
|
||||
|
||||
|
||||
<p>
|
||||
|
||||
Even worse, although less likely, a malicious user could
|
||||
provide the path <code>/home/username/; cat /etc/passwd #/important
|
||||
project/temp</code> in order to execute the command <code>cat
|
||||
/etc/passwd</code>.
|
||||
|
||||
</p>
|
||||
|
||||
<p>
|
||||
|
||||
To avoid such potentially catastrophic behaviors, provide the
|
||||
directory as an argument that does not get interpreted by a
|
||||
shell:
|
||||
|
||||
</p>
|
||||
|
||||
<sample src="examples/shell-command-injection-from-environment_fixed.js" />
|
||||
|
||||
</example>
|
||||
<references>
|
||||
|
||||
<li>
|
||||
OWASP:
|
||||
<a href="https://www.owasp.org/index.php/Command_Injection">Command Injection</a>.
|
||||
</li>
|
||||
|
||||
</references>
|
||||
</qhelp>
|
||||
@@ -0,0 +1,29 @@
|
||||
/**
|
||||
* @name Shell command built from environment values
|
||||
* @description Building a shell command string with values from the enclosing
|
||||
* environment may cause subtle bugs or vulnerabilities.
|
||||
* @kind path-problem
|
||||
* @problem.severity warning
|
||||
* @precision high
|
||||
* @id js/shell-command-injection-from-environment
|
||||
* @tags correctness
|
||||
* security
|
||||
* external/cwe/cwe-078
|
||||
* external/cwe/cwe-088
|
||||
*/
|
||||
|
||||
import javascript
|
||||
import DataFlow::PathGraph
|
||||
import semmle.javascript.security.dataflow.ShellCommandInjectionFromEnvironment::ShellCommandInjectionFromEnvironment
|
||||
|
||||
from
|
||||
Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink, DataFlow::Node highlight,
|
||||
Source sourceNode
|
||||
where
|
||||
sourceNode = source.getNode() and
|
||||
cfg.hasFlowPath(source, sink) and
|
||||
if cfg.isSinkWithHighlight(sink.getNode(), _)
|
||||
then cfg.isSinkWithHighlight(sink.getNode(), highlight)
|
||||
else highlight = sink.getNode()
|
||||
select highlight, source, sink, "This shell command depends on an uncontrolled $@.", sourceNode,
|
||||
sourceNode.getSourceType()
|
||||
@@ -0,0 +1,6 @@
|
||||
var cp = require("child_process"),
|
||||
path = require("path");
|
||||
function cleanupTemp() {
|
||||
let cmd = "rm -rf " + path.join(__dirname, "temp");
|
||||
cp.execSync(cmd); // BAD
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
var cp = require("child_process"),
|
||||
path = require("path");
|
||||
function cleanupTemp() {
|
||||
let cmd = "rm",
|
||||
args = ["-rf", path.join(__dirname, "temp")];
|
||||
cp.execFileSync(cmd, args); // GOOD
|
||||
}
|
||||
@@ -10,8 +10,8 @@ attacks such as cross-site scripting. One particular example of this is HTML ent
|
||||
where HTML special characters are replaced by HTML character entities to prevent them from being
|
||||
interpreted as HTML markup. For example, the less-than character is encoded as <code>&lt;</code>
|
||||
and the double-quote character as <code>&quot;</code>.
|
||||
Other examples include backslash-escaping for including untrusted data in string literals and
|
||||
percent-encoding for URI components.
|
||||
Other examples include backslash escaping or JSON encoding for including untrusted data in string
|
||||
literals, and percent-encoding for URI components.
|
||||
</p>
|
||||
<p>
|
||||
The reverse process of replacing escape sequences with the characters they represent is known as
|
||||
|
||||
@@ -46,7 +46,12 @@ string getStringValue(RegExpLiteral rl) {
|
||||
*/
|
||||
DataFlow::Node getASimplePredecessor(DataFlow::Node nd) {
|
||||
result = nd.getAPredecessor() and
|
||||
not nd.(DataFlow::SsaDefinitionNode).getSsaVariable().getDefinition() instanceof SsaPhiNode
|
||||
not exists(SsaDefinition ssa |
|
||||
ssa = nd.(DataFlow::SsaDefinitionNode).getSsaVariable().getDefinition()
|
||||
|
|
||||
ssa instanceof SsaPhiNode or
|
||||
ssa instanceof SsaVariableCapture
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -54,38 +59,31 @@ DataFlow::Node getASimplePredecessor(DataFlow::Node nd) {
|
||||
* into a form described by regular expression `regex`.
|
||||
*/
|
||||
predicate escapingScheme(string metachar, string regex) {
|
||||
metachar = "&" and regex = "&.*;"
|
||||
metachar = "&" and regex = "&.+;"
|
||||
or
|
||||
metachar = "%" and regex = "%.*"
|
||||
metachar = "%" and regex = "%.+"
|
||||
or
|
||||
metachar = "\\" and regex = "\\\\.*"
|
||||
metachar = "\\" and regex = "\\\\.+"
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to `String.prototype.replace` that replaces all instances of a pattern.
|
||||
* A method call that performs string replacement.
|
||||
*/
|
||||
class Replacement extends DataFlow::Node {
|
||||
RegExpLiteral pattern;
|
||||
|
||||
Replacement() {
|
||||
exists(DataFlow::MethodCallNode mcn | this = mcn |
|
||||
mcn.getMethodName() = "replace" and
|
||||
pattern.flow().(DataFlow::SourceNode).flowsTo(mcn.getArgument(0)) and
|
||||
mcn.getNumArgument() = 2 and
|
||||
pattern.isGlobal()
|
||||
)
|
||||
}
|
||||
|
||||
abstract class Replacement extends DataFlow::Node {
|
||||
/**
|
||||
* Holds if this replacement replaces the string `input` with `output`.
|
||||
*/
|
||||
predicate replaces(string input, string output) {
|
||||
exists(DataFlow::MethodCallNode mcn |
|
||||
mcn = this and
|
||||
input = getStringValue(pattern) and
|
||||
output = mcn.getArgument(1).getStringValue()
|
||||
)
|
||||
}
|
||||
abstract predicate replaces(string input, string output);
|
||||
|
||||
/**
|
||||
* Gets the input of this replacement.
|
||||
*/
|
||||
abstract DataFlow::Node getInput();
|
||||
|
||||
/**
|
||||
* Gets the output of this replacement.
|
||||
*/
|
||||
abstract DataFlow::SourceNode getOutput();
|
||||
|
||||
/**
|
||||
* Holds if this replacement escapes `char` using `metachar`.
|
||||
@@ -118,9 +116,12 @@ class Replacement extends DataFlow::Node {
|
||||
/**
|
||||
* Gets the previous replacement in this chain of replacements.
|
||||
*/
|
||||
Replacement getPreviousReplacement() {
|
||||
result = getASimplePredecessor*(this.(DataFlow::MethodCallNode).getReceiver())
|
||||
}
|
||||
Replacement getPreviousReplacement() { result.getOutput() = getASimplePredecessor*(getInput()) }
|
||||
|
||||
/**
|
||||
* Gets the next replacement in this chain of replacements.
|
||||
*/
|
||||
Replacement getNextReplacement() { this = result.getPreviousReplacement() }
|
||||
|
||||
/**
|
||||
* Gets an earlier replacement in this chain of replacements that
|
||||
@@ -130,7 +131,9 @@ class Replacement extends DataFlow::Node {
|
||||
exists(Replacement pred | pred = this.getPreviousReplacement() |
|
||||
if pred.escapes(_, metachar)
|
||||
then result = pred
|
||||
else result = pred.getAnEarlierEscaping(metachar)
|
||||
else (
|
||||
not pred.unescapes(metachar, _) and result = pred.getAnEarlierEscaping(metachar)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -142,11 +145,100 @@ class Replacement extends DataFlow::Node {
|
||||
exists(Replacement succ | this = succ.getPreviousReplacement() |
|
||||
if succ.unescapes(metachar, _)
|
||||
then result = succ
|
||||
else result = succ.getALaterUnescaping(metachar)
|
||||
else (
|
||||
not succ.escapes(_, metachar) and result = succ.getALaterUnescaping(metachar)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to `String.prototype.replace` that replaces all instances of a pattern.
|
||||
*/
|
||||
class GlobalStringReplacement extends Replacement, DataFlow::MethodCallNode {
|
||||
RegExpLiteral pattern;
|
||||
|
||||
GlobalStringReplacement() {
|
||||
this.getMethodName() = "replace" and
|
||||
pattern.flow().(DataFlow::SourceNode).flowsTo(this.getArgument(0)) and
|
||||
this.getNumArgument() = 2 and
|
||||
pattern.isGlobal()
|
||||
}
|
||||
|
||||
override predicate replaces(string input, string output) {
|
||||
input = getStringValue(pattern) and
|
||||
output = this.getArgument(1).getStringValue()
|
||||
or
|
||||
exists(DataFlow::FunctionNode replacer, DataFlow::PropRead pr, DataFlow::ObjectLiteralNode map |
|
||||
replacer = getCallback(1) and
|
||||
replacer.getParameter(0).flowsToExpr(pr.getPropertyNameExpr()) and
|
||||
pr = map.getAPropertyRead() and
|
||||
pr.flowsTo(replacer.getAReturn()) and
|
||||
map.asExpr().(ObjectExpr).getPropertyByName(input).getInit().getStringValue() = output
|
||||
)
|
||||
}
|
||||
|
||||
override DataFlow::Node getInput() { result = this.getReceiver() }
|
||||
|
||||
override DataFlow::SourceNode getOutput() { result = this }
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to `JSON.stringify`, viewed as a string replacement.
|
||||
*/
|
||||
class JsonStringifyReplacement extends Replacement, DataFlow::CallNode {
|
||||
JsonStringifyReplacement() { this = DataFlow::globalVarRef("JSON").getAMemberCall("stringify") }
|
||||
|
||||
override predicate replaces(string input, string output) {
|
||||
input = "\\" and output = "\\\\"
|
||||
// the other replacements are not relevant for this query
|
||||
}
|
||||
|
||||
override DataFlow::Node getInput() { result = this.getArgument(0) }
|
||||
|
||||
override DataFlow::SourceNode getOutput() { result = this }
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to `JSON.parse`, viewed as a string replacement.
|
||||
*/
|
||||
class JsonParseReplacement extends Replacement {
|
||||
JsonParserCall self;
|
||||
|
||||
JsonParseReplacement() { this = self }
|
||||
|
||||
override predicate replaces(string input, string output) {
|
||||
input = "\\\\" and output = "\\"
|
||||
// the other replacements are not relevant for this query
|
||||
}
|
||||
|
||||
override DataFlow::Node getInput() { result = self.getInput() }
|
||||
|
||||
override DataFlow::SourceNode getOutput() { result = self.getOutput() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A string replacement wrapped in a utility function.
|
||||
*/
|
||||
class WrappedReplacement extends Replacement, DataFlow::CallNode {
|
||||
int i;
|
||||
|
||||
Replacement inner;
|
||||
|
||||
WrappedReplacement() {
|
||||
exists(DataFlow::FunctionNode wrapped | wrapped.getFunction() = getACallee() |
|
||||
wrapped.getParameter(i).flowsTo(inner.getPreviousReplacement*().getInput()) and
|
||||
inner.getNextReplacement*().getOutput().flowsTo(wrapped.getAReturn())
|
||||
)
|
||||
}
|
||||
|
||||
override predicate replaces(string input, string output) { inner.replaces(input, output) }
|
||||
|
||||
override DataFlow::Node getInput() { result = getArgument(i) }
|
||||
|
||||
override DataFlow::SourceNode getOutput() { result = this }
|
||||
}
|
||||
|
||||
from Replacement primary, Replacement supplementary, string message, string metachar
|
||||
where
|
||||
primary.escapes(metachar, _) and
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
* @id js/loop-bound-injection
|
||||
* @tags security
|
||||
* external/cwe/cwe-834
|
||||
* @precision medium
|
||||
* @precision high
|
||||
*/
|
||||
|
||||
import javascript
|
||||
|
||||
@@ -14,17 +14,19 @@ import PortalExitSource
|
||||
import PortalEntrySink
|
||||
|
||||
from
|
||||
TaintTracking::Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink, Portal p1,
|
||||
Portal p2, DataFlow::FlowLabel lbl1, DataFlow::FlowLabel lbl2
|
||||
TaintTracking::Configuration cfg, DataFlow::SourcePathNode source, DataFlow::SinkPathNode sink, Portal p1,
|
||||
Portal p2, DataFlow::FlowLabel lbl1, DataFlow::FlowLabel lbl2, DataFlow::MidPathNode last
|
||||
where
|
||||
cfg.hasFlowPath(source, sink) and
|
||||
cfg = source.getConfiguration() and
|
||||
last = source.getASuccessor*() and
|
||||
sink = last.getASuccessor() and
|
||||
p1 = source.getNode().(PortalExitSource).getPortal() and
|
||||
p2 = sink.getNode().(PortalEntrySink).getPortal() and
|
||||
lbl1 = sink.getPathSummary().getStartLabel() and
|
||||
lbl2 = sink.getPathSummary().getEndLabel() and
|
||||
lbl1 = last.getPathSummary().getStartLabel() and
|
||||
lbl2 = last.getPathSummary().getEndLabel() and
|
||||
// avoid constructing infeasible paths
|
||||
sink.getPathSummary().hasCall() = false and
|
||||
sink.getPathSummary().hasReturn() = false and
|
||||
last.getPathSummary().hasCall() = false and
|
||||
last.getPathSummary().hasReturn() = false and
|
||||
// restrict to steps flow function parameters to returns
|
||||
p1.(ParameterPortal).getBasePortal() = p2.(ReturnPortal).getBasePortal() and
|
||||
// restrict to data/taint flow
|
||||
|
||||
@@ -11,10 +11,13 @@ import Configurations
|
||||
import PortalExitSource
|
||||
import SinkFromAnnotation
|
||||
|
||||
from DataFlow::Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink, Portal p
|
||||
from DataFlow::Configuration cfg, DataFlow::SourcePathNode source, DataFlow::SinkPathNode sink,
|
||||
Portal p, DataFlow::MidPathNode last
|
||||
where
|
||||
cfg.hasFlowPath(source, sink) and
|
||||
cfg = source.getConfiguration() and
|
||||
last = source.getASuccessor*() and
|
||||
sink = last.getASuccessor() and
|
||||
p = source.getNode().(PortalExitSource).getPortal() and
|
||||
// avoid constructing infeasible paths
|
||||
sink.getPathSummary().hasReturn() = false
|
||||
select p.toString(), source.getPathSummary().getStartLabel().toString(), cfg.toString()
|
||||
last.getPathSummary().hasReturn() = false
|
||||
select p.toString(), last.getPathSummary().getStartLabel().toString(), cfg.toString()
|
||||
|
||||
@@ -11,10 +11,13 @@ import Configurations
|
||||
import PortalEntrySink
|
||||
import SourceFromAnnotation
|
||||
|
||||
from DataFlow::Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink, Portal p
|
||||
from DataFlow::Configuration cfg, DataFlow::SourcePathNode source, DataFlow::SinkPathNode sink,
|
||||
Portal p, DataFlow::MidPathNode last
|
||||
where
|
||||
cfg.hasFlowPath(source, sink) and
|
||||
cfg = source.getConfiguration() and
|
||||
last = source.getASuccessor*() and
|
||||
sink = last.getASuccessor() and
|
||||
p = sink.getNode().(PortalEntrySink).getPortal() and
|
||||
// avoid constructing infeasible paths
|
||||
sink.getPathSummary().hasCall() = false
|
||||
select p.toString(), sink.getPathSummary().getEndLabel().toString(), cfg.toString()
|
||||
last.getPathSummary().hasCall() = false
|
||||
select p.toString(), last.getPathSummary().getEndLabel().toString(), cfg.toString()
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
<!DOCTYPE qhelp PUBLIC
|
||||
"-//Semmle//qhelp//EN"
|
||||
"qhelp.dtd">
|
||||
<qhelp>
|
||||
<overview>
|
||||
<p>
|
||||
On some platforms, the builtin function <code>parseInt</code> parses strings starting with the digit
|
||||
<code>0</code> as octal values (unless an explicit radix is provided). This can lead to unexpected
|
||||
results when parsing decimal numbers that may be zero-padded, such as dates.
|
||||
</p>
|
||||
|
||||
</overview>
|
||||
<recommendation>
|
||||
|
||||
<p>
|
||||
Provide an explicit radix as the second parameter to <code>parseInt</code>.
|
||||
</p>
|
||||
|
||||
</recommendation>
|
||||
<example>
|
||||
|
||||
<p>
|
||||
In the following example, <code>parseInt</code> is used to convert the contents of a field in an HTML
|
||||
form to a number:
|
||||
</p>
|
||||
|
||||
<sample src="examples/ParseIntRadix.js" />
|
||||
|
||||
<p>
|
||||
Now assume that a user has entered a zero-padded decimal number, say <code>09</code>, into the form.
|
||||
Since the first digit is a zero, older versions of <code>parseInt</code> interpret this value as an
|
||||
octal number. When they then encounter <code>9</code> (which is not an octal digit), they will stop
|
||||
parsing and discard the rest of the string, returning the value <code>0</code>, which is probably not
|
||||
what was expected.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
To avoid this problem, an explicit radix parameter should be parsed as follows:
|
||||
</p>
|
||||
|
||||
<sample src="examples/ParseIntRadixGood.js" />
|
||||
|
||||
</example>
|
||||
<references>
|
||||
|
||||
|
||||
<li>D. Crockford, <i>JavaScript: The Good Parts</i>, Appendix A.7. O'Reilly, 2008.</li>
|
||||
|
||||
|
||||
</references>
|
||||
</qhelp>
|
||||
@@ -1,21 +0,0 @@
|
||||
/**
|
||||
* @name Call to parseInt without radix
|
||||
* @description Calls to the 'parseInt' function should always specify a radix to avoid accidentally
|
||||
* parsing a number as octal.
|
||||
* @kind problem
|
||||
* @problem.severity recommendation
|
||||
* @id js/parseint-without-radix
|
||||
* @tags reliability
|
||||
* maintainability
|
||||
* external/cwe/cwe-676
|
||||
* @precision very-high
|
||||
* @deprecated This is no longer a problem with modern browsers. Deprecated since 1.17.
|
||||
*/
|
||||
|
||||
import javascript
|
||||
|
||||
from DataFlow::CallNode parseInt
|
||||
where
|
||||
parseInt = DataFlow::globalVarRef("parseInt").getACall() and
|
||||
parseInt.getNumArgument() = 1
|
||||
select parseInt, "Missing radix parameter."
|
||||
43
javascript/ql/src/Statements/UseOfReturnlessFunction.qhelp
Normal file
43
javascript/ql/src/Statements/UseOfReturnlessFunction.qhelp
Normal file
@@ -0,0 +1,43 @@
|
||||
<!DOCTYPE qhelp PUBLIC
|
||||
"-//Semmle//qhelp//EN"
|
||||
"qhelp.dtd">
|
||||
<qhelp>
|
||||
<overview>
|
||||
<p>
|
||||
JavaScript functions that do not return an expression will implicitly return
|
||||
<code>undefined</code>. Using the implicit return value from such a function
|
||||
is not an error in itself, but it is a pattern indicating that some
|
||||
misunderstanding has occurred.
|
||||
</p>
|
||||
|
||||
</overview>
|
||||
<recommendation>
|
||||
|
||||
<p>
|
||||
Do not use the return value from a function that does not return an expression.
|
||||
</p>
|
||||
|
||||
</recommendation>
|
||||
<example>
|
||||
|
||||
<p>
|
||||
In the example below, the function <code>renderText</code> is used to render
|
||||
text through side effects, and the function does not return an expression.
|
||||
However, the programmer still uses the return value from
|
||||
<code>renderText</code> as if the function returned an expression, which is
|
||||
clearly an error.
|
||||
</p>
|
||||
|
||||
<sample src="examples/UseOfReturnlessFunction.js" />
|
||||
|
||||
<p>
|
||||
The program can be fixed either by removing the use of the value returned by
|
||||
<code>renderText</code>, or by changing the <code>renderText</code> function
|
||||
to return an expression.
|
||||
</p>
|
||||
|
||||
</example>
|
||||
<references>
|
||||
<li>Mozilla Developer Network: <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/return">Return</a>.</li>
|
||||
</references>
|
||||
</qhelp>
|
||||
168
javascript/ql/src/Statements/UseOfReturnlessFunction.ql
Normal file
168
javascript/ql/src/Statements/UseOfReturnlessFunction.ql
Normal file
@@ -0,0 +1,168 @@
|
||||
/**
|
||||
* @name Use of returnless function
|
||||
* @description Using the return value of a function that does not return an expression is indicative of a mistake.
|
||||
* @kind problem
|
||||
* @problem.severity warning
|
||||
* @id js/use-of-returnless-function
|
||||
* @tags maintainability
|
||||
* correctness
|
||||
* @precision high
|
||||
*/
|
||||
|
||||
import javascript
|
||||
import Declarations.UnusedVariable
|
||||
import Expressions.ExprHasNoEffect
|
||||
import Statements.UselessConditional
|
||||
|
||||
predicate returnsVoid(Function f) {
|
||||
not f.isGenerator() and
|
||||
not f.isAsync() and
|
||||
not exists(f.getAReturnedExpr())
|
||||
}
|
||||
|
||||
predicate isStub(Function f) {
|
||||
f.getBody().(BlockStmt).getNumChild() = 0
|
||||
or
|
||||
f instanceof ExternalDecl
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `e` is in a syntactic context where it likely is fine that the value of `e` comes from a call to a returnless function.
|
||||
*/
|
||||
predicate benignContext(Expr e) {
|
||||
inVoidContext(e) or
|
||||
|
||||
// A return statement is often used to just end the function.
|
||||
e = any(Function f).getAReturnedExpr()
|
||||
or
|
||||
exists(ConditionalExpr cond | cond.getABranch() = e and benignContext(cond))
|
||||
or
|
||||
exists(LogicalBinaryExpr bin | bin.getAnOperand() = e and benignContext(bin))
|
||||
or
|
||||
exists(Expr parent | parent.getUnderlyingValue() = e and benignContext(parent))
|
||||
or
|
||||
any(VoidExpr voidExpr).getOperand() = e
|
||||
|
||||
or
|
||||
// weeds out calls inside HTML-attributes.
|
||||
e.getParent() instanceof CodeInAttribute or
|
||||
// and JSX-attributes.
|
||||
e = any(JSXAttribute attr).getValue() or
|
||||
|
||||
exists(AwaitExpr await | await.getOperand() = e and benignContext(await))
|
||||
or
|
||||
// Avoid double reporting with js/trivial-conditional
|
||||
isExplicitConditional(_, e)
|
||||
or
|
||||
// Avoid double reporting with js/comparison-between-incompatible-types
|
||||
any(Comparison binOp).getAnOperand() = e
|
||||
or
|
||||
// Avoid double reporting with js/property-access-on-non-object
|
||||
any(PropAccess ac).getBase() = e
|
||||
or
|
||||
// Avoid double-reporting with js/unused-local-variable
|
||||
exists(VariableDeclarator v | v.getInit() = e and v.getBindingPattern().getVariable() instanceof UnusedLocal)
|
||||
or
|
||||
// Avoid double reporting with js/call-to-non-callable
|
||||
any(InvokeExpr invoke).getCallee() = e
|
||||
or
|
||||
// arguments to Promise.resolve (and promise library variants) are benign.
|
||||
e = any(ResolvedPromiseDefinition promise).getValue().asExpr()
|
||||
}
|
||||
|
||||
predicate oneshotClosure(InvokeExpr call) {
|
||||
call.getCallee().getUnderlyingValue() instanceof ImmediatelyInvokedFunctionExpr
|
||||
}
|
||||
|
||||
predicate alwaysThrows(Function f) {
|
||||
exists(ReachableBasicBlock entry, DataFlow::Node throwNode |
|
||||
entry = f.getEntryBB() and
|
||||
throwNode.asExpr() = any(ThrowStmt t).getExpr() and
|
||||
entry.dominates(throwNode.getBasicBlock())
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if the last statement in the function is flagged by the js/useless-expression query.
|
||||
*/
|
||||
predicate lastStatementHasNoEffect(Function f) {
|
||||
hasNoEffect(f.getExit().getAPredecessor())
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `func` is a callee of `call`, and all possible callees of `call` never return a value.
|
||||
*/
|
||||
predicate callToVoidFunction(DataFlow::CallNode call, Function func) {
|
||||
not call.isIncomplete() and
|
||||
func = call.getACallee() and
|
||||
forall(Function f | f = call.getACallee() |
|
||||
returnsVoid(f) and not isStub(f) and not alwaysThrows(f)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `name` is the name of a method from `Array.prototype` or Lodash,
|
||||
* where that method takes a callback as parameter,
|
||||
* and the callback is expected to return a value.
|
||||
*/
|
||||
predicate hasNonVoidCallbackMethod(string name) {
|
||||
name = "every" or
|
||||
name = "filter" or
|
||||
name = "find" or
|
||||
name = "findIndex" or
|
||||
name = "flatMap" or
|
||||
name = "map" or
|
||||
name = "reduce" or
|
||||
name = "reduceRight" or
|
||||
name = "some" or
|
||||
name = "sort"
|
||||
}
|
||||
|
||||
DataFlow::SourceNode array(DataFlow::TypeTracker t) {
|
||||
t.start() and result instanceof DataFlow::ArrayCreationNode
|
||||
or
|
||||
exists (DataFlow::TypeTracker t2 |
|
||||
result = array(t2).track(t2, t)
|
||||
)
|
||||
}
|
||||
|
||||
DataFlow::SourceNode array() { result = array(DataFlow::TypeTracker::end()) }
|
||||
|
||||
/**
|
||||
* Holds if `call` is an Array or Lodash method accepting a callback `func`,
|
||||
* where the `call` expects a callback that returns an expression,
|
||||
* but `func` does not return a value.
|
||||
*/
|
||||
predicate voidArrayCallback(DataFlow::CallNode call, Function func) {
|
||||
hasNonVoidCallbackMethod(call.getCalleeName()) and
|
||||
exists(int index |
|
||||
index = min(int i | exists(call.getCallback(i))) and
|
||||
func = call.getCallback(index).getFunction()
|
||||
) and
|
||||
returnsVoid(func) and
|
||||
not isStub(func) and
|
||||
not alwaysThrows(func) and
|
||||
(
|
||||
call.getReceiver().getALocalSource() = array()
|
||||
or
|
||||
call.getCalleeNode().getALocalSource() instanceof LodashUnderscore::Member
|
||||
)
|
||||
}
|
||||
|
||||
from DataFlow::CallNode call, Function func, string name, string msg
|
||||
where
|
||||
(
|
||||
callToVoidFunction(call, func) and
|
||||
msg = "the $@ does not return anything, yet the return value is used." and
|
||||
name = func.describe()
|
||||
or
|
||||
voidArrayCallback(call, func) and
|
||||
msg = "the $@ does not return anything, yet the return value from the call to " + call.getCalleeName() + " is used." and
|
||||
name = "callback function"
|
||||
) and
|
||||
not benignContext(call.asExpr()) and
|
||||
not lastStatementHasNoEffect(func) and
|
||||
// anonymous one-shot closure. Those are used in weird ways and we ignore them.
|
||||
not oneshotClosure(call.asExpr())
|
||||
select
|
||||
call, msg, func, name
|
||||
@@ -16,6 +16,7 @@ import javascript
|
||||
import semmle.javascript.RestrictedLocations
|
||||
import semmle.javascript.dataflow.Refinements
|
||||
import semmle.javascript.DefensiveProgramming
|
||||
import UselessConditional
|
||||
|
||||
/**
|
||||
* Gets the unique definition of `v`.
|
||||
@@ -123,22 +124,6 @@ predicate whitelist(Expr e) {
|
||||
isConstantBooleanReturnValue(e)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `e` is part of a conditional node `cond` that evaluates
|
||||
* `e` and checks its value for truthiness, and the return value of `e`
|
||||
* is not used for anything other than this truthiness check.
|
||||
*/
|
||||
predicate isExplicitConditional(ASTNode cond, Expr e) {
|
||||
e = cond.(IfStmt).getCondition()
|
||||
or
|
||||
e = cond.(LoopStmt).getTest()
|
||||
or
|
||||
e = cond.(ConditionalExpr).getCondition()
|
||||
or
|
||||
isExplicitConditional(_, cond) and
|
||||
e = cond.(Expr).getUnderlyingValue().(LogicalBinaryExpr).getAnOperand()
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `e` is part of a conditional node `cond` that evaluates
|
||||
* `e` and checks its value for truthiness.
|
||||
|
||||
21
javascript/ql/src/Statements/UselessConditional.qll
Normal file
21
javascript/ql/src/Statements/UselessConditional.qll
Normal file
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* Provides predicates for working with useless conditionals.
|
||||
*/
|
||||
|
||||
import javascript
|
||||
|
||||
/**
|
||||
* Holds if `e` is part of a conditional node `cond` that evaluates
|
||||
* `e` and checks its value for truthiness, and the return value of `e`
|
||||
* is not used for anything other than this truthiness check.
|
||||
*/
|
||||
predicate isExplicitConditional(ASTNode cond, Expr e) {
|
||||
e = cond.(IfStmt).getCondition()
|
||||
or
|
||||
e = cond.(LoopStmt).getTest()
|
||||
or
|
||||
e = cond.(ConditionalExpr).getCondition()
|
||||
or
|
||||
isExplicitConditional(_, cond) and
|
||||
e = cond.(Expr).getUnderlyingValue().(LogicalBinaryExpr).getAnOperand()
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
var stage = require("./stage")
|
||||
|
||||
function renderText(text, id) {
|
||||
document.getElementById(id).innerText = text;
|
||||
}
|
||||
|
||||
var text = renderText("Two households, both alike in dignity", "scene");
|
||||
|
||||
stage.show(text);
|
||||
4
javascript/ql/src/codeql-suites/javascript-lgtm-full.qls
Normal file
4
javascript/ql/src/codeql-suites/javascript-lgtm-full.qls
Normal file
@@ -0,0 +1,4 @@
|
||||
- description: Standard LGTM queries for JavaScript, including ones not displayed by default
|
||||
- qlpack: codeql-javascript
|
||||
- apply: lgtm-selectors.yml
|
||||
from: codeql-suite-helpers
|
||||
4
javascript/ql/src/codeql-suites/javascript-lgtm.qls
Normal file
4
javascript/ql/src/codeql-suites/javascript-lgtm.qls
Normal file
@@ -0,0 +1,4 @@
|
||||
- description: Standard LGTM queries for JavaScript
|
||||
- apply: codeql-suites/javascript-lgtm-full.qls
|
||||
- apply: lgtm-displayed-only.yml
|
||||
from: codeql-suite-helpers
|
||||
@@ -47,97 +47,6 @@ class RelevantFunction extends Function {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `name` is a the name of an external module.
|
||||
*/
|
||||
predicate isExternalLibrary(string name) {
|
||||
// Mentioned in package.json
|
||||
any(Dependency dep).info(name, _) or
|
||||
// Node.js built-in
|
||||
name = "assert" or
|
||||
name = "async_hooks" or
|
||||
name = "child_process" or
|
||||
name = "cluster" or
|
||||
name = "crypto" or
|
||||
name = "dns" or
|
||||
name = "domain" or
|
||||
name = "events" or
|
||||
name = "fs" or
|
||||
name = "http" or
|
||||
name = "http2" or
|
||||
name = "https" or
|
||||
name = "inspector" or
|
||||
name = "net" or
|
||||
name = "os" or
|
||||
name = "path" or
|
||||
name = "perf_hooks" or
|
||||
name = "process" or
|
||||
name = "punycode" or
|
||||
name = "querystring" or
|
||||
name = "readline" or
|
||||
name = "repl" or
|
||||
name = "stream" or
|
||||
name = "string_decoder" or
|
||||
name = "timer" or
|
||||
name = "tls" or
|
||||
name = "trace_events" or
|
||||
name = "tty" or
|
||||
name = "dgram" or
|
||||
name = "url" or
|
||||
name = "util" or
|
||||
name = "v8" or
|
||||
name = "vm" or
|
||||
name = "worker_threads" or
|
||||
name = "zlib"
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if the global variable `name` is defined externally.
|
||||
*/
|
||||
predicate isExternalGlobal(string name) {
|
||||
exists(ExternalGlobalDecl decl | decl.getName() = name)
|
||||
or
|
||||
exists(Dependency dep |
|
||||
// If name is never assigned anywhere, and it coincides with a dependency,
|
||||
// it's most likely coming from there.
|
||||
dep.info(name, _) and
|
||||
not exists(Assignment assign | assign.getLhs().(GlobalVarAccess).getName() = name)
|
||||
)
|
||||
or
|
||||
name = "_"
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a node that was derived from an import of `moduleName`.
|
||||
*
|
||||
* This is a rough approximation as it follows all property reads, invocations,
|
||||
* and callbacks, so some of these might refer to internal objects.
|
||||
*
|
||||
* Additionally, we don't recognize when a project imports another file in the
|
||||
* same project using its module name (for example import "vscode" from inside the vscode project).
|
||||
*/
|
||||
SourceNode externalNode() {
|
||||
exists(string moduleName |
|
||||
result = moduleImport(moduleName) and
|
||||
isExternalLibrary(moduleName)
|
||||
)
|
||||
or
|
||||
exists(string name |
|
||||
result = globalVarRef(name) and
|
||||
isExternalGlobal(name)
|
||||
)
|
||||
or
|
||||
result = DOM::domValueRef()
|
||||
or
|
||||
result = jquery()
|
||||
or
|
||||
result = externalNode().getAPropertyRead()
|
||||
or
|
||||
result = externalNode().getAnInvocation()
|
||||
or
|
||||
result = externalNode().(InvokeNode).getCallback(_).getParameter(_)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a data flow node that can be resolved to a function, usually a callback.
|
||||
*
|
||||
@@ -167,7 +76,7 @@ SourceNode nodeLeadingToInvocation() {
|
||||
result.flowsTo(arg)
|
||||
)
|
||||
or
|
||||
exists(AdditionalPartialInvokeNode invoke, Node arg |
|
||||
exists(PartialInvokeNode invoke, Node arg |
|
||||
invoke.isPartialArgument(arg, _, _) and
|
||||
result.flowsTo(arg)
|
||||
)
|
||||
@@ -192,49 +101,15 @@ class ResolvableCall extends RelevantInvoke {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A call site that is believed to call an external function.
|
||||
*/
|
||||
class ExternalCall extends RelevantInvoke {
|
||||
ExternalCall() {
|
||||
not this instanceof ResolvableCall and // avoid double counting
|
||||
(
|
||||
// Call to modelled external library
|
||||
this = externalNode()
|
||||
or
|
||||
// 'require' call or similar
|
||||
this = moduleImport(_)
|
||||
or
|
||||
// Resolved to externs file
|
||||
exists(this.(InvokeNode).getACallee(1))
|
||||
or
|
||||
// Modelled as taint step but isn't from an NPM module, for example, `substring` or `push`.
|
||||
exists(TaintTracking::AdditionalTaintStep step |
|
||||
step.step(_, this)
|
||||
or
|
||||
step.step(this.getAnArgument(), _)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A call site that could not be resolved.
|
||||
*/
|
||||
class UnresolvableCall extends RelevantInvoke {
|
||||
UnresolvableCall() {
|
||||
not this instanceof ResolvableCall and
|
||||
not this instanceof ExternalCall
|
||||
not this instanceof ResolvableCall
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A call that is believed to call a function within the same project.
|
||||
*/
|
||||
class NonExternalCall extends RelevantInvoke {
|
||||
NonExternalCall() { not this instanceof ExternalCall }
|
||||
}
|
||||
|
||||
/**
|
||||
* A function with at least one call site.
|
||||
*/
|
||||
|
||||
4
javascript/ql/src/qlpack.yml
Normal file
4
javascript/ql/src/qlpack.yml
Normal file
@@ -0,0 +1,4 @@
|
||||
name: codeql-javascript
|
||||
version: 0.0.0
|
||||
dbscheme: semmlecode.javascript.dbscheme
|
||||
suites: codeql-suites
|
||||
97
javascript/ql/src/semmle/javascript/CharacterEscapes.qll
Normal file
97
javascript/ql/src/semmle/javascript/CharacterEscapes.qll
Normal file
@@ -0,0 +1,97 @@
|
||||
/**
|
||||
* Provides classes for reasoning about character escapes in literals.
|
||||
*/
|
||||
|
||||
import javascript
|
||||
|
||||
module CharacterEscapes {
|
||||
/**
|
||||
* Provides sets of characters (as strings) with specific string/regexp characteristics.
|
||||
*/
|
||||
private module Sets {
|
||||
string sharedEscapeChars() { result = "fnrtvux0\\" }
|
||||
|
||||
string regexpAssertionChars() { result = "bB" }
|
||||
|
||||
string regexpCharClassChars() { result = "cdDpPsSwW" }
|
||||
|
||||
string regexpBackreferenceChars() { result = "123456789k" }
|
||||
|
||||
string regexpMetaChars() { result = "^$*+?.()|{}[]-" }
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the `i`th character of `raw`, which is preceded by an uneven number of backslashes.
|
||||
*/
|
||||
bindingset[raw]
|
||||
string getAnEscapedCharacter(string raw, int i) {
|
||||
result = raw.regexpFind("(?<=(^|[^\\\\])\\\\(\\\\{2}){0,10}).", _, i)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `n` is delimited by `delim` and contains `rawStringNode` with the raw string value `raw`.
|
||||
*/
|
||||
private predicate hasRawStringAndQuote(
|
||||
DataFlow::ValueNode n, string delim, ASTNode rawStringNode, string raw
|
||||
) {
|
||||
rawStringNode = n.asExpr() and
|
||||
raw = rawStringNode.(StringLiteral).getRawValue() and
|
||||
delim = raw.charAt(0)
|
||||
or
|
||||
rawStringNode = n.asExpr().(TemplateLiteral).getAnElement() and
|
||||
raw = rawStringNode.(TemplateElement).getRawValue() and
|
||||
delim = "`"
|
||||
or
|
||||
rawStringNode = n.asExpr() and
|
||||
raw = rawStringNode.(RegExpLiteral).getRawValue() and
|
||||
delim = "/"
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a character in `n` that is preceded by a single useless backslash.
|
||||
*
|
||||
* The character is the `i`th character of `rawStringNode`'s raw string value.
|
||||
*/
|
||||
string getAnIdentityEscapedCharacter(DataFlow::Node n, ASTNode rawStringNode, int i) {
|
||||
exists(string delim, string raw, string additionalEscapeChars |
|
||||
hasRawStringAndQuote(n, delim, rawStringNode, raw) and
|
||||
if rawStringNode instanceof RegExpLiteral
|
||||
then
|
||||
additionalEscapeChars = Sets::regexpMetaChars() + Sets::regexpAssertionChars() + Sets::regexpCharClassChars() +
|
||||
Sets::regexpBackreferenceChars()
|
||||
else additionalEscapeChars = "b"
|
||||
|
|
||||
result = getAnEscapedCharacter(raw, i) and
|
||||
not result = (Sets::sharedEscapeChars() + delim + additionalEscapeChars).charAt(_)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `src` likely contains a regular expression mistake, to be reported by `js/useless-regexp-character-escape`.
|
||||
*/
|
||||
predicate hasALikelyRegExpPatternMistake(RegExpPatternSource src) {
|
||||
exists(getALikelyRegExpPatternMistake(src, _, _, _))
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a character in `n` that is preceded by a single useless backslash, resulting in a likely regular expression mistake explained by `mistake`.
|
||||
*
|
||||
* The character is the `i`th character of the raw string value of `rawStringNode`.
|
||||
*/
|
||||
string getALikelyRegExpPatternMistake(
|
||||
RegExpPatternSource src, string mistake, ASTNode rawStringNode, int i
|
||||
) {
|
||||
result = getAnIdentityEscapedCharacter(src, rawStringNode, i) and
|
||||
(
|
||||
result = Sets::regexpAssertionChars().charAt(_) and mistake = "is not an assertion"
|
||||
or
|
||||
result = Sets::regexpCharClassChars().charAt(_) and mistake = "is not a character class"
|
||||
or
|
||||
result = Sets::regexpBackreferenceChars().charAt(_) and mistake = "is not a backreference"
|
||||
or
|
||||
// conservative formulation: we do not know in general if the sequence is enclosed in a character class `[...]`
|
||||
result = Sets::regexpMetaChars().charAt(_) and
|
||||
mistake = "may still represent a meta-character"
|
||||
)
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user