mirror of
https://github.com/github/codeql.git
synced 2026-04-30 03:05:15 +02:00
Merge branch 'main' into codeql-ci/js/ml-powered-pack-release-0.3.3
This commit is contained in:
@@ -0,0 +1,11 @@
|
||||
class AstNodeWithSymbol extends @ast_node_with_symbol {
|
||||
string toString() { none() }
|
||||
}
|
||||
|
||||
class Symbol extends @symbol {
|
||||
string toString() { none() }
|
||||
}
|
||||
|
||||
from AstNodeWithSymbol node, Symbol symbol
|
||||
where ast_node_symbol(node, symbol) and not node instanceof @external_module_declaration
|
||||
select node, symbol
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,4 @@
|
||||
description: Associate symbols with external module declarations
|
||||
compatibility: backwards
|
||||
|
||||
ast_node_symbol.rel: run ast_node_symbol.qlo
|
||||
1217
javascript/downgrades/initial/semmlecode.javascript.dbscheme
Normal file
1217
javascript/downgrades/initial/semmlecode.javascript.dbscheme
Normal file
File diff suppressed because it is too large
Load Diff
4
javascript/downgrades/qlpack.yml
Normal file
4
javascript/downgrades/qlpack.yml
Normal file
@@ -0,0 +1,4 @@
|
||||
name: codeql/javascript-downgrades
|
||||
groups: javascript
|
||||
downgrades: .
|
||||
library: true
|
||||
@@ -1696,4 +1696,3 @@ module.exports.R_OK = fs.R_OK;
|
||||
module.exports.W_OK = fs.W_OK;
|
||||
|
||||
module.exports.X_OK = fs.X_OK;
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"name": "typescript-parser-wrapper",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"typescript": "4.6.2"
|
||||
"typescript": "4.8.2"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "tsc --project tsconfig.json",
|
||||
|
||||
@@ -168,20 +168,9 @@ export function augmentAst(ast: AugmentedSourceFile, code: string, project: Proj
|
||||
}
|
||||
}
|
||||
|
||||
// Number of conditional type expressions the visitor is currently inside.
|
||||
// We disable type extraction inside such type expressions, to avoid complications
|
||||
// with `infer` types.
|
||||
let insideConditionalTypes = 0;
|
||||
|
||||
visitAstNode(ast);
|
||||
function visitAstNode(node: AugmentedNode) {
|
||||
if (node.kind === ts.SyntaxKind.ConditionalType) {
|
||||
++insideConditionalTypes;
|
||||
}
|
||||
ts.forEachChild(node, visitAstNode);
|
||||
if (node.kind === ts.SyntaxKind.ConditionalType) {
|
||||
--insideConditionalTypes;
|
||||
}
|
||||
|
||||
// fill in line/column info
|
||||
if ("pos" in node) {
|
||||
@@ -202,7 +191,7 @@ export function augmentAst(ast: AugmentedSourceFile, code: string, project: Proj
|
||||
}
|
||||
}
|
||||
|
||||
if (typeChecker != null && insideConditionalTypes === 0) {
|
||||
if (typeChecker != null) {
|
||||
if (isTypedNode(node)) {
|
||||
let contextualType = isContextuallyTypedNode(node)
|
||||
? typeChecker.getContextualType(node)
|
||||
|
||||
@@ -241,7 +241,7 @@ const astProperties: string[] = [
|
||||
"constructor",
|
||||
"declarationList",
|
||||
"declarations",
|
||||
"decorators",
|
||||
"illegalDecorators",
|
||||
"default",
|
||||
"delete",
|
||||
"dotDotDotToken",
|
||||
@@ -670,6 +670,12 @@ function handleOpenProjectCommand(command: OpenProjectCommand) {
|
||||
if (file.endsWith(".d.ts")) {
|
||||
return pathlib.basename(file, ".d.ts");
|
||||
}
|
||||
if (file.endsWith(".d.mts") || file.endsWith(".d.cts")) {
|
||||
// We don't extract d.mts or d.cts files, but their symbol can coincide with that of a d.ts file,
|
||||
// which means any module bindings we generate for it will ultimately be visible in QL.
|
||||
let base = pathlib.basename(file);
|
||||
return base.substring(0, base.length - '.d.mts'.length);
|
||||
}
|
||||
let base = pathlib.basename(file);
|
||||
let dot = base.lastIndexOf('.');
|
||||
return dot === -1 || dot === 0 ? base : base.substring(0, dot);
|
||||
|
||||
@@ -947,7 +947,7 @@ export class TypeTable {
|
||||
* Returns a unique string for the given call/constructor signature.
|
||||
*/
|
||||
private getSignatureString(kind: ts.SignatureKind, signature: AugmentedSignature): string {
|
||||
let modifiers : ts.ModifiersArray = signature.getDeclaration()?.modifiers;
|
||||
let modifiers = signature.getDeclaration()?.modifiers;
|
||||
let isAbstract = modifiers && modifiers.filter(modifier => modifier.kind == ts.SyntaxKind.AbstractKeyword).length > 0
|
||||
|
||||
let parameters = signature.getParameters();
|
||||
@@ -1068,6 +1068,7 @@ export class TypeTable {
|
||||
let superType = this.typeChecker.getTypeFromTypeNode(typeExpr);
|
||||
if (superType == null) continue;
|
||||
let baseTypeSymbol = superType.symbol;
|
||||
baseTypeSymbol = (baseTypeSymbol as any)?.type?.symbol ?? baseTypeSymbol;
|
||||
if (baseTypeSymbol == null) continue;
|
||||
let baseId = this.getSymbolId(baseTypeSymbol);
|
||||
// Note: take care not to perform a recursive call between the two `push` calls.
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
version "12.7.11"
|
||||
resolved node-12.7.11.tgz#be879b52031cfb5d295b047f5462d8ef1a716446
|
||||
|
||||
typescript@4.6.2:
|
||||
version "4.6.2"
|
||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.6.2.tgz#fe12d2727b708f4eef40f51598b3398baa9611d4"
|
||||
integrity sha512-HM/hFigTBHZhLXshn9sN37H085+hQGeJHJ/X7LpBWLID/fbc2acUMfU+lGD98X81sKP+pFa9f0DZmCwB9GnbAg==
|
||||
typescript@4.8.2:
|
||||
version "4.8.2"
|
||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.8.2.tgz#e3b33d5ccfb5914e4eeab6699cf208adee3fd790"
|
||||
integrity sha512-C0I1UsrrDHo2fYI5oaCGbSejwX4ch+9Y5jTQELvovfmFkK3HHSZJB8MSJcWLmCUBzQBchCrZ9rMRV6GuNrvGtw==
|
||||
|
||||
@@ -448,7 +448,11 @@ public class ESNextParser extends JSXParser {
|
||||
protected Statement parseForStatement(Position startLoc) {
|
||||
int startPos = this.start;
|
||||
boolean isAwait = false;
|
||||
if (this.inAsync && this.eatContextual("await")) isAwait = true;
|
||||
if (this.inAsync || (options.esnext() && !this.inFunction)) {
|
||||
if (this.eatContextual("await")) {
|
||||
isAwait = true;
|
||||
}
|
||||
}
|
||||
Statement forStmt = super.parseForStatement(startLoc);
|
||||
if (isAwait) {
|
||||
if (forStmt instanceof ForOfStatement) ((ForOfStatement) forStmt).setAwait(true);
|
||||
|
||||
@@ -141,8 +141,9 @@ public class Fetcher {
|
||||
entryPath = entryPath.subpath(1, entryPath.getNameCount());
|
||||
|
||||
String filename = entryPath.getFileName().toString();
|
||||
if (!filename.endsWith(".d.ts") && !filename.equals("package.json")) {
|
||||
continue; // Only extract .d.ts files and package.json
|
||||
if (!filename.endsWith(".d.ts") && !filename.endsWith(".d.mts") && !filename.endsWith(".d.cts")
|
||||
&& !filename.equals("package.json")) {
|
||||
continue; // Only extract .d.ts, .d.mts, .d.cts files, and package.json
|
||||
}
|
||||
relativePaths.add(entryPath);
|
||||
Path outputFile = destDir.resolve(entryPath);
|
||||
|
||||
@@ -590,7 +590,7 @@ public class ASTExtractor {
|
||||
trapwriter.addTuple("literals", valueString, source, key);
|
||||
Position start = nd.getLoc().getStart();
|
||||
com.semmle.util.locations.Position startPos = new com.semmle.util.locations.Position(start.getLine(), start.getColumn() + 1 /* Convert from 0-based to 1-based. */, start.getOffset());
|
||||
|
||||
|
||||
if (nd.isRegExp()) {
|
||||
OffsetTranslation offsets = new OffsetTranslation();
|
||||
offsets.set(0, 1); // skip the initial '/'
|
||||
@@ -622,7 +622,7 @@ public class ASTExtractor {
|
||||
/**
|
||||
* Constant-folds simple string concatenations in `exp` while keeping an offset translation
|
||||
* that tracks back to the original source.
|
||||
*/
|
||||
*/
|
||||
private Pair<String, OffsetTranslation> getStringConcatResult(Expression exp) {
|
||||
if (exp instanceof BinaryExpression) {
|
||||
BinaryExpression be = (BinaryExpression) exp;
|
||||
@@ -636,7 +636,7 @@ public class ASTExtractor {
|
||||
if (str.length() > 1000) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
int delta = be.getRight().getLoc().getStart().getOffset() - be.getLeft().getLoc().getStart().getOffset();
|
||||
int offset = left.fst().length();
|
||||
return Pair.make(str, left.snd().append(right.snd(), offset, delta));
|
||||
@@ -747,7 +747,7 @@ public class ASTExtractor {
|
||||
visit(nd.getProperty(), key, 1, IdContext.TYPE_LABEL);
|
||||
} else {
|
||||
IdContext baseIdContext =
|
||||
c.idcontext == IdContext.EXPORT ? IdContext.EXPORT_BASE : IdContext.VAR_BIND;
|
||||
(c.idcontext == IdContext.EXPORT || c.idcontext == IdContext.EXPORT_BASE) ? IdContext.EXPORT_BASE : IdContext.VAR_BIND;
|
||||
visit(nd.getObject(), key, 0, baseIdContext);
|
||||
visit(nd.getProperty(), key, 1, nd.isComputed() ? IdContext.VAR_BIND : IdContext.LABEL);
|
||||
}
|
||||
@@ -848,14 +848,14 @@ public class ASTExtractor {
|
||||
public Label visit(BinaryExpression nd, Context c) {
|
||||
Label key = super.visit(nd, c);
|
||||
if (nd.getOperator().equals("in") && nd.getLeft() instanceof Identifier && ((Identifier)nd.getLeft()).getName().startsWith("#")) {
|
||||
// this happens with Ergonomic brand checks for Private Fields (see https://github.com/tc39/proposal-private-fields-in-in).
|
||||
// this happens with Ergonomic brand checks for Private Fields (see https://github.com/tc39/proposal-private-fields-in-in).
|
||||
// it's the only case where private field identifiers are used not as a field.
|
||||
visit(nd.getLeft(), key, 0, IdContext.LABEL, true);
|
||||
} else {
|
||||
visit(nd.getLeft(), key, 0, true);
|
||||
}
|
||||
visit(nd.getRight(), key, 1, true);
|
||||
|
||||
|
||||
extractRegxpFromBinop(nd, c);
|
||||
return key;
|
||||
}
|
||||
@@ -1815,7 +1815,7 @@ public class ASTExtractor {
|
||||
visit(nd.getLocal(), lbl, 1, nd.hasTypeKeyword() ? IdContext.TYPE_ONLY_IMPORT : c.idcontext);
|
||||
if (nd.hasTypeKeyword()) {
|
||||
trapwriter.addTuple("has_type_keyword", lbl);
|
||||
}
|
||||
}
|
||||
return lbl;
|
||||
}
|
||||
|
||||
@@ -2191,6 +2191,7 @@ public class ASTExtractor {
|
||||
visitAll(nd.getBody(), key);
|
||||
contextManager.leaveContainer();
|
||||
scopeManager.leaveScope();
|
||||
emitNodeSymbol(nd, key);
|
||||
return key;
|
||||
}
|
||||
|
||||
|
||||
@@ -86,8 +86,6 @@ import com.semmle.util.trap.TrapWriter;
|
||||
* <code>XML</code> is also supported
|
||||
* <li><code>LGTM_INDEX_XML_MODE</code>: whether to extract XML files
|
||||
* <li><code>LGTM_THREADS</code>: the maximum number of files to extract in parallel
|
||||
* <li><code>LGTM_TRAP_CACHE</code>: the path of a directory to use for trap caching
|
||||
* <li><code>LGTM_TRAP_CACHE_BOUND</code>: the size to bound the trap cache to
|
||||
* </ul>
|
||||
*
|
||||
* <p>It extracts the following:
|
||||
@@ -220,7 +218,7 @@ public class AutoBuild {
|
||||
this.LGTM_SRC = toRealPath(getPathFromEnvVar("LGTM_SRC"));
|
||||
this.SEMMLE_DIST = Paths.get(EnvironmentVariables.getExtractorRoot());
|
||||
this.outputConfig = new ExtractorOutputConfig(LegacyLanguage.JAVASCRIPT);
|
||||
this.trapCache = mkTrapCache();
|
||||
this.trapCache = ITrapCache.fromExtractorOptions();
|
||||
this.typeScriptMode =
|
||||
getEnumFromEnvVar("LGTM_INDEX_TYPESCRIPT", TypeScriptMode.class, TypeScriptMode.FULL);
|
||||
this.defaultEncoding = getEnvVar("LGTM_INDEX_DEFAULT_ENCODING");
|
||||
@@ -281,28 +279,6 @@ public class AutoBuild {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set up TRAP cache based on environment variables <code>LGTM_TRAP_CACHE</code> and <code>
|
||||
* LGTM_TRAP_CACHE_BOUND</code>.
|
||||
*/
|
||||
private ITrapCache mkTrapCache() {
|
||||
ITrapCache trapCache;
|
||||
String trapCachePath = getEnvVar("LGTM_TRAP_CACHE");
|
||||
if (trapCachePath != null) {
|
||||
Long sizeBound = null;
|
||||
String trapCacheBound = getEnvVar("LGTM_TRAP_CACHE_BOUND");
|
||||
if (trapCacheBound != null) {
|
||||
sizeBound = DefaultTrapCache.asFileSize(trapCacheBound);
|
||||
if (sizeBound == null)
|
||||
throw new UserError("Invalid TRAP cache size bound: " + trapCacheBound);
|
||||
}
|
||||
trapCache = new DefaultTrapCache(trapCachePath, sizeBound, Main.EXTRACTOR_VERSION);
|
||||
} else {
|
||||
trapCache = new DummyTrapCache();
|
||||
}
|
||||
return trapCache;
|
||||
}
|
||||
|
||||
private void setupFileTypes() {
|
||||
for (String spec : Main.NEWLINE.split(getEnvVar("LGTM_INDEX_FILETYPES", ""))) {
|
||||
spec = spec.trim();
|
||||
@@ -513,14 +489,13 @@ public class AutoBuild {
|
||||
SEMMLE_DIST.resolve(".cache").resolve("trap-cache").resolve("javascript");
|
||||
if (Files.isDirectory(trapCachePath)) {
|
||||
trapCache =
|
||||
new DefaultTrapCache(trapCachePath.toString(), null, Main.EXTRACTOR_VERSION) {
|
||||
new DefaultTrapCache(trapCachePath.toString(), null, Main.EXTRACTOR_VERSION, false) {
|
||||
boolean warnedAboutCacheMiss = false;
|
||||
|
||||
@Override
|
||||
public File lookup(String source, ExtractorConfig config, FileType type) {
|
||||
File f = super.lookup(source, config, type);
|
||||
// only return `f` if it exists; this has the effect of making the cache read-only
|
||||
if (f.exists()) return f;
|
||||
if (f != null) return f;
|
||||
// warn on first failed lookup
|
||||
if (!warnedAboutCacheMiss) {
|
||||
warn("Trap cache lookup for externs failed.");
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
package com.semmle.js.extractor;
|
||||
|
||||
import com.semmle.util.process.Env;
|
||||
|
||||
public class ExtractorOptionsUtil {
|
||||
public static String readExtractorOption(String... option) {
|
||||
StringBuilder name = new StringBuilder("CODEQL_EXTRACTOR_JAVASCRIPT_OPTION");
|
||||
for (String segment : option)
|
||||
name.append("_").append(segment.toUpperCase());
|
||||
return Env.systemEnv().getNonEmpty(name.toString());
|
||||
}
|
||||
}
|
||||
@@ -203,7 +203,7 @@ public class FileExtractor {
|
||||
}
|
||||
},
|
||||
|
||||
TYPESCRIPT(".ts", ".tsx") {
|
||||
TYPESCRIPT(".ts", ".tsx", ".mts", ".cts") {
|
||||
@Override
|
||||
protected boolean contains(File f, String lcExt, ExtractorConfig config) {
|
||||
if (config.getTypeScriptMode() == TypeScriptMode.NONE) return false;
|
||||
@@ -217,9 +217,6 @@ public class FileExtractor {
|
||||
}
|
||||
|
||||
private boolean hasBadFileHeader(File f, String lcExt, ExtractorConfig config) {
|
||||
if (!".ts".equals(lcExt)) {
|
||||
return false;
|
||||
}
|
||||
try (FileInputStream fis = new FileInputStream(f)) {
|
||||
byte[] bytes = new byte[fileHeaderSize];
|
||||
int length = fis.read(bytes);
|
||||
|
||||
@@ -40,7 +40,8 @@ public class HTMLExtractor implements IExtractor {
|
||||
this.textualExtractor = textualExtractor;
|
||||
|
||||
this.scopeManager =
|
||||
new ScopeManager(textualExtractor.getTrapwriter(), config.getEcmaVersion(), true);
|
||||
new ScopeManager(textualExtractor.getTrapwriter(), config.getEcmaVersion(),
|
||||
ScopeManager.FileKind.TEMPLATE);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -425,7 +426,7 @@ public class HTMLExtractor implements IExtractor {
|
||||
extractSnippet(
|
||||
TopLevelKind.ANGULAR_STYLE_TEMPLATE,
|
||||
config.withSourceType(SourceType.ANGULAR_STYLE_TEMPLATE),
|
||||
new ScopeManager(textualExtractor.getTrapwriter(), ECMAVersion.ECMA2020, true),
|
||||
new ScopeManager(textualExtractor.getTrapwriter(), ECMAVersion.ECMA2020, ScopeManager.FileKind.TEMPLATE),
|
||||
textualExtractor,
|
||||
m.group(bodyGroup),
|
||||
m.start(bodyGroup),
|
||||
|
||||
@@ -15,8 +15,6 @@ import com.semmle.extractor.html.HtmlPopulator;
|
||||
import com.semmle.js.extractor.ExtractorConfig.Platform;
|
||||
import com.semmle.js.extractor.ExtractorConfig.SourceType;
|
||||
import com.semmle.js.extractor.FileExtractor.FileType;
|
||||
import com.semmle.js.extractor.trapcache.DefaultTrapCache;
|
||||
import com.semmle.js.extractor.trapcache.DummyTrapCache;
|
||||
import com.semmle.js.extractor.trapcache.ITrapCache;
|
||||
import com.semmle.js.parser.ParsedProject;
|
||||
import com.semmle.ts.extractor.TypeExtractor;
|
||||
@@ -43,7 +41,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 = "2022-02-22";
|
||||
public static final String EXTRACTOR_VERSION = "2022-08-25";
|
||||
|
||||
public static final Pattern NEWLINE = Pattern.compile("\n");
|
||||
|
||||
@@ -61,8 +59,6 @@ public class Main {
|
||||
private static final String P_PLATFORM = "--platform";
|
||||
private static final String P_QUIET = "--quiet";
|
||||
private static final String P_SOURCE_TYPE = "--source-type";
|
||||
private static final String P_TRAP_CACHE = "--trap-cache";
|
||||
private static final String P_TRAP_CACHE_BOUND = "--trap-cache-bound";
|
||||
private static final String P_TYPESCRIPT = "--typescript";
|
||||
private static final String P_TYPESCRIPT_FULL = "--typescript-full";
|
||||
private static final String P_TYPESCRIPT_RAM = "--typescript-ram";
|
||||
@@ -112,22 +108,7 @@ public class Main {
|
||||
ap.parse();
|
||||
|
||||
extractorConfig = parseJSOptions(ap);
|
||||
ITrapCache trapCache;
|
||||
if (ap.has(P_TRAP_CACHE)) {
|
||||
Long sizeBound = null;
|
||||
if (ap.has(P_TRAP_CACHE_BOUND)) {
|
||||
String tcb = ap.getString(P_TRAP_CACHE_BOUND);
|
||||
sizeBound = DefaultTrapCache.asFileSize(tcb);
|
||||
if (sizeBound == null) ap.error("Invalid TRAP cache size bound: " + tcb);
|
||||
}
|
||||
trapCache = new DefaultTrapCache(ap.getString(P_TRAP_CACHE), sizeBound, EXTRACTOR_VERSION);
|
||||
} else {
|
||||
if (ap.has(P_TRAP_CACHE_BOUND))
|
||||
ap.error(
|
||||
P_TRAP_CACHE_BOUND + " should only be specified together with " + P_TRAP_CACHE + ".");
|
||||
trapCache = new DummyTrapCache();
|
||||
}
|
||||
fileExtractor = new FileExtractor(extractorConfig, extractorOutputConfig, trapCache);
|
||||
fileExtractor = new FileExtractor(extractorConfig, extractorOutputConfig, ITrapCache.fromExtractorOptions());
|
||||
|
||||
setupMatchers(ap);
|
||||
|
||||
@@ -153,7 +134,7 @@ public class Main {
|
||||
ensureFileIsExtracted(file, ap);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
TypeScriptParser tsParser = extractorState.getTypeScriptParser();
|
||||
tsParser.setTypescriptRam(extractorConfig.getTypeScriptRam());
|
||||
if (containsTypeScriptFiles()) {
|
||||
@@ -432,12 +413,6 @@ public class Main {
|
||||
argsParser.addToleratedFlag(P_TOLERATE_PARSE_ERRORS, 0);
|
||||
argsParser.addFlag(
|
||||
P_ABORT_ON_PARSE_ERRORS, 0, "Abort extraction if a parse error is encountered.");
|
||||
argsParser.addFlag(P_TRAP_CACHE, 1, "Use the given directory as the TRAP cache.");
|
||||
argsParser.addFlag(
|
||||
P_TRAP_CACHE_BOUND,
|
||||
1,
|
||||
"A (soft) upper limit on the size of the TRAP cache, "
|
||||
+ "in standard size units (e.g., 'g' for gigabytes).");
|
||||
argsParser.addFlag(P_DEFAULT_ENCODING, 1, "The encoding to use; default is UTF-8.");
|
||||
argsParser.addFlag(P_TYPESCRIPT, 0, "Enable basic TypesScript support.");
|
||||
argsParser.addFlag(
|
||||
@@ -460,7 +435,7 @@ public class Main {
|
||||
if (ap.has(P_TYPESCRIPT)) return TypeScriptMode.BASIC;
|
||||
return TypeScriptMode.NONE;
|
||||
}
|
||||
|
||||
|
||||
private Path inferSourceRoot(ArgsParser ap) {
|
||||
List<File> files = getFilesArg(ap);
|
||||
Path sourceRoot = files.iterator().next().toPath().toAbsolutePath().getParent();
|
||||
|
||||
@@ -97,20 +97,31 @@ public class ScopeManager {
|
||||
}
|
||||
}
|
||||
|
||||
public static enum FileKind {
|
||||
/** Any file not specific to one of the other file kinds. */
|
||||
PLAIN,
|
||||
|
||||
/** A file potentially containing template tags. */
|
||||
TEMPLATE,
|
||||
|
||||
/** A d.ts file, containing TypeScript ambient declarations. */
|
||||
TYPESCRIPT_DECLARATION,
|
||||
}
|
||||
|
||||
private final TrapWriter trapWriter;
|
||||
private Scope curScope;
|
||||
private final Scope toplevelScope;
|
||||
private final ECMAVersion ecmaVersion;
|
||||
private final Set<String> implicitGlobals = new LinkedHashSet<String>();
|
||||
private Scope implicitVariableScope;
|
||||
private final boolean isInTemplateScope;
|
||||
private final FileKind fileKind;
|
||||
|
||||
public ScopeManager(TrapWriter trapWriter, ECMAVersion ecmaVersion, boolean isInTemplateScope) {
|
||||
public ScopeManager(TrapWriter trapWriter, ECMAVersion ecmaVersion, FileKind fileKind) {
|
||||
this.trapWriter = trapWriter;
|
||||
this.toplevelScope = enterScope(ScopeKind.GLOBAL, trapWriter.globalID("global_scope"), null);
|
||||
this.ecmaVersion = ecmaVersion;
|
||||
this.implicitVariableScope = toplevelScope;
|
||||
this.isInTemplateScope = isInTemplateScope;
|
||||
this.implicitVariableScope = toplevelScope;
|
||||
this.fileKind = fileKind;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -118,7 +129,11 @@ public class ScopeManager {
|
||||
* relevant template tags.
|
||||
*/
|
||||
public boolean isInTemplateFile() {
|
||||
return isInTemplateScope;
|
||||
return this.fileKind == FileKind.TEMPLATE;
|
||||
}
|
||||
|
||||
public boolean isInTypeScriptDeclarationFile() {
|
||||
return this.fileKind == FileKind.TYPESCRIPT_DECLARATION;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -221,7 +236,7 @@ public class ScopeManager {
|
||||
|
||||
/**
|
||||
* Get the label for a given variable in the current scope; if it cannot be found, add it to the
|
||||
* implicit variable scope (usually the global scope).
|
||||
* implicit variable scope (usually the global scope).
|
||||
*/
|
||||
public Label getVarKey(String name) {
|
||||
Label lbl = curScope.lookupVariable(name);
|
||||
@@ -411,7 +426,7 @@ public class ScopeManager {
|
||||
// cases where we turn on the 'declKind' flags
|
||||
@Override
|
||||
public Void visit(FunctionDeclaration nd, Void v) {
|
||||
if (nd.hasDeclareKeyword()) return null;
|
||||
if (nd.hasDeclareKeyword() && !isInTypeScriptDeclarationFile()) return null;
|
||||
// strict mode functions are block-scoped, non-strict mode ones aren't
|
||||
if (blockscope == isStrict) visit(nd.getId(), DeclKind.var);
|
||||
return null;
|
||||
@@ -419,7 +434,7 @@ public class ScopeManager {
|
||||
|
||||
@Override
|
||||
public Void visit(ClassDeclaration nd, Void c) {
|
||||
if (nd.hasDeclareKeyword()) return null;
|
||||
if (nd.hasDeclareKeyword() && !isInTypeScriptDeclarationFile()) return null;
|
||||
if (blockscope) visit(nd.getClassDef().getId(), DeclKind.varAndType);
|
||||
return null;
|
||||
}
|
||||
@@ -468,7 +483,7 @@ public class ScopeManager {
|
||||
|
||||
@Override
|
||||
public Void visit(VariableDeclaration nd, Void v) {
|
||||
if (nd.hasDeclareKeyword()) return null;
|
||||
if (nd.hasDeclareKeyword() && !isInTypeScriptDeclarationFile()) return null;
|
||||
// in block scoping mode, only process 'let'; in non-block scoping
|
||||
// mode, only process non-'let'
|
||||
if (blockscope == nd.isBlockScoped(ecmaVersion)) visit(nd.getDeclarations());
|
||||
@@ -503,7 +518,8 @@ public class ScopeManager {
|
||||
@Override
|
||||
public Void visit(NamespaceDeclaration nd, Void c) {
|
||||
if (blockscope) return null;
|
||||
boolean hasVariable = nd.isInstantiated() && !nd.hasDeclareKeyword();
|
||||
boolean isAmbientOutsideDtsFile = nd.hasDeclareKeyword() && !isInTypeScriptDeclarationFile();
|
||||
boolean hasVariable = nd.isInstantiated() && !isAmbientOutsideDtsFile;
|
||||
visit(nd.getName(), hasVariable ? DeclKind.varAndNamespace : DeclKind.namespace);
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -77,7 +77,7 @@ public class ScriptExtractor implements IExtractor {
|
||||
}
|
||||
|
||||
ScopeManager scopeManager =
|
||||
new ScopeManager(textualExtractor.getTrapwriter(), config.getEcmaVersion(), false);
|
||||
new ScopeManager(textualExtractor.getTrapwriter(), config.getEcmaVersion(), ScopeManager.FileKind.PLAIN);
|
||||
Label toplevelLabel = null;
|
||||
LoCInfo loc;
|
||||
try {
|
||||
|
||||
@@ -22,8 +22,10 @@ public class TypeScriptExtractor implements IExtractor {
|
||||
String source = textualExtractor.getSource();
|
||||
File sourceFile = textualExtractor.getExtractedFile();
|
||||
Result res = state.getTypeScriptParser().parse(sourceFile, source, textualExtractor.getMetrics());
|
||||
ScopeManager scopeManager =
|
||||
new ScopeManager(textualExtractor.getTrapwriter(), ECMAVersion.ECMA2017, false);
|
||||
ScopeManager.FileKind fileKind = sourceFile.getName().endsWith(".d.ts")
|
||||
? ScopeManager.FileKind.TYPESCRIPT_DECLARATION
|
||||
: ScopeManager.FileKind.PLAIN;
|
||||
ScopeManager scopeManager = new ScopeManager(textualExtractor.getTrapwriter(), ECMAVersion.ECMA2017, fileKind);
|
||||
try {
|
||||
FileSnippet snippet = state.getSnippets().get(sourceFile.toPath());
|
||||
SourceType sourceType = snippet != null ? snippet.getSourceType() : jsExtractor.establishSourceType(source, false);
|
||||
|
||||
@@ -26,9 +26,15 @@ public class DefaultTrapCache implements ITrapCache {
|
||||
*/
|
||||
private final String extractorVersion;
|
||||
|
||||
public DefaultTrapCache(String trapCache, Long sizeBound, String extractorVersion) {
|
||||
/**
|
||||
* Whether this cache supports write operations.
|
||||
*/
|
||||
private final boolean writeable;
|
||||
|
||||
public DefaultTrapCache(String trapCache, Long sizeBound, String extractorVersion, boolean writeable) {
|
||||
this.trapCache = new File(trapCache);
|
||||
this.extractorVersion = extractorVersion;
|
||||
this.writeable = writeable;
|
||||
try {
|
||||
initCache(sizeBound);
|
||||
} catch (ResourceError | SecurityException e) {
|
||||
@@ -135,6 +141,8 @@ public class DefaultTrapCache implements ITrapCache {
|
||||
digestor.write(type.toString());
|
||||
digestor.write(config);
|
||||
digestor.write(source);
|
||||
return new File(trapCache, digestor.getDigest() + ".trap.gz");
|
||||
File result = new File(trapCache, digestor.getDigest() + ".trap.gz");
|
||||
if (!writeable && !result.exists()) return null; // If the cache isn't writable, only return the file if it exists
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
package com.semmle.js.extractor.trapcache;
|
||||
|
||||
import static com.semmle.js.extractor.ExtractorOptionsUtil.readExtractorOption;
|
||||
|
||||
import com.semmle.js.extractor.ExtractorConfig;
|
||||
import com.semmle.js.extractor.FileExtractor;
|
||||
import com.semmle.js.extractor.Main;
|
||||
import com.semmle.util.exception.UserError;
|
||||
import java.io.File;
|
||||
|
||||
/** Generic TRAP cache interface. */
|
||||
@@ -18,4 +22,29 @@ public interface ITrapCache {
|
||||
* cached information), or does not yet exist (and should be populated by the extractor)
|
||||
*/
|
||||
public File lookup(String source, ExtractorConfig config, FileExtractor.FileType type);
|
||||
|
||||
/**
|
||||
* Build a TRAP cache as defined by the extractor options, which are read from the corresponding
|
||||
* environment variables as defined in
|
||||
* https://github.com/github/codeql-core/blob/main/design/spec/codeql-extractors.md
|
||||
*
|
||||
* @return a TRAP cache
|
||||
*/
|
||||
public static ITrapCache fromExtractorOptions() {
|
||||
String trapCachePath = readExtractorOption("trap", "cache", "dir");
|
||||
if (trapCachePath != null) {
|
||||
Long sizeBound = null;
|
||||
String trapCacheBound = readExtractorOption("trap", "cache", "bound");
|
||||
if (trapCacheBound != null) {
|
||||
sizeBound = DefaultTrapCache.asFileSize(trapCacheBound);
|
||||
if (sizeBound == null)
|
||||
throw new UserError("Invalid TRAP cache size bound: " + trapCacheBound);
|
||||
}
|
||||
boolean writeable = true;
|
||||
String trapCacheWrite = readExtractorOption("trap", "cache", "write");
|
||||
if (trapCacheWrite != null) writeable = trapCacheWrite.equalsIgnoreCase("TRUE");
|
||||
return new DefaultTrapCache(trapCachePath, sizeBound, Main.EXTRACTOR_VERSION, writeable);
|
||||
}
|
||||
return new DummyTrapCache();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,9 +7,10 @@ import com.semmle.js.ast.Visitor;
|
||||
import java.util.List;
|
||||
|
||||
/** A statement of form <code>declare module "X" {...}</code>. */
|
||||
public class ExternalModuleDeclaration extends Statement {
|
||||
public class ExternalModuleDeclaration extends Statement implements INodeWithSymbol {
|
||||
private final Literal name;
|
||||
private final List<Statement> body;
|
||||
private int symbol = -1;
|
||||
|
||||
public ExternalModuleDeclaration(SourceLocation loc, Literal name, List<Statement> body) {
|
||||
super("ExternalModuleDeclaration", loc);
|
||||
@@ -29,4 +30,14 @@ public class ExternalModuleDeclaration extends Statement {
|
||||
public List<Statement> getBody() {
|
||||
return body;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSymbol() {
|
||||
return this.symbol;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSymbol(int symbol) {
|
||||
this.symbol = symbol;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -591,7 +591,7 @@ public class TypeScriptASTConverter {
|
||||
return convertTryStatement(node, loc);
|
||||
case "TupleType":
|
||||
return convertTupleType(node, loc);
|
||||
case "NamedTupleMember":
|
||||
case "NamedTupleMember":
|
||||
return convertNamedTupleMember(node, loc);
|
||||
case "TypeAliasDeclaration":
|
||||
return convertTypeAliasDeclaration(node, loc);
|
||||
@@ -976,8 +976,9 @@ public class TypeScriptASTConverter {
|
||||
hasDeclareKeyword,
|
||||
hasAbstractKeyword);
|
||||
attachSymbolInformation(classDecl.getClassDef(), node);
|
||||
if (node.has("decorators")) {
|
||||
classDecl.addDecorators(convertChildren(node, "decorators"));
|
||||
List<Decorator> decorators = getDecorators(node);
|
||||
if (!decorators.isEmpty()) {
|
||||
classDecl.addDecorators(decorators);
|
||||
advanceUntilAfter(loc, classDecl.getDecorators());
|
||||
}
|
||||
Node exportedDecl = fixExports(loc, classDecl);
|
||||
@@ -989,6 +990,17 @@ public class TypeScriptASTConverter {
|
||||
return exportedDecl;
|
||||
}
|
||||
|
||||
List<Decorator> getDecorators(JsonObject node) throws ParseError {
|
||||
List<Decorator> result = new ArrayList<>();
|
||||
for (JsonElement elt : getChildIterable(node, "modifiers")) {
|
||||
JsonObject modifier = elt.getAsJsonObject();
|
||||
if (hasKind(modifier, "Decorator")) {
|
||||
result.add((Decorator) convertNode(modifier));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private Node convertCommaListExpression(JsonObject node, SourceLocation loc) throws ParseError {
|
||||
return new SequenceExpression(loc, convertChildren(node, "elements"));
|
||||
}
|
||||
@@ -1041,7 +1053,7 @@ public class TypeScriptASTConverter {
|
||||
private List<DecoratorList> convertParameterDecorators(JsonObject function) throws ParseError {
|
||||
List<DecoratorList> decoratorLists = new ArrayList<>();
|
||||
for (JsonElement parameter : getProperParameters(function)) {
|
||||
decoratorLists.add(makeDecoratorList(parameter.getAsJsonObject().get("decorators")));
|
||||
decoratorLists.add(makeDecoratorList(parameter.getAsJsonObject().get("modifiers")));
|
||||
}
|
||||
return decoratorLists;
|
||||
}
|
||||
@@ -1139,7 +1151,7 @@ public class TypeScriptASTConverter {
|
||||
loc,
|
||||
hasModifier(node, "ConstKeyword"),
|
||||
hasModifier(node, "DeclareKeyword"),
|
||||
convertChildrenNotNull(node, "decorators"),
|
||||
convertChildrenNotNull(node, "illegalDecorators"), // as of https://github.com/microsoft/TypeScript/pull/50343/ the property is called `illegalDecorators` instead of `decorators`
|
||||
convertChild(node, "name"),
|
||||
convertChildren(node, "members"));
|
||||
attachSymbolInformation(enumDeclaration, node);
|
||||
@@ -1664,8 +1676,9 @@ public class TypeScriptASTConverter {
|
||||
FunctionExpression method = convertImplicitFunction(node, loc);
|
||||
MethodDefinition methodDefinition =
|
||||
new MethodDefinition(loc, flags, methodKind, convertChild(node, "name"), method);
|
||||
if (node.has("decorators")) {
|
||||
methodDefinition.addDecorators(convertChildren(node, "decorators"));
|
||||
List<Decorator> decorators = getDecorators(node);
|
||||
if (!decorators.isEmpty()) {
|
||||
methodDefinition.addDecorators(decorators);
|
||||
advanceUntilAfter(loc, methodDefinition.getDecorators());
|
||||
}
|
||||
return methodDefinition;
|
||||
@@ -1710,7 +1723,9 @@ public class TypeScriptASTConverter {
|
||||
}
|
||||
if (nameNode instanceof Literal) {
|
||||
// Declaration of form: declare module "X" {...}
|
||||
return new ExternalModuleDeclaration(loc, (Literal) nameNode, body);
|
||||
ExternalModuleDeclaration decl = new ExternalModuleDeclaration(loc, (Literal) nameNode, body);
|
||||
attachSymbolInformation(decl, node);
|
||||
return decl;
|
||||
}
|
||||
if (hasFlag(node, "GlobalAugmentation")) {
|
||||
// Declaration of form: declare global {...}
|
||||
@@ -2077,8 +2092,9 @@ public class TypeScriptASTConverter {
|
||||
convertChild(node, "name"),
|
||||
convertChild(node, "initializer"),
|
||||
convertChildAsType(node, "type"));
|
||||
if (node.has("decorators")) {
|
||||
fieldDefinition.addDecorators(convertChildren(node, "decorators"));
|
||||
List<Decorator> decorators = getDecorators(node);
|
||||
if (!decorators.isEmpty()) {
|
||||
fieldDefinition.addDecorators(decorators);
|
||||
advanceUntilAfter(loc, fieldDefinition.getDecorators());
|
||||
}
|
||||
return fieldDefinition;
|
||||
|
||||
9
javascript/extractor/tests/esnext/input/for-await.js
Normal file
9
javascript/extractor/tests/esnext/input/for-await.js
Normal file
@@ -0,0 +1,9 @@
|
||||
async function foo() {
|
||||
for await (const call of calls) {
|
||||
call();
|
||||
}
|
||||
}
|
||||
|
||||
for await (const call of calls) {
|
||||
call();
|
||||
}
|
||||
450
javascript/extractor/tests/esnext/output/trap/for-await.js.trap
Normal file
450
javascript/extractor/tests/esnext/output/trap/for-await.js.trap
Normal file
@@ -0,0 +1,450 @@
|
||||
#10000=@"/for-await.js;sourcefile"
|
||||
files(#10000,"/for-await.js")
|
||||
#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,"async function foo() {","
|
||||
")
|
||||
#20003=@"loc,{#10000},1,1,1,22"
|
||||
locations_default(#20003,#10000,1,1,1,22)
|
||||
hasLocation(#20002,#20003)
|
||||
#20004=*
|
||||
lines(#20004,#20001," for await (const call of calls) {","
|
||||
")
|
||||
#20005=@"loc,{#10000},2,1,2,37"
|
||||
locations_default(#20005,#10000,2,1,2,37)
|
||||
hasLocation(#20004,#20005)
|
||||
indentation(#10000,2," ",4)
|
||||
#20006=*
|
||||
lines(#20006,#20001," call(); ","
|
||||
")
|
||||
#20007=@"loc,{#10000},3,1,3,19"
|
||||
locations_default(#20007,#10000,3,1,3,19)
|
||||
hasLocation(#20006,#20007)
|
||||
indentation(#10000,3," ",8)
|
||||
#20008=*
|
||||
lines(#20008,#20001," }","
|
||||
")
|
||||
#20009=@"loc,{#10000},4,1,4,5"
|
||||
locations_default(#20009,#10000,4,1,4,5)
|
||||
hasLocation(#20008,#20009)
|
||||
indentation(#10000,4," ",4)
|
||||
#20010=*
|
||||
lines(#20010,#20001,"}","
|
||||
")
|
||||
#20011=@"loc,{#10000},5,1,5,1"
|
||||
locations_default(#20011,#10000,5,1,5,1)
|
||||
hasLocation(#20010,#20011)
|
||||
#20012=*
|
||||
lines(#20012,#20001,"","
|
||||
")
|
||||
#20013=@"loc,{#10000},6,1,6,0"
|
||||
locations_default(#20013,#10000,6,1,6,0)
|
||||
hasLocation(#20012,#20013)
|
||||
#20014=*
|
||||
lines(#20014,#20001,"for await (const call of calls) {","
|
||||
")
|
||||
#20015=@"loc,{#10000},7,1,7,33"
|
||||
locations_default(#20015,#10000,7,1,7,33)
|
||||
hasLocation(#20014,#20015)
|
||||
#20016=*
|
||||
lines(#20016,#20001," call();","
|
||||
")
|
||||
#20017=@"loc,{#10000},8,1,8,11"
|
||||
locations_default(#20017,#10000,8,1,8,11)
|
||||
hasLocation(#20016,#20017)
|
||||
indentation(#10000,8," ",4)
|
||||
#20018=*
|
||||
lines(#20018,#20001,"}","")
|
||||
#20019=@"loc,{#10000},9,1,9,1"
|
||||
locations_default(#20019,#10000,9,1,9,1)
|
||||
hasLocation(#20018,#20019)
|
||||
numlines(#20001,9,8,0)
|
||||
#20020=*
|
||||
tokeninfo(#20020,6,#20001,0,"async")
|
||||
#20021=@"loc,{#10000},1,1,1,5"
|
||||
locations_default(#20021,#10000,1,1,1,5)
|
||||
hasLocation(#20020,#20021)
|
||||
#20022=*
|
||||
tokeninfo(#20022,7,#20001,1,"function")
|
||||
#20023=@"loc,{#10000},1,7,1,14"
|
||||
locations_default(#20023,#10000,1,7,1,14)
|
||||
hasLocation(#20022,#20023)
|
||||
#20024=*
|
||||
tokeninfo(#20024,6,#20001,2,"foo")
|
||||
#20025=@"loc,{#10000},1,16,1,18"
|
||||
locations_default(#20025,#10000,1,16,1,18)
|
||||
hasLocation(#20024,#20025)
|
||||
#20026=*
|
||||
tokeninfo(#20026,8,#20001,3,"(")
|
||||
#20027=@"loc,{#10000},1,19,1,19"
|
||||
locations_default(#20027,#10000,1,19,1,19)
|
||||
hasLocation(#20026,#20027)
|
||||
#20028=*
|
||||
tokeninfo(#20028,8,#20001,4,")")
|
||||
#20029=@"loc,{#10000},1,20,1,20"
|
||||
locations_default(#20029,#10000,1,20,1,20)
|
||||
hasLocation(#20028,#20029)
|
||||
#20030=*
|
||||
tokeninfo(#20030,8,#20001,5,"{")
|
||||
#20031=@"loc,{#10000},1,22,1,22"
|
||||
locations_default(#20031,#10000,1,22,1,22)
|
||||
hasLocation(#20030,#20031)
|
||||
#20032=*
|
||||
tokeninfo(#20032,7,#20001,6,"for")
|
||||
#20033=@"loc,{#10000},2,5,2,7"
|
||||
locations_default(#20033,#10000,2,5,2,7)
|
||||
hasLocation(#20032,#20033)
|
||||
#20034=*
|
||||
tokeninfo(#20034,6,#20001,7,"await")
|
||||
#20035=@"loc,{#10000},2,9,2,13"
|
||||
locations_default(#20035,#10000,2,9,2,13)
|
||||
hasLocation(#20034,#20035)
|
||||
#20036=*
|
||||
tokeninfo(#20036,8,#20001,8,"(")
|
||||
#20037=@"loc,{#10000},2,15,2,15"
|
||||
locations_default(#20037,#10000,2,15,2,15)
|
||||
hasLocation(#20036,#20037)
|
||||
#20038=*
|
||||
tokeninfo(#20038,7,#20001,9,"const")
|
||||
#20039=@"loc,{#10000},2,16,2,20"
|
||||
locations_default(#20039,#10000,2,16,2,20)
|
||||
hasLocation(#20038,#20039)
|
||||
#20040=*
|
||||
tokeninfo(#20040,6,#20001,10,"call")
|
||||
#20041=@"loc,{#10000},2,22,2,25"
|
||||
locations_default(#20041,#10000,2,22,2,25)
|
||||
hasLocation(#20040,#20041)
|
||||
#20042=*
|
||||
tokeninfo(#20042,6,#20001,11,"of")
|
||||
#20043=@"loc,{#10000},2,27,2,28"
|
||||
locations_default(#20043,#10000,2,27,2,28)
|
||||
hasLocation(#20042,#20043)
|
||||
#20044=*
|
||||
tokeninfo(#20044,6,#20001,12,"calls")
|
||||
#20045=@"loc,{#10000},2,30,2,34"
|
||||
locations_default(#20045,#10000,2,30,2,34)
|
||||
hasLocation(#20044,#20045)
|
||||
#20046=*
|
||||
tokeninfo(#20046,8,#20001,13,")")
|
||||
#20047=@"loc,{#10000},2,35,2,35"
|
||||
locations_default(#20047,#10000,2,35,2,35)
|
||||
hasLocation(#20046,#20047)
|
||||
#20048=*
|
||||
tokeninfo(#20048,8,#20001,14,"{")
|
||||
#20049=@"loc,{#10000},2,37,2,37"
|
||||
locations_default(#20049,#10000,2,37,2,37)
|
||||
hasLocation(#20048,#20049)
|
||||
#20050=*
|
||||
tokeninfo(#20050,6,#20001,15,"call")
|
||||
#20051=@"loc,{#10000},3,9,3,12"
|
||||
locations_default(#20051,#10000,3,9,3,12)
|
||||
hasLocation(#20050,#20051)
|
||||
#20052=*
|
||||
tokeninfo(#20052,8,#20001,16,"(")
|
||||
#20053=@"loc,{#10000},3,13,3,13"
|
||||
locations_default(#20053,#10000,3,13,3,13)
|
||||
hasLocation(#20052,#20053)
|
||||
#20054=*
|
||||
tokeninfo(#20054,8,#20001,17,")")
|
||||
#20055=@"loc,{#10000},3,14,3,14"
|
||||
locations_default(#20055,#10000,3,14,3,14)
|
||||
hasLocation(#20054,#20055)
|
||||
#20056=*
|
||||
tokeninfo(#20056,8,#20001,18,";")
|
||||
#20057=@"loc,{#10000},3,15,3,15"
|
||||
locations_default(#20057,#10000,3,15,3,15)
|
||||
hasLocation(#20056,#20057)
|
||||
#20058=*
|
||||
tokeninfo(#20058,8,#20001,19,"}")
|
||||
#20059=@"loc,{#10000},4,5,4,5"
|
||||
locations_default(#20059,#10000,4,5,4,5)
|
||||
hasLocation(#20058,#20059)
|
||||
#20060=*
|
||||
tokeninfo(#20060,8,#20001,20,"}")
|
||||
hasLocation(#20060,#20011)
|
||||
#20061=*
|
||||
tokeninfo(#20061,7,#20001,21,"for")
|
||||
#20062=@"loc,{#10000},7,1,7,3"
|
||||
locations_default(#20062,#10000,7,1,7,3)
|
||||
hasLocation(#20061,#20062)
|
||||
#20063=*
|
||||
tokeninfo(#20063,6,#20001,22,"await")
|
||||
#20064=@"loc,{#10000},7,5,7,9"
|
||||
locations_default(#20064,#10000,7,5,7,9)
|
||||
hasLocation(#20063,#20064)
|
||||
#20065=*
|
||||
tokeninfo(#20065,8,#20001,23,"(")
|
||||
#20066=@"loc,{#10000},7,11,7,11"
|
||||
locations_default(#20066,#10000,7,11,7,11)
|
||||
hasLocation(#20065,#20066)
|
||||
#20067=*
|
||||
tokeninfo(#20067,7,#20001,24,"const")
|
||||
#20068=@"loc,{#10000},7,12,7,16"
|
||||
locations_default(#20068,#10000,7,12,7,16)
|
||||
hasLocation(#20067,#20068)
|
||||
#20069=*
|
||||
tokeninfo(#20069,6,#20001,25,"call")
|
||||
#20070=@"loc,{#10000},7,18,7,21"
|
||||
locations_default(#20070,#10000,7,18,7,21)
|
||||
hasLocation(#20069,#20070)
|
||||
#20071=*
|
||||
tokeninfo(#20071,6,#20001,26,"of")
|
||||
#20072=@"loc,{#10000},7,23,7,24"
|
||||
locations_default(#20072,#10000,7,23,7,24)
|
||||
hasLocation(#20071,#20072)
|
||||
#20073=*
|
||||
tokeninfo(#20073,6,#20001,27,"calls")
|
||||
#20074=@"loc,{#10000},7,26,7,30"
|
||||
locations_default(#20074,#10000,7,26,7,30)
|
||||
hasLocation(#20073,#20074)
|
||||
#20075=*
|
||||
tokeninfo(#20075,8,#20001,28,")")
|
||||
#20076=@"loc,{#10000},7,31,7,31"
|
||||
locations_default(#20076,#10000,7,31,7,31)
|
||||
hasLocation(#20075,#20076)
|
||||
#20077=*
|
||||
tokeninfo(#20077,8,#20001,29,"{")
|
||||
#20078=@"loc,{#10000},7,33,7,33"
|
||||
locations_default(#20078,#10000,7,33,7,33)
|
||||
hasLocation(#20077,#20078)
|
||||
#20079=*
|
||||
tokeninfo(#20079,6,#20001,30,"call")
|
||||
#20080=@"loc,{#10000},8,5,8,8"
|
||||
locations_default(#20080,#10000,8,5,8,8)
|
||||
hasLocation(#20079,#20080)
|
||||
#20081=*
|
||||
tokeninfo(#20081,8,#20001,31,"(")
|
||||
#20082=@"loc,{#10000},8,9,8,9"
|
||||
locations_default(#20082,#10000,8,9,8,9)
|
||||
hasLocation(#20081,#20082)
|
||||
#20083=*
|
||||
tokeninfo(#20083,8,#20001,32,")")
|
||||
#20084=@"loc,{#10000},8,10,8,10"
|
||||
locations_default(#20084,#10000,8,10,8,10)
|
||||
hasLocation(#20083,#20084)
|
||||
#20085=*
|
||||
tokeninfo(#20085,8,#20001,33,";")
|
||||
#20086=@"loc,{#10000},8,11,8,11"
|
||||
locations_default(#20086,#10000,8,11,8,11)
|
||||
hasLocation(#20085,#20086)
|
||||
#20087=*
|
||||
tokeninfo(#20087,8,#20001,34,"}")
|
||||
hasLocation(#20087,#20019)
|
||||
#20088=*
|
||||
tokeninfo(#20088,0,#20001,35,"")
|
||||
#20089=@"loc,{#10000},9,2,9,1"
|
||||
locations_default(#20089,#10000,9,2,9,1)
|
||||
hasLocation(#20088,#20089)
|
||||
toplevels(#20001,0)
|
||||
#20090=@"loc,{#10000},1,1,9,1"
|
||||
locations_default(#20090,#10000,1,1,9,1)
|
||||
hasLocation(#20001,#20090)
|
||||
#20091=@"var;{foo};{#20000}"
|
||||
variables(#20091,"foo",#20000)
|
||||
#20092=*
|
||||
stmts(#20092,17,#20001,0,"async f ... }\n}")
|
||||
#20093=@"loc,{#10000},1,1,5,1"
|
||||
locations_default(#20093,#10000,1,1,5,1)
|
||||
hasLocation(#20092,#20093)
|
||||
stmt_containers(#20092,#20001)
|
||||
#20094=*
|
||||
exprs(#20094,78,#20092,-1,"foo")
|
||||
hasLocation(#20094,#20025)
|
||||
expr_containers(#20094,#20092)
|
||||
literals("foo","foo",#20094)
|
||||
decl(#20094,#20091)
|
||||
#20095=*
|
||||
scopes(#20095,1)
|
||||
scopenodes(#20092,#20095)
|
||||
scopenesting(#20095,#20000)
|
||||
#20096=@"var;{arguments};{#20095}"
|
||||
variables(#20096,"arguments",#20095)
|
||||
is_arguments_object(#20096)
|
||||
is_async(#20092)
|
||||
#20097=*
|
||||
stmts(#20097,1,#20092,-2,"{\n f ... }\n}")
|
||||
#20098=@"loc,{#10000},1,22,5,1"
|
||||
locations_default(#20098,#10000,1,22,5,1)
|
||||
hasLocation(#20097,#20098)
|
||||
stmt_containers(#20097,#20092)
|
||||
#20099=*
|
||||
stmts(#20099,21,#20097,0,"for awa ... \n }")
|
||||
#20100=@"loc,{#10000},2,5,4,5"
|
||||
locations_default(#20100,#10000,2,5,4,5)
|
||||
hasLocation(#20099,#20100)
|
||||
stmt_containers(#20099,#20092)
|
||||
#20101=*
|
||||
exprs(#20101,79,#20099,1,"calls")
|
||||
hasLocation(#20101,#20045)
|
||||
enclosing_stmt(#20101,#20099)
|
||||
expr_containers(#20101,#20092)
|
||||
literals("calls","calls",#20101)
|
||||
#20102=@"var;{calls};{#20000}"
|
||||
variables(#20102,"calls",#20000)
|
||||
bind(#20101,#20102)
|
||||
#20103=*
|
||||
scopes(#20103,6)
|
||||
scopenodes(#20099,#20103)
|
||||
scopenesting(#20103,#20095)
|
||||
#20104=@"var;{call};{#20103}"
|
||||
variables(#20104,"call",#20103)
|
||||
#20105=*
|
||||
stmts(#20105,22,#20099,0,"const call")
|
||||
#20106=@"loc,{#10000},2,16,2,25"
|
||||
locations_default(#20106,#10000,2,16,2,25)
|
||||
hasLocation(#20105,#20106)
|
||||
stmt_containers(#20105,#20092)
|
||||
#20107=*
|
||||
exprs(#20107,64,#20105,0,"call")
|
||||
hasLocation(#20107,#20041)
|
||||
enclosing_stmt(#20107,#20105)
|
||||
expr_containers(#20107,#20092)
|
||||
#20108=*
|
||||
exprs(#20108,78,#20107,0,"call")
|
||||
hasLocation(#20108,#20041)
|
||||
enclosing_stmt(#20108,#20105)
|
||||
expr_containers(#20108,#20092)
|
||||
literals("call","call",#20108)
|
||||
decl(#20108,#20104)
|
||||
#20109=*
|
||||
stmts(#20109,1,#20099,2,"{\n ... \n }")
|
||||
#20110=@"loc,{#10000},2,37,4,5"
|
||||
locations_default(#20110,#10000,2,37,4,5)
|
||||
hasLocation(#20109,#20110)
|
||||
stmt_containers(#20109,#20092)
|
||||
#20111=*
|
||||
stmts(#20111,2,#20109,0,"call();")
|
||||
#20112=@"loc,{#10000},3,9,3,15"
|
||||
locations_default(#20112,#10000,3,9,3,15)
|
||||
hasLocation(#20111,#20112)
|
||||
stmt_containers(#20111,#20092)
|
||||
#20113=*
|
||||
exprs(#20113,13,#20111,0,"call()")
|
||||
#20114=@"loc,{#10000},3,9,3,14"
|
||||
locations_default(#20114,#10000,3,9,3,14)
|
||||
hasLocation(#20113,#20114)
|
||||
enclosing_stmt(#20113,#20111)
|
||||
expr_containers(#20113,#20092)
|
||||
#20115=*
|
||||
exprs(#20115,79,#20113,-1,"call")
|
||||
hasLocation(#20115,#20051)
|
||||
enclosing_stmt(#20115,#20111)
|
||||
expr_containers(#20115,#20092)
|
||||
literals("call","call",#20115)
|
||||
bind(#20115,#20104)
|
||||
is_for_await_of(#20099)
|
||||
#20116=*
|
||||
stmts(#20116,21,#20001,1,"for awa ... ll();\n}")
|
||||
#20117=@"loc,{#10000},7,1,9,1"
|
||||
locations_default(#20117,#10000,7,1,9,1)
|
||||
hasLocation(#20116,#20117)
|
||||
stmt_containers(#20116,#20001)
|
||||
#20118=*
|
||||
exprs(#20118,79,#20116,1,"calls")
|
||||
hasLocation(#20118,#20074)
|
||||
enclosing_stmt(#20118,#20116)
|
||||
expr_containers(#20118,#20001)
|
||||
literals("calls","calls",#20118)
|
||||
bind(#20118,#20102)
|
||||
#20119=*
|
||||
scopes(#20119,6)
|
||||
scopenodes(#20116,#20119)
|
||||
scopenesting(#20119,#20000)
|
||||
#20120=@"var;{call};{#20119}"
|
||||
variables(#20120,"call",#20119)
|
||||
#20121=*
|
||||
stmts(#20121,22,#20116,0,"const call")
|
||||
#20122=@"loc,{#10000},7,12,7,21"
|
||||
locations_default(#20122,#10000,7,12,7,21)
|
||||
hasLocation(#20121,#20122)
|
||||
stmt_containers(#20121,#20001)
|
||||
#20123=*
|
||||
exprs(#20123,64,#20121,0,"call")
|
||||
hasLocation(#20123,#20070)
|
||||
enclosing_stmt(#20123,#20121)
|
||||
expr_containers(#20123,#20001)
|
||||
#20124=*
|
||||
exprs(#20124,78,#20123,0,"call")
|
||||
hasLocation(#20124,#20070)
|
||||
enclosing_stmt(#20124,#20121)
|
||||
expr_containers(#20124,#20001)
|
||||
literals("call","call",#20124)
|
||||
decl(#20124,#20120)
|
||||
#20125=*
|
||||
stmts(#20125,1,#20116,2,"{\n call();\n}")
|
||||
#20126=@"loc,{#10000},7,33,9,1"
|
||||
locations_default(#20126,#10000,7,33,9,1)
|
||||
hasLocation(#20125,#20126)
|
||||
stmt_containers(#20125,#20001)
|
||||
#20127=*
|
||||
stmts(#20127,2,#20125,0,"call();")
|
||||
#20128=@"loc,{#10000},8,5,8,11"
|
||||
locations_default(#20128,#10000,8,5,8,11)
|
||||
hasLocation(#20127,#20128)
|
||||
stmt_containers(#20127,#20001)
|
||||
#20129=*
|
||||
exprs(#20129,13,#20127,0,"call()")
|
||||
#20130=@"loc,{#10000},8,5,8,10"
|
||||
locations_default(#20130,#10000,8,5,8,10)
|
||||
hasLocation(#20129,#20130)
|
||||
enclosing_stmt(#20129,#20127)
|
||||
expr_containers(#20129,#20001)
|
||||
#20131=*
|
||||
exprs(#20131,79,#20129,-1,"call")
|
||||
hasLocation(#20131,#20080)
|
||||
enclosing_stmt(#20131,#20127)
|
||||
expr_containers(#20131,#20001)
|
||||
literals("call","call",#20131)
|
||||
bind(#20131,#20120)
|
||||
is_for_await_of(#20116)
|
||||
#20132=*
|
||||
entry_cfg_node(#20132,#20001)
|
||||
#20133=@"loc,{#10000},1,1,1,0"
|
||||
locations_default(#20133,#10000,1,1,1,0)
|
||||
hasLocation(#20132,#20133)
|
||||
#20134=*
|
||||
exit_cfg_node(#20134,#20001)
|
||||
hasLocation(#20134,#20089)
|
||||
successor(#20118,#20116)
|
||||
successor(#20116,#20121)
|
||||
successor(#20116,#20134)
|
||||
successor(#20125,#20127)
|
||||
successor(#20127,#20131)
|
||||
successor(#20131,#20129)
|
||||
successor(#20129,#20116)
|
||||
successor(#20121,#20124)
|
||||
successor(#20124,#20123)
|
||||
successor(#20123,#20125)
|
||||
successor(#20092,#20118)
|
||||
#20135=*
|
||||
entry_cfg_node(#20135,#20092)
|
||||
hasLocation(#20135,#20133)
|
||||
#20136=*
|
||||
exit_cfg_node(#20136,#20092)
|
||||
#20137=@"loc,{#10000},5,2,5,1"
|
||||
locations_default(#20137,#10000,5,2,5,1)
|
||||
hasLocation(#20136,#20137)
|
||||
successor(#20097,#20101)
|
||||
successor(#20101,#20099)
|
||||
successor(#20099,#20105)
|
||||
successor(#20099,#20136)
|
||||
successor(#20109,#20111)
|
||||
successor(#20111,#20115)
|
||||
successor(#20115,#20113)
|
||||
successor(#20113,#20099)
|
||||
successor(#20105,#20108)
|
||||
successor(#20108,#20107)
|
||||
successor(#20107,#20109)
|
||||
successor(#20135,#20097)
|
||||
successor(#20094,#20092)
|
||||
successor(#20132,#20094)
|
||||
numlines(#10000,9,8,0)
|
||||
filetype(#10000,"javascript")
|
||||
@@ -97,28 +97,28 @@ scopenodes(#20001,#20033)
|
||||
scopenesting(#20033,#20000)
|
||||
is_module(#20001)
|
||||
is_es2015_module(#20001)
|
||||
#20034=*
|
||||
stmts(#20034,30,#20001,0,"export ... foo();")
|
||||
hasLocation(#20034,#20003)
|
||||
stmt_containers(#20034,#20001)
|
||||
#20034=@"var;{foo};{#20033}"
|
||||
variables(#20034,"foo",#20033)
|
||||
#20035=*
|
||||
stmts(#20035,17,#20034,-1,"declare ... foo();")
|
||||
#20036=@"loc,{#10000},1,8,1,30"
|
||||
locations_default(#20036,#10000,1,8,1,30)
|
||||
hasLocation(#20035,#20036)
|
||||
stmts(#20035,30,#20001,0,"export ... foo();")
|
||||
hasLocation(#20035,#20003)
|
||||
stmt_containers(#20035,#20001)
|
||||
has_declare_keyword(#20035)
|
||||
#20037=*
|
||||
exprs(#20037,78,#20035,-1,"foo")
|
||||
hasLocation(#20037,#20013)
|
||||
expr_containers(#20037,#20035)
|
||||
literals("foo","foo",#20037)
|
||||
#20038=@"var;{foo};{#20000}"
|
||||
variables(#20038,"foo",#20000)
|
||||
decl(#20037,#20038)
|
||||
#20036=*
|
||||
stmts(#20036,17,#20035,-1,"declare ... foo();")
|
||||
#20037=@"loc,{#10000},1,8,1,30"
|
||||
locations_default(#20037,#10000,1,8,1,30)
|
||||
hasLocation(#20036,#20037)
|
||||
stmt_containers(#20036,#20001)
|
||||
has_declare_keyword(#20036)
|
||||
#20038=*
|
||||
exprs(#20038,78,#20036,-1,"foo")
|
||||
hasLocation(#20038,#20013)
|
||||
expr_containers(#20038,#20036)
|
||||
literals("foo","foo",#20038)
|
||||
decl(#20038,#20034)
|
||||
#20039=*
|
||||
scopes(#20039,1)
|
||||
scopenodes(#20035,#20039)
|
||||
scopenodes(#20036,#20039)
|
||||
scopenesting(#20039,#20033)
|
||||
#20040=@"var;{arguments};{#20039}"
|
||||
variables(#20040,"arguments",#20039)
|
||||
@@ -142,8 +142,8 @@ hasLocation(#20043,#20044)
|
||||
exit_cfg_node(#20045,#20001)
|
||||
hasLocation(#20045,#20031)
|
||||
successor(#20041,#20045)
|
||||
successor(#20034,#20035)
|
||||
successor(#20035,#20041)
|
||||
successor(#20043,#20034)
|
||||
successor(#20035,#20036)
|
||||
successor(#20036,#20041)
|
||||
successor(#20043,#20035)
|
||||
numlines(#10000,2,2,0)
|
||||
filetype(#10000,"typescript")
|
||||
|
||||
@@ -305,11 +305,12 @@ hasLocation(#20096,#20097)
|
||||
enclosing_stmt(#20096,#20092)
|
||||
expr_containers(#20096,#20001)
|
||||
#20098=*
|
||||
exprs(#20098,79,#20096,0,"M")
|
||||
exprs(#20098,103,#20096,0,"M")
|
||||
hasLocation(#20098,#20052)
|
||||
enclosing_stmt(#20098,#20092)
|
||||
expr_containers(#20098,#20001)
|
||||
literals("M","M",#20098)
|
||||
namespacebind(#20098,#20069)
|
||||
bind(#20098,#20066)
|
||||
#20099=*
|
||||
exprs(#20099,0,#20096,1,"N")
|
||||
|
||||
@@ -14,7 +14,7 @@ import DataFlow::PathGraph
|
||||
/**
|
||||
* Gets the name of an unescaped placeholder in a lodash template.
|
||||
*
|
||||
* For example, the string `<h1><%= title %></h1>` contains the placeholder `title`.
|
||||
* For example, the string `"<h1><%= title %></h1>"` contains the placeholder "title".
|
||||
*/
|
||||
bindingset[s]
|
||||
string getAPlaceholderInString(string s) {
|
||||
|
||||
@@ -175,7 +175,7 @@ predicate isOtherModeledArgument(DataFlow::Node n, FilteringReason reason) {
|
||||
or
|
||||
n instanceof CryptographicKey and reason instanceof CryptographicKeyReason
|
||||
or
|
||||
any(CryptographicOperation op).getInput().flow() = n and
|
||||
any(CryptographicOperation op).getInput() = n and
|
||||
reason instanceof CryptographicOperationFlowReason
|
||||
or
|
||||
exists(DataFlow::CallNode call | n = call.getAnArgument() |
|
||||
|
||||
@@ -144,9 +144,9 @@ private module AccessPaths {
|
||||
not param = base.getReceiver()
|
||||
|
|
||||
result = param and
|
||||
name = param.getAnImmediateUse().asExpr().(Parameter).getName()
|
||||
name = param.asSource().(DataFlow::ParameterNode).getName()
|
||||
or
|
||||
param.getAnImmediateUse().asExpr() instanceof DestructuringPattern and
|
||||
param.asSource().asExpr() instanceof DestructuringPattern and
|
||||
result = param.getMember(name)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -42,10 +42,10 @@ module SinkEndpointFilter {
|
||||
result = "modeled database access"
|
||||
or
|
||||
// Remove calls to APIs that aren't relevant to NoSQL injection
|
||||
call.getReceiver().asExpr() instanceof HTTP::RequestExpr and
|
||||
call.getReceiver() instanceof HTTP::RequestNode and
|
||||
result = "receiver is a HTTP request expression"
|
||||
or
|
||||
call.getReceiver().asExpr() instanceof HTTP::ResponseExpr and
|
||||
call.getReceiver() instanceof HTTP::ResponseNode and
|
||||
result = "receiver is a HTTP response expression"
|
||||
)
|
||||
or
|
||||
@@ -115,7 +115,7 @@ predicate isBaseAdditionalFlowStep(
|
||||
inlbl = TaintedObject::label() and
|
||||
outlbl = TaintedObject::label() and
|
||||
exists(NoSql::Query query, DataFlow::SourceNode queryObj |
|
||||
queryObj.flowsToExpr(query) and
|
||||
queryObj.flowsTo(query) and
|
||||
queryObj.flowsTo(trg) and
|
||||
src = queryObj.getAPropertyWrite().getRhs()
|
||||
)
|
||||
|
||||
@@ -15,16 +15,16 @@ import extraction.ExtractEndpointData
|
||||
|
||||
string getAReasonSinkExcluded(DataFlow::Node sinkCandidate, Query query) {
|
||||
query instanceof NosqlInjectionQuery and
|
||||
result = NosqlInjectionATM::SinkEndpointFilter::getAReasonSinkExcluded(sinkCandidate)
|
||||
result = NosqlInjectionAtm::SinkEndpointFilter::getAReasonSinkExcluded(sinkCandidate)
|
||||
or
|
||||
query instanceof SqlInjectionQuery and
|
||||
result = SqlInjectionATM::SinkEndpointFilter::getAReasonSinkExcluded(sinkCandidate)
|
||||
result = SqlInjectionAtm::SinkEndpointFilter::getAReasonSinkExcluded(sinkCandidate)
|
||||
or
|
||||
query instanceof TaintedPathQuery and
|
||||
result = TaintedPathATM::SinkEndpointFilter::getAReasonSinkExcluded(sinkCandidate)
|
||||
result = TaintedPathAtm::SinkEndpointFilter::getAReasonSinkExcluded(sinkCandidate)
|
||||
or
|
||||
query instanceof XssQuery and
|
||||
result = XssATM::SinkEndpointFilter::getAReasonSinkExcluded(sinkCandidate)
|
||||
result = XssAtm::SinkEndpointFilter::getAReasonSinkExcluded(sinkCandidate)
|
||||
}
|
||||
|
||||
pragma[inline]
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* For internal use only.
|
||||
*
|
||||
*
|
||||
* Count the number of sinks and alerts for a particular dataflow config.
|
||||
*/
|
||||
|
||||
import javascript
|
||||
import evaluation.EndToEndEvaluation
|
||||
|
||||
query predicate countAlertsAndSinks(int numAlerts, int numSinks) {
|
||||
numAlerts =
|
||||
count(DataFlow::Configuration cfg, DataFlow::Node source, DataFlow::Node sink |
|
||||
cfg.hasFlow(source, sink) and not isFlowExcluded(source, sink)
|
||||
) and
|
||||
numSinks =
|
||||
count(DataFlow::Node sink |
|
||||
exists(DataFlow::Configuration cfg | cfg.isSink(sink) or cfg.isSink(sink, _))
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
/*
|
||||
* For internal use only.
|
||||
*
|
||||
*
|
||||
* Count the number of sinks and alerts for the `CodeInjection` security query.
|
||||
*/
|
||||
|
||||
import semmle.javascript.security.dataflow.CodeInjectionQuery
|
||||
import CountAlertsAndSinks
|
||||
@@ -0,0 +1,9 @@
|
||||
/*
|
||||
* For internal use only.
|
||||
*
|
||||
*
|
||||
* Count the number of sinks and alerts for the `NosqlInection` security query.
|
||||
*/
|
||||
|
||||
import semmle.javascript.security.dataflow.NosqlInjectionQuery
|
||||
import CountAlertsAndSinks
|
||||
@@ -0,0 +1,9 @@
|
||||
/*
|
||||
* For internal use only.
|
||||
*
|
||||
*
|
||||
* Count the number of sinks and alerts for the `SqlInection` security query.
|
||||
*/
|
||||
|
||||
import semmle.javascript.security.dataflow.SqlInjectionQuery
|
||||
import CountAlertsAndSinks
|
||||
@@ -0,0 +1,9 @@
|
||||
/*
|
||||
* For internal use only.
|
||||
*
|
||||
*
|
||||
* Count the number of sinks and alerts for the `TaintedPath` security query.
|
||||
*/
|
||||
|
||||
import semmle.javascript.security.dataflow.TaintedPathQuery
|
||||
import CountAlertsAndSinks
|
||||
@@ -0,0 +1,9 @@
|
||||
/*
|
||||
* For internal use only.
|
||||
*
|
||||
*
|
||||
* Count the number of sinks and alerts for the `DomBasedXss` security query.
|
||||
*/
|
||||
|
||||
import semmle.javascript.security.dataflow.DomBasedXssQuery
|
||||
import CountAlertsAndSinks
|
||||
@@ -0,0 +1,9 @@
|
||||
/*
|
||||
* For internal use only.
|
||||
*
|
||||
*
|
||||
* Count the number of sinks and alerts for the `XssThroughDom` security query.
|
||||
*/
|
||||
|
||||
import semmle.javascript.security.dataflow.XssThroughDomQuery
|
||||
import CountAlertsAndSinks
|
||||
@@ -5,7 +5,8 @@
|
||||
* evaluation pipeline.
|
||||
*/
|
||||
|
||||
import semmle.javascript.security.dataflow.NosqlInjection
|
||||
import javascript
|
||||
import semmle.javascript.security.dataflow.NosqlInjectionQuery as NosqlInjection
|
||||
import EndToEndEvaluation as EndToEndEvaluation
|
||||
|
||||
from
|
||||
|
||||
@@ -5,7 +5,8 @@
|
||||
* evaluation pipeline.
|
||||
*/
|
||||
|
||||
import semmle.javascript.security.dataflow.SqlInjection
|
||||
import javascript
|
||||
import semmle.javascript.security.dataflow.SqlInjectionQuery as SqlInjection
|
||||
import EndToEndEvaluation as EndToEndEvaluation
|
||||
|
||||
from
|
||||
|
||||
@@ -5,7 +5,8 @@
|
||||
* evaluation pipeline.
|
||||
*/
|
||||
|
||||
import semmle.javascript.security.dataflow.TaintedPath
|
||||
import javascript
|
||||
import semmle.javascript.security.dataflow.TaintedPathQuery as TaintedPath
|
||||
import EndToEndEvaluation as EndToEndEvaluation
|
||||
|
||||
from
|
||||
|
||||
@@ -5,7 +5,8 @@
|
||||
* pipeline.
|
||||
*/
|
||||
|
||||
import semmle.javascript.security.dataflow.DomBasedXss
|
||||
import javascript
|
||||
import semmle.javascript.security.dataflow.DomBasedXssQuery as DomBasedXss
|
||||
import EndToEndEvaluation as EndToEndEvaluation
|
||||
|
||||
from
|
||||
|
||||
@@ -14,10 +14,26 @@ import experimental.adaptivethreatmodeling.EndpointFeatures as EndpointFeatures
|
||||
import experimental.adaptivethreatmodeling.EndpointScoring as EndpointScoring
|
||||
import experimental.adaptivethreatmodeling.EndpointTypes
|
||||
import experimental.adaptivethreatmodeling.FilteringReasons
|
||||
import experimental.adaptivethreatmodeling.NosqlInjectionATM as NosqlInjectionATM
|
||||
import experimental.adaptivethreatmodeling.SqlInjectionATM as SqlInjectionATM
|
||||
import experimental.adaptivethreatmodeling.TaintedPathATM as TaintedPathATM
|
||||
import experimental.adaptivethreatmodeling.XssATM as XssATM
|
||||
import experimental.adaptivethreatmodeling.NosqlInjectionATM as NosqlInjectionAtm
|
||||
|
||||
/** DEPRECATED: Alias for NosqlInjectionAtm */
|
||||
deprecated module NosqlInjectionATM = NosqlInjectionAtm;
|
||||
|
||||
import experimental.adaptivethreatmodeling.SqlInjectionATM as SqlInjectionAtm
|
||||
|
||||
/** DEPRECATED: Alias for SqlInjectionAtm */
|
||||
deprecated module SqlInjectionATM = SqlInjectionAtm;
|
||||
|
||||
import experimental.adaptivethreatmodeling.TaintedPathATM as TaintedPathAtm
|
||||
|
||||
/** DEPRECATED: Alias for TaintedPathAtm */
|
||||
deprecated module TaintedPathATM = TaintedPathAtm;
|
||||
|
||||
import experimental.adaptivethreatmodeling.XssATM as XssAtm
|
||||
|
||||
/** DEPRECATED: Alias for XssAtm */
|
||||
deprecated module XssATM = XssAtm;
|
||||
|
||||
import Labels
|
||||
import NoFeaturizationRestrictionsConfig
|
||||
import Queries
|
||||
@@ -25,13 +41,13 @@ import Queries
|
||||
/** Gets the ATM configuration object for the specified query. */
|
||||
AtmConfig getAtmCfg(Query query) {
|
||||
query instanceof NosqlInjectionQuery and
|
||||
result instanceof NosqlInjectionATM::NosqlInjectionAtmConfig
|
||||
result instanceof NosqlInjectionAtm::NosqlInjectionAtmConfig
|
||||
or
|
||||
query instanceof SqlInjectionQuery and result instanceof SqlInjectionATM::SqlInjectionAtmConfig
|
||||
query instanceof SqlInjectionQuery and result instanceof SqlInjectionAtm::SqlInjectionAtmConfig
|
||||
or
|
||||
query instanceof TaintedPathQuery and result instanceof TaintedPathATM::TaintedPathAtmConfig
|
||||
query instanceof TaintedPathQuery and result instanceof TaintedPathAtm::TaintedPathAtmConfig
|
||||
or
|
||||
query instanceof XssQuery and result instanceof XssATM::DomBasedXssAtmConfig
|
||||
query instanceof XssQuery and result instanceof XssAtm::DomBasedXssAtmConfig
|
||||
}
|
||||
|
||||
/** DEPRECATED: Alias for getAtmCfg */
|
||||
@@ -39,13 +55,13 @@ deprecated ATMConfig getATMCfg(Query query) { result = getAtmCfg(query) }
|
||||
|
||||
/** Gets the ATM data flow configuration for the specified query. */
|
||||
DataFlow::Configuration getDataFlowCfg(Query query) {
|
||||
query instanceof NosqlInjectionQuery and result instanceof NosqlInjectionATM::Configuration
|
||||
query instanceof NosqlInjectionQuery and result instanceof NosqlInjectionAtm::Configuration
|
||||
or
|
||||
query instanceof SqlInjectionQuery and result instanceof SqlInjectionATM::Configuration
|
||||
query instanceof SqlInjectionQuery and result instanceof SqlInjectionAtm::Configuration
|
||||
or
|
||||
query instanceof TaintedPathQuery and result instanceof TaintedPathATM::Configuration
|
||||
query instanceof TaintedPathQuery and result instanceof TaintedPathAtm::Configuration
|
||||
or
|
||||
query instanceof XssQuery and result instanceof XssATM::Configuration
|
||||
query instanceof XssQuery and result instanceof XssAtm::Configuration
|
||||
}
|
||||
|
||||
/** Gets a known sink for the specified query. */
|
||||
@@ -188,10 +204,10 @@ module FlowFromSource {
|
||||
|
||||
Query getQuery() { result = q }
|
||||
|
||||
/** The sinks are the endpoints we're extracting. */
|
||||
/** Holds if `sink` is an endpoint we're extracting. */
|
||||
override predicate isSink(DataFlow::Node sink) { sink = getAnEndpoint(q) }
|
||||
|
||||
/** The sinks are the endpoints we're extracting. */
|
||||
/** Holds if `sink` is an endpoint we're extracting. */
|
||||
override predicate isSink(DataFlow::Node sink, DataFlow::FlowLabel lbl) {
|
||||
sink = getAnEndpoint(q) and exists(lbl)
|
||||
}
|
||||
|
||||
@@ -4,25 +4,25 @@
|
||||
* Maps ML-powered queries to their `EndpointType` for clearer labelling while evaluating ML model during training.
|
||||
*/
|
||||
|
||||
import experimental.adaptivethreatmodeling.SqlInjectionATM as SqlInjectionATM
|
||||
import experimental.adaptivethreatmodeling.NosqlInjectionATM as NosqlInjectionATM
|
||||
import experimental.adaptivethreatmodeling.TaintedPathATM as TaintedPathATM
|
||||
import experimental.adaptivethreatmodeling.XssATM as XssATM
|
||||
import experimental.adaptivethreatmodeling.SqlInjectionATM as SqlInjectionAtm
|
||||
import experimental.adaptivethreatmodeling.NosqlInjectionATM as NosqlInjectionAtm
|
||||
import experimental.adaptivethreatmodeling.TaintedPathATM as TaintedPathAtm
|
||||
import experimental.adaptivethreatmodeling.XssATM as XssAtm
|
||||
import experimental.adaptivethreatmodeling.AdaptiveThreatModeling
|
||||
|
||||
from string queryName, AtmConfig c, EndpointType e
|
||||
where
|
||||
(
|
||||
queryName = "SqlInjection" and
|
||||
c instanceof SqlInjectionATM::SqlInjectionAtmConfig
|
||||
c instanceof SqlInjectionAtm::SqlInjectionAtmConfig
|
||||
or
|
||||
queryName = "NosqlInjection" and
|
||||
c instanceof NosqlInjectionATM::NosqlInjectionAtmConfig
|
||||
c instanceof NosqlInjectionAtm::NosqlInjectionAtmConfig
|
||||
or
|
||||
queryName = "TaintedPath" and
|
||||
c instanceof TaintedPathATM::TaintedPathAtmConfig
|
||||
c instanceof TaintedPathAtm::TaintedPathAtmConfig
|
||||
or
|
||||
queryName = "Xss" and c instanceof XssATM::DomBasedXssAtmConfig
|
||||
queryName = "Xss" and c instanceof XssAtm::DomBasedXssAtmConfig
|
||||
) and
|
||||
e = c.getASinkEndpointType()
|
||||
select queryName, e.getEncoding() as label
|
||||
|
||||
@@ -7,20 +7,20 @@
|
||||
*/
|
||||
|
||||
import javascript
|
||||
import experimental.adaptivethreatmodeling.NosqlInjectionATM as NosqlInjectionATM
|
||||
import experimental.adaptivethreatmodeling.SqlInjectionATM as SqlInjectionATM
|
||||
import experimental.adaptivethreatmodeling.TaintedPathATM as TaintedPathATM
|
||||
import experimental.adaptivethreatmodeling.XssATM as XssATM
|
||||
import experimental.adaptivethreatmodeling.NosqlInjectionATM as NosqlInjectionAtm
|
||||
import experimental.adaptivethreatmodeling.SqlInjectionATM as SqlInjectionAtm
|
||||
import experimental.adaptivethreatmodeling.TaintedPathATM as TaintedPathAtm
|
||||
import experimental.adaptivethreatmodeling.XssATM as XssAtm
|
||||
import experimental.adaptivethreatmodeling.EndpointFeatures as EndpointFeatures
|
||||
import experimental.adaptivethreatmodeling.StandardEndpointFilters as StandardEndpointFilters
|
||||
import extraction.NoFeaturizationRestrictionsConfig
|
||||
|
||||
query predicate tokenFeatures(DataFlow::Node endpoint, string featureName, string featureValue) {
|
||||
(
|
||||
not exists(NosqlInjectionATM::SinkEndpointFilter::getAReasonSinkExcluded(endpoint)) or
|
||||
not exists(SqlInjectionATM::SinkEndpointFilter::getAReasonSinkExcluded(endpoint)) or
|
||||
not exists(TaintedPathATM::SinkEndpointFilter::getAReasonSinkExcluded(endpoint)) or
|
||||
not exists(XssATM::SinkEndpointFilter::getAReasonSinkExcluded(endpoint)) or
|
||||
not exists(NosqlInjectionAtm::SinkEndpointFilter::getAReasonSinkExcluded(endpoint)) or
|
||||
not exists(SqlInjectionAtm::SinkEndpointFilter::getAReasonSinkExcluded(endpoint)) or
|
||||
not exists(TaintedPathAtm::SinkEndpointFilter::getAReasonSinkExcluded(endpoint)) or
|
||||
not exists(XssAtm::SinkEndpointFilter::getAReasonSinkExcluded(endpoint)) or
|
||||
StandardEndpointFilters::isArgumentToModeledFunction(endpoint)
|
||||
) and
|
||||
EndpointFeatures::tokenFeatures(endpoint, featureName, featureValue)
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
*
|
||||
* This test checks several components of the endpoint filters for each query to see whether they
|
||||
* filter out any known sinks. It explicitly does not check the endpoint filtering step that's based
|
||||
* on whether the endpoint is an argument to a modelled function, since this necessarily filters out
|
||||
* on whether the endpoint is an argument to a modeled function, since this necessarily filters out
|
||||
* all known sinks. However, we can test all the other filtering steps against the set of known
|
||||
* sinks.
|
||||
*
|
||||
@@ -17,31 +17,31 @@ import semmle.javascript.security.dataflow.SqlInjectionCustomizations
|
||||
import semmle.javascript.security.dataflow.TaintedPathCustomizations
|
||||
import semmle.javascript.security.dataflow.DomBasedXssCustomizations
|
||||
import experimental.adaptivethreatmodeling.StandardEndpointFilters as StandardEndpointFilters
|
||||
import experimental.adaptivethreatmodeling.NosqlInjectionATM as NosqlInjectionATM
|
||||
import experimental.adaptivethreatmodeling.SqlInjectionATM as SqlInjectionATM
|
||||
import experimental.adaptivethreatmodeling.TaintedPathATM as TaintedPathATM
|
||||
import experimental.adaptivethreatmodeling.XssATM as XssATM
|
||||
import experimental.adaptivethreatmodeling.NosqlInjectionATM as NosqlInjectionAtm
|
||||
import experimental.adaptivethreatmodeling.SqlInjectionATM as SqlInjectionAtm
|
||||
import experimental.adaptivethreatmodeling.TaintedPathATM as TaintedPathAtm
|
||||
import experimental.adaptivethreatmodeling.XssATM as XssAtm
|
||||
|
||||
query predicate nosqlFilteredTruePositives(DataFlow::Node endpoint, string reason) {
|
||||
endpoint instanceof NosqlInjection::Sink and
|
||||
reason = NosqlInjectionATM::SinkEndpointFilter::getAReasonSinkExcluded(endpoint) and
|
||||
reason = NosqlInjectionAtm::SinkEndpointFilter::getAReasonSinkExcluded(endpoint) and
|
||||
not reason = ["argument to modeled function", "modeled sink", "modeled database access"]
|
||||
}
|
||||
|
||||
query predicate sqlFilteredTruePositives(DataFlow::Node endpoint, string reason) {
|
||||
endpoint instanceof SqlInjection::Sink and
|
||||
reason = SqlInjectionATM::SinkEndpointFilter::getAReasonSinkExcluded(endpoint) and
|
||||
reason = SqlInjectionAtm::SinkEndpointFilter::getAReasonSinkExcluded(endpoint) and
|
||||
reason != "argument to modeled function"
|
||||
}
|
||||
|
||||
query predicate taintedPathFilteredTruePositives(DataFlow::Node endpoint, string reason) {
|
||||
endpoint instanceof TaintedPath::Sink and
|
||||
reason = TaintedPathATM::SinkEndpointFilter::getAReasonSinkExcluded(endpoint) and
|
||||
reason = TaintedPathAtm::SinkEndpointFilter::getAReasonSinkExcluded(endpoint) and
|
||||
reason != "argument to modeled function"
|
||||
}
|
||||
|
||||
query predicate xssFilteredTruePositives(DataFlow::Node endpoint, string reason) {
|
||||
endpoint instanceof DomBasedXss::Sink and
|
||||
reason = XssATM::SinkEndpointFilter::getAReasonSinkExcluded(endpoint) and
|
||||
reason = XssAtm::SinkEndpointFilter::getAReasonSinkExcluded(endpoint) and
|
||||
reason != "argument to modeled function"
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import javascript
|
||||
import experimental.adaptivethreatmodeling.NosqlInjectionATM as NosqlInjectionATM
|
||||
import experimental.adaptivethreatmodeling.NosqlInjectionATM as NosqlInjectionAtm
|
||||
|
||||
query predicate effectiveSinks(DataFlow::Node node) {
|
||||
not exists(NosqlInjectionATM::SinkEndpointFilter::getAReasonSinkExcluded(node))
|
||||
not exists(NosqlInjectionAtm::SinkEndpointFilter::getAReasonSinkExcluded(node))
|
||||
}
|
||||
|
||||
@@ -1,3 +1,50 @@
|
||||
## 0.2.4
|
||||
|
||||
### Deprecated APIs
|
||||
|
||||
* Many classes/predicates/modules with upper-case acronyms in their name have been renamed to follow our style-guide.
|
||||
The old name still exists as a deprecated alias.
|
||||
* The utility files previously in the `semmle.javascript.security.performance` package have been moved to the `semmle.javascript.security.regexp` package.
|
||||
The previous files still exist as deprecated aliases.
|
||||
|
||||
### Minor Analysis Improvements
|
||||
|
||||
* Most deprecated predicates/classes/modules that have been deprecated for over a year have been deleted.
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Fixed that top-level `for await` statements would produce a syntax error. These statements are now parsed correctly.
|
||||
|
||||
## 0.2.3
|
||||
|
||||
## 0.2.2
|
||||
|
||||
## 0.2.1
|
||||
|
||||
### Minor Analysis Improvements
|
||||
|
||||
* The `chownr` library is now modeled as a sink for the `js/path-injection` query.
|
||||
* Improved modeling of sensitive data sources, so common words like `certain` and `secretary` are no longer considered a certificate and a secret (respectively).
|
||||
* The `gray-matter` library is now modeled as a sink for the `js/code-injection` query.
|
||||
|
||||
## 0.2.0
|
||||
|
||||
### Major Analysis Improvements
|
||||
|
||||
* Added support for TypeScript 4.7.
|
||||
|
||||
### Minor Analysis Improvements
|
||||
|
||||
* All new ECMAScript 2022 features are now supported.
|
||||
|
||||
## 0.1.4
|
||||
|
||||
## 0.1.3
|
||||
|
||||
### Minor Analysis Improvements
|
||||
|
||||
* The `isLibaryFile` predicate from `ClassifyFiles.qll` has been renamed to `isLibraryFile` to fix a typo.
|
||||
|
||||
## 0.1.2
|
||||
|
||||
### Deprecated APIs
|
||||
|
||||
@@ -37,6 +37,8 @@ predicate inVoidContext(Expr e) {
|
||||
)
|
||||
or
|
||||
exists(LogicalBinaryExpr logical | e = logical.getRightOperand() and inVoidContext(logical))
|
||||
or
|
||||
exists(ConditionalExpr cond | e = cond.getABranch() | inVoidContext(cond))
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
57
javascript/ql/lib/change-notes/2022-04-04-dataflow-models.md
Normal file
57
javascript/ql/lib/change-notes/2022-04-04-dataflow-models.md
Normal file
@@ -0,0 +1,57 @@
|
||||
---
|
||||
category: breaking
|
||||
---
|
||||
* Many library models have been rewritten to use dataflow nodes instead of the AST.
|
||||
The types of some classes have been changed, and these changes may break existing code.
|
||||
Other classes and predicates have been renamed, in these cases the old name is still available as a deprecated feature.
|
||||
|
||||
* The basetype of the following list of classes has changed from an expression to a dataflow node, and thus code using these classes might break.
|
||||
The fix to these breakages is usually to use `asExpr()` to get an expression from a dataflow node, or to use `.flow()` to get a dataflow node from an expression.
|
||||
- DOM.qll#WebStorageWrite
|
||||
- CryptoLibraries.qll#CryptographicOperation
|
||||
- Express.qll#Express::RequestBodyAccess
|
||||
- HTTP.qll#HTTP::ResponseBody
|
||||
- HTTP.qll#HTTP::CookieDefinition
|
||||
- HTTP.qll#HTTP::ServerDefinition
|
||||
- HTTP.qll#HTTP::RouteSetup
|
||||
- NoSQL.qll#NoSql::Query
|
||||
- SQL.qll#SQL::SqlString
|
||||
- SQL.qll#SQL::SqlSanitizer
|
||||
- HTTP.qll#ResponseBody
|
||||
- HTTP.qll#CookieDefinition
|
||||
- HTTP.qll#ServerDefinition
|
||||
- HTTP.qll#RouteSetup
|
||||
- HTTP.qll#HTTP::RedirectInvocation
|
||||
- HTTP.qll#RedirectInvocation
|
||||
- Express.qll#Express::RouterDefinition
|
||||
- AngularJSCore.qll#LinkFunction
|
||||
- Connect.qll#Connect::StandardRouteHandler
|
||||
- CryptoLibraries.qll#CryptographicKeyCredentialsExpr
|
||||
- AWS.qll#AWS::Credentials
|
||||
- Azure.qll#Azure::Credentials
|
||||
- Connect.qll#Connect::Credentials
|
||||
- DigitalOcean.qll#DigitalOcean::Credentials
|
||||
- Express.qll#Express::Credentials
|
||||
- NodeJSLib.qll#NodeJSLib::Credentials
|
||||
- PkgCloud.qll#PkgCloud::Credentials
|
||||
- Request.qll#Request::Credentials
|
||||
- ServiceDefinitions.qll#InjectableFunctionServiceRequest
|
||||
- SensitiveActions.qll#SensitiveVariableAccess
|
||||
- SensitiveActions.qll#CleartextPasswordExpr
|
||||
- Connect.qll#Connect::ServerDefinition
|
||||
- Restify.qll#Restify::ServerDefinition
|
||||
- Connect.qll#Connect::RouteSetup
|
||||
- Express.qll#Express::RouteSetup
|
||||
- Fastify.qll#Fastify::RouteSetup
|
||||
- Hapi.qll#Hapi::RouteSetup
|
||||
- Koa.qll#Koa::RouteSetup
|
||||
- Restify.qll#Restify::RouteSetup
|
||||
- NodeJSLib.qll#NodeJSLib::RouteSetup
|
||||
- Express.qll#Express::StandardRouteHandler
|
||||
- Express.qll#Express::SetCookie
|
||||
- Hapi.qll#Hapi::RouteHandler
|
||||
- HTTP.qll#HTTP::Servers::StandardHeaderDefinition
|
||||
- HTTP.qll#Servers::StandardHeaderDefinition
|
||||
- Hapi.qll#Hapi::ServerDefinition
|
||||
- Koa.qll#Koa::AppDefinition
|
||||
- SensitiveActions.qll#SensitiveCall
|
||||
@@ -0,0 +1,4 @@
|
||||
---
|
||||
category: majorAnalysis
|
||||
---
|
||||
* Added support for TypeScript 4.8.
|
||||
4
javascript/ql/lib/change-notes/2022-08-09-mermaid.md
Normal file
4
javascript/ql/lib/change-notes/2022-08-09-mermaid.md
Normal file
@@ -0,0 +1,4 @@
|
||||
---
|
||||
category: minorAnalysis
|
||||
---
|
||||
* A model for the `mermaid` library has been added. XSS queries can now detect flow through the `render` method of the `mermaid` library.
|
||||
@@ -1,4 +1,5 @@
|
||||
---
|
||||
category: minorAnalysis
|
||||
---
|
||||
## 0.1.3
|
||||
|
||||
### Minor Analysis Improvements
|
||||
|
||||
* The `isLibaryFile` predicate from `ClassifyFiles.qll` has been renamed to `isLibraryFile` to fix a typo.
|
||||
1
javascript/ql/lib/change-notes/released/0.1.4.md
Normal file
1
javascript/ql/lib/change-notes/released/0.1.4.md
Normal file
@@ -0,0 +1 @@
|
||||
## 0.1.4
|
||||
9
javascript/ql/lib/change-notes/released/0.2.0.md
Normal file
9
javascript/ql/lib/change-notes/released/0.2.0.md
Normal file
@@ -0,0 +1,9 @@
|
||||
## 0.2.0
|
||||
|
||||
### Major Analysis Improvements
|
||||
|
||||
* Added support for TypeScript 4.7.
|
||||
|
||||
### Minor Analysis Improvements
|
||||
|
||||
* All new ECMAScript 2022 features are now supported.
|
||||
7
javascript/ql/lib/change-notes/released/0.2.1.md
Normal file
7
javascript/ql/lib/change-notes/released/0.2.1.md
Normal file
@@ -0,0 +1,7 @@
|
||||
## 0.2.1
|
||||
|
||||
### Minor Analysis Improvements
|
||||
|
||||
* The `chownr` library is now modeled as a sink for the `js/path-injection` query.
|
||||
* Improved modeling of sensitive data sources, so common words like `certain` and `secretary` are no longer considered a certificate and a secret (respectively).
|
||||
* The `gray-matter` library is now modeled as a sink for the `js/code-injection` query.
|
||||
1
javascript/ql/lib/change-notes/released/0.2.2.md
Normal file
1
javascript/ql/lib/change-notes/released/0.2.2.md
Normal file
@@ -0,0 +1 @@
|
||||
## 0.2.2
|
||||
1
javascript/ql/lib/change-notes/released/0.2.3.md
Normal file
1
javascript/ql/lib/change-notes/released/0.2.3.md
Normal file
@@ -0,0 +1 @@
|
||||
## 0.2.3
|
||||
16
javascript/ql/lib/change-notes/released/0.2.4.md
Normal file
16
javascript/ql/lib/change-notes/released/0.2.4.md
Normal file
@@ -0,0 +1,16 @@
|
||||
## 0.2.4
|
||||
|
||||
### Deprecated APIs
|
||||
|
||||
* Many classes/predicates/modules with upper-case acronyms in their name have been renamed to follow our style-guide.
|
||||
The old name still exists as a deprecated alias.
|
||||
* The utility files previously in the `semmle.javascript.security.performance` package have been moved to the `semmle.javascript.security.regexp` package.
|
||||
The previous files still exist as deprecated aliases.
|
||||
|
||||
### Minor Analysis Improvements
|
||||
|
||||
* Most deprecated predicates/classes/modules that have been deprecated for over a year have been deleted.
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Fixed that top-level `for await` statements would produce a syntax error. These statements are now parsed correctly.
|
||||
@@ -1,2 +1,2 @@
|
||||
---
|
||||
lastReleaseVersion: 0.1.2
|
||||
lastReleaseVersion: 0.2.4
|
||||
|
||||
@@ -45,7 +45,7 @@ private predicate variableDefLookup(VarAccess va, AstNode def, string kind) {
|
||||
|
||||
/**
|
||||
* Holds if variable access `va` is of kind `kind` and refers to the
|
||||
* variable declaration.
|
||||
* variable declaration `decl`.
|
||||
*
|
||||
* For example, in the statement `var x = 42, y = x;`, the initializing
|
||||
* expression of `y` is a variable access `x` of kind `"V"` that refers to
|
||||
@@ -1,5 +1,5 @@
|
||||
name: codeql/javascript-all
|
||||
version: 0.1.3-dev
|
||||
version: 0.2.5-dev
|
||||
groups: javascript
|
||||
dbscheme: semmlecode.javascript.dbscheme
|
||||
extractor: javascript
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
import javascript
|
||||
private import semmle.javascript.internal.CachedStages
|
||||
private import Expressions.ExprHasNoEffect
|
||||
|
||||
/**
|
||||
* An AMD `define` call.
|
||||
@@ -26,7 +27,7 @@ private import semmle.javascript.internal.CachedStages
|
||||
*/
|
||||
class AmdModuleDefinition extends CallExpr {
|
||||
AmdModuleDefinition() {
|
||||
getParent() instanceof ExprStmt and
|
||||
inVoidContext(this) and
|
||||
getCallee().(GlobalVarAccess).getName() = "define" and
|
||||
exists(int n | n = getNumArgument() |
|
||||
n = 1
|
||||
@@ -202,13 +203,22 @@ private class ConstantAmdDependencyPathElement extends PathExpr, ConstantString
|
||||
override string getValue() { result = getStringValue() }
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `nd` is nested inside an AMD module definition.
|
||||
*/
|
||||
private predicate inAmdModuleDefinition(AstNode nd) {
|
||||
nd.getParent() instanceof AmdModuleDefinition
|
||||
or
|
||||
inAmdModuleDefinition(nd.getParent())
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `def` is an AMD module definition in `tl` which is not
|
||||
* nested inside another module definition.
|
||||
*/
|
||||
private predicate amdModuleTopLevel(AmdModuleDefinition def, TopLevel tl) {
|
||||
def.getTopLevel() = tl and
|
||||
not def.getParent+() instanceof AmdModuleDefinition
|
||||
not inAmdModuleDefinition(def)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -11,7 +11,7 @@ import javascript
|
||||
*/
|
||||
module Actions {
|
||||
/** A YAML node in a GitHub Actions workflow file. */
|
||||
private class Node extends YAMLNode {
|
||||
private class Node extends YamlNode {
|
||||
Node() {
|
||||
this.getLocation()
|
||||
.getFile()
|
||||
@@ -24,9 +24,12 @@ module Actions {
|
||||
* An Actions workflow. This is a mapping at the top level of an Actions YAML workflow file.
|
||||
* See https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions.
|
||||
*/
|
||||
class Workflow extends Node, YAMLDocument, YAMLMapping {
|
||||
class Workflow extends Node, YamlDocument, YamlMapping {
|
||||
/** Gets the `jobs` mapping from job IDs to job definitions in this workflow. */
|
||||
YAMLMapping getJobs() { result = this.lookup("jobs") }
|
||||
YamlMapping getJobs() { result = this.lookup("jobs") }
|
||||
|
||||
/** Gets the name of the workflow. */
|
||||
string getName() { result = this.lookup("name").(YamlString).getValue() }
|
||||
|
||||
/** Gets the name of the workflow file. */
|
||||
string getFileName() { result = this.getFile().getBaseName() }
|
||||
@@ -42,7 +45,7 @@ module Actions {
|
||||
* An Actions On trigger within a workflow.
|
||||
* See https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#on.
|
||||
*/
|
||||
class On extends YAMLNode, YAMLMappingLikeNode {
|
||||
class On extends YamlNode, YamlMappingLikeNode {
|
||||
Workflow workflow;
|
||||
|
||||
On() { workflow.lookup("on") = this }
|
||||
@@ -55,7 +58,7 @@ module Actions {
|
||||
* An Actions job within a workflow.
|
||||
* See https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobs.
|
||||
*/
|
||||
class Job extends YAMLNode, YAMLMapping {
|
||||
class Job extends YamlNode, YamlMapping {
|
||||
string jobId;
|
||||
Workflow workflow;
|
||||
|
||||
@@ -71,19 +74,19 @@ module Actions {
|
||||
* Gets the ID of this job, as a YAML scalar node.
|
||||
* This is the job's key within the `jobs` mapping.
|
||||
*/
|
||||
YAMLString getIdNode() { workflow.getJobs().maps(result, this) }
|
||||
YamlString getIdNode() { workflow.getJobs().maps(result, this) }
|
||||
|
||||
/** Gets the human-readable name of this job, if any, as a string. */
|
||||
string getName() { result = this.getNameNode().getValue() }
|
||||
|
||||
/** Gets the human-readable name of this job, if any, as a YAML scalar node. */
|
||||
YAMLString getNameNode() { result = this.lookup("name") }
|
||||
YamlString getNameNode() { result = this.lookup("name") }
|
||||
|
||||
/** Gets the step at the given index within this job. */
|
||||
Step getStep(int index) { result.getJob() = this and result.getIndex() = index }
|
||||
|
||||
/** Gets the sequence of `steps` within this job. */
|
||||
YAMLSequence getSteps() { result = this.lookup("steps") }
|
||||
YamlSequence getSteps() { result = this.lookup("steps") }
|
||||
|
||||
/** Gets the workflow this job belongs to. */
|
||||
Workflow getWorkflow() { result = workflow }
|
||||
@@ -96,7 +99,7 @@ module Actions {
|
||||
* An `if` within a job.
|
||||
* See https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idif.
|
||||
*/
|
||||
class JobIf extends YAMLNode, YAMLScalar {
|
||||
class JobIf extends YamlNode, YamlScalar {
|
||||
Job job;
|
||||
|
||||
JobIf() { job.lookup("if") = this }
|
||||
@@ -109,7 +112,7 @@ module Actions {
|
||||
* A step within an Actions job.
|
||||
* See https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idsteps.
|
||||
*/
|
||||
class Step extends YAMLNode, YAMLMapping {
|
||||
class Step extends YamlNode, YamlMapping {
|
||||
int index;
|
||||
Job job;
|
||||
|
||||
@@ -129,13 +132,16 @@ module Actions {
|
||||
|
||||
/** Gets the value of the `if` field in this step, if any. */
|
||||
StepIf getIf() { result.getStep() = this }
|
||||
|
||||
/** Gets the ID of this step, if any. */
|
||||
string getId() { result = this.lookup("id").(YamlString).getValue() }
|
||||
}
|
||||
|
||||
/**
|
||||
* An `if` within a step.
|
||||
* See https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idstepsif.
|
||||
*/
|
||||
class StepIf extends YAMLNode, YAMLScalar {
|
||||
class StepIf extends YamlNode, YamlScalar {
|
||||
Step step;
|
||||
|
||||
StepIf() { step.lookup("if") = this }
|
||||
@@ -164,7 +170,7 @@ module Actions {
|
||||
*
|
||||
* Does not handle local repository references, e.g. `.github/actions/action-name`.
|
||||
*/
|
||||
class Uses extends YAMLNode, YAMLScalar {
|
||||
class Uses extends YamlNode, YamlScalar {
|
||||
Step step;
|
||||
|
||||
Uses() { step.lookup("uses") = this }
|
||||
@@ -194,7 +200,7 @@ module Actions {
|
||||
* arg2: abc
|
||||
* ```
|
||||
*/
|
||||
class With extends YAMLNode, YAMLMapping {
|
||||
class With extends YamlNode, YamlMapping {
|
||||
Step step;
|
||||
|
||||
With() { step.lookup("with") = this }
|
||||
@@ -213,7 +219,7 @@ module Actions {
|
||||
* ref: ${{ github.event.pull_request.head.sha }}
|
||||
* ```
|
||||
*/
|
||||
class Ref extends YAMLNode, YAMLString {
|
||||
class Ref extends YamlNode, YamlString {
|
||||
With with;
|
||||
|
||||
Ref() { with.lookup("ref") = this }
|
||||
@@ -226,7 +232,7 @@ module Actions {
|
||||
* A `run` field within an Actions job step, which runs command-line programs using an operating system shell.
|
||||
* See https://docs.github.com/en/free-pro-team@latest/actions/reference/workflow-syntax-for-github-actions#jobsjob_idstepsrun.
|
||||
*/
|
||||
class Run extends YAMLNode, YAMLString {
|
||||
class Run extends YamlNode, YamlString {
|
||||
Step step;
|
||||
|
||||
Run() { step.lookup("run") = this }
|
||||
|
||||
@@ -2,11 +2,7 @@
|
||||
* Provides an implementation of _API graphs_, which are an abstract representation of the API
|
||||
* surface used and/or defined by a code base.
|
||||
*
|
||||
* The nodes of the API graph represent definitions and uses of API components. The edges are
|
||||
* directed and labeled; they specify how the components represented by nodes relate to each other.
|
||||
* For example, if one of the nodes represents a definition of an API function, then there
|
||||
* will be nodes corresponding to the function's parameters, which are connected to the function
|
||||
* node by edges labeled `parameter <i>`.
|
||||
* See `API::Node` for more in-depth documentation.
|
||||
*/
|
||||
|
||||
import javascript
|
||||
@@ -14,50 +10,159 @@ private import semmle.javascript.dataflow.internal.FlowSteps as FlowSteps
|
||||
private import internal.CachedStages
|
||||
|
||||
/**
|
||||
* Provides classes and predicates for working with APIs defined or used in a database.
|
||||
* Provides classes and predicates for working with the API boundary between the current
|
||||
* codebase and external libraries.
|
||||
*
|
||||
* See `API::Node` for more in-depth documentation.
|
||||
*/
|
||||
module API {
|
||||
/**
|
||||
* An abstract representation of a definition or use of an API component such as a function
|
||||
* exported by an npm package, a parameter of such a function, or its result.
|
||||
* A node in the API graph, representing a value that has crossed the boundary between this
|
||||
* codebase and an external library (or in general, any external codebase).
|
||||
*
|
||||
* ### Basic usage
|
||||
*
|
||||
* API graphs are typically used to identify "API calls", that is, calls to an external function
|
||||
* whose implementation is not necessarily part of the current codebase.
|
||||
*
|
||||
* The most basic use of API graphs is typically as follows:
|
||||
* 1. Start with `API::moduleImport` for the relevant library.
|
||||
* 2. Follow up with a chain of accessors such as `getMember` describing how to get to the relevant API function.
|
||||
* 3. Map the resulting API graph nodes to data-flow nodes, using `asSource` or `asSink`.
|
||||
*
|
||||
* For example, a simplified way to get arguments to `underscore.extend` would be
|
||||
* ```ql
|
||||
* API::moduleImport("underscore").getMember("extend").getParameter(0).asSink()
|
||||
* ```
|
||||
*
|
||||
* The most commonly used accessors are `getMember`, `getParameter`, and `getReturn`.
|
||||
*
|
||||
* ### API graph nodes
|
||||
*
|
||||
* There are two kinds of nodes in the API graphs, distinguished by who is "holding" the value:
|
||||
* - **Use-nodes** represent values held by the current codebase, which came from an external library.
|
||||
* (The current codebase is "using" a value that came from the library).
|
||||
* - **Def-nodes** represent values held by the external library, which came from this codebase.
|
||||
* (The current codebase "defines" the value seen by the library).
|
||||
*
|
||||
* API graph nodes are associated with data-flow nodes in the current codebase.
|
||||
* (Since external libraries are not part of the database, there is no way to associate with concrete
|
||||
* data-flow nodes from the external library).
|
||||
* - **Use-nodes** are associated with data-flow nodes where a value enters the current codebase,
|
||||
* such as the return value of a call to an external function.
|
||||
* - **Def-nodes** are associated with data-flow nodes where a value leaves the current codebase,
|
||||
* such as an argument passed in a call to an external function.
|
||||
*
|
||||
*
|
||||
* ### Access paths and edge labels
|
||||
*
|
||||
* Nodes in the API graph are associated with a set of access paths, describing a series of operations
|
||||
* that may be performed to obtain that value.
|
||||
*
|
||||
* For example, the access path `API::moduleImport("lodash").getMember("extend")` represents the action of
|
||||
* importing `lodash` and then accessing the member `extend` on the resulting object.
|
||||
* It would be associated with an expression such as `require("lodash").extend`.
|
||||
*
|
||||
* Each edge in the graph is labelled by such an "operation". For an edge `A->B`, the type of the `A` node
|
||||
* determines who is performing the operation, and the type of the `B` node determines who ends up holding
|
||||
* the result:
|
||||
* - An edge starting from a use-node describes what the current codebase is doing to a value that
|
||||
* came from a library.
|
||||
* - An edge starting from a def-node describes what the external library might do to a value that
|
||||
* came from the current codebase.
|
||||
* - An edge ending in a use-node means the result ends up in the current codebase (at its associated data-flow node).
|
||||
* - An edge ending in a def-node means the result ends up in external code (its associated data-flow node is
|
||||
* the place where it was "last seen" in the current codebase before flowing out)
|
||||
*
|
||||
* Because the implementation of the external library is not visible, it is not known exactly what operations
|
||||
* it will perform on values that flow there. Instead, the edges starting from a def-node are operations that would
|
||||
* lead to an observable effect within the current codebase; without knowing for certain if the library will actually perform
|
||||
* those operations. (When constructing these edges, we assume the library is somewhat well-behaved).
|
||||
*
|
||||
* For example, given this snippet:
|
||||
* ```js
|
||||
* require('foo')(x => { doSomething(x) })
|
||||
* ```
|
||||
* A callback is passed to the external function `foo`. We can't know if `foo` will actually invoke this callback.
|
||||
* But _if_ the library should decide to invoke the callback, then a value will flow into the current codebase via the `x` parameter.
|
||||
* For that reason, an edge is generated representing the argument-passing operation that might be performed by `foo`.
|
||||
* This edge is going from the def-node associated with the callback to the use-node associated with the parameter `x`.
|
||||
*
|
||||
* ### Thinking in operations versus code patterns
|
||||
*
|
||||
* Treating edges as "operations" helps avoid a pitfall in which library models become overly specific to certain code patterns.
|
||||
* Consider the following two equivalent calls to `foo`:
|
||||
* ```js
|
||||
* const foo = require('foo');
|
||||
*
|
||||
* foo({
|
||||
* myMethod(x) {...}
|
||||
* });
|
||||
*
|
||||
* foo({
|
||||
* get myMethod() {
|
||||
* return function(x) {...}
|
||||
* }
|
||||
* });
|
||||
* ```
|
||||
* If `foo` calls `myMethod` on its first parameter, either of the `myMethod` implementations will be invoked.
|
||||
* And indeed, the access path `API::moduleImport("foo").getParameter(0).getMember("myMethod").getParameter(0)` correctly
|
||||
* identifies both `x` parameters.
|
||||
*
|
||||
* Observe how `getMember("myMethod")` behaves when the member is defined via a getter. When thinking in code patterns,
|
||||
* it might seem obvious that `getMember` should have obtained a reference to the getter method itself.
|
||||
* But when seeing it as an access to `myMethod` performed by the library, we can deduce that the relevant expression
|
||||
* on the client side is actually the return-value of the getter.
|
||||
*
|
||||
* Although one may think of API graphs as a tool to find certain program elements in the codebase,
|
||||
* it can lead to some situations where intuition does not match what works best in practice.
|
||||
*/
|
||||
class Node extends Impl::TApiNode {
|
||||
/**
|
||||
* Gets a data-flow node corresponding to a use of the API component represented by this node.
|
||||
* Get a data-flow node where this value may flow after entering the current codebase.
|
||||
*
|
||||
* For example, `require('fs').readFileSync` is a use of the function `readFileSync` from the
|
||||
* `fs` module, and `require('fs').readFileSync(file)` is a use of the return of that function.
|
||||
*
|
||||
* This includes indirect uses found via data flow, meaning that in
|
||||
* `f(obj.foo); function f(x) {};` both `obj.foo` and `x` are uses of the `foo` member from `obj`.
|
||||
*
|
||||
* As another example, in the assignment `exports.plusOne = (x) => x+1` the two references to
|
||||
* `x` are uses of the first parameter of `plusOne`.
|
||||
* This is similar to `asSource()` but additionally includes nodes that are transitively reachable by data flow.
|
||||
* See `asSource()` for examples.
|
||||
*/
|
||||
pragma[inline]
|
||||
DataFlow::Node getAUse() {
|
||||
exists(DataFlow::SourceNode src | Impl::use(this, src) |
|
||||
Impl::trackUseNode(src).flowsTo(result)
|
||||
)
|
||||
DataFlow::Node getAValueReachableFromSource() {
|
||||
Impl::trackUseNode(this.asSource()).flowsTo(result)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an immediate use of the API component represented by this node.
|
||||
* Get a data-flow node where this value enters the current codebase.
|
||||
*
|
||||
* For example, `require('fs').readFileSync` is a an immediate use of the `readFileSync` member
|
||||
* from the `fs` module.
|
||||
* For example:
|
||||
* ```js
|
||||
* // API::moduleImport("fs").asSource()
|
||||
* require('fs');
|
||||
*
|
||||
* Unlike `getAUse()`, this predicate only gets the immediate references, not the indirect uses
|
||||
* found via data flow. This means that in `const x = fs.readFile` only `fs.readFile` is a reference
|
||||
* to the `readFile` member of `fs`, neither `x` nor any node that `x` flows to is a reference to
|
||||
* this API component.
|
||||
* // API::moduleImport("fs").getMember("readFile").asSource()
|
||||
* require('fs').readFile;
|
||||
*
|
||||
* // API::moduleImport("fs").getMember("readFile").getReturn().asSource()
|
||||
* require('fs').readFile();
|
||||
*
|
||||
* require('fs').readFile(
|
||||
* filename,
|
||||
* // 'y' matched by API::moduleImport("fs").getMember("readFile").getParameter(1).getParameter(0).asSource()
|
||||
* y => {
|
||||
* ...
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
DataFlow::SourceNode getAnImmediateUse() { Impl::use(this, result) }
|
||||
DataFlow::SourceNode asSource() { Impl::use(this, result) }
|
||||
|
||||
/** DEPRECATED. This predicate has been renamed to `asSource`. */
|
||||
deprecated DataFlow::SourceNode getAnImmediateUse() { result = this.asSource() }
|
||||
|
||||
/** DEPRECATED. This predicate has been renamed to `getAValueReachableFromSource`. */
|
||||
deprecated DataFlow::Node getAUse() { result = this.getAValueReachableFromSource() }
|
||||
|
||||
/**
|
||||
* Gets a call to the function represented by this API component.
|
||||
*/
|
||||
CallNode getACall() { result = this.getReturn().getAnImmediateUse() }
|
||||
CallNode getACall() { result = this.getReturn().asSource() }
|
||||
|
||||
/**
|
||||
* Gets a call to the function represented by this API component,
|
||||
@@ -72,7 +177,7 @@ module API {
|
||||
/**
|
||||
* Gets a `new` call to the function represented by this API component.
|
||||
*/
|
||||
NewNode getAnInstantiation() { result = this.getInstance().getAnImmediateUse() }
|
||||
NewNode getAnInstantiation() { result = this.getInstance().asSource() }
|
||||
|
||||
/**
|
||||
* Gets an invocation (with our without `new`) to the function represented by this API component.
|
||||
@@ -80,26 +185,38 @@ module API {
|
||||
InvokeNode getAnInvocation() { result = this.getACall() or result = this.getAnInstantiation() }
|
||||
|
||||
/**
|
||||
* Gets a data-flow node corresponding to the right-hand side of a definition of the API
|
||||
* component represented by this node.
|
||||
* Get a data-flow node where this value leaves the current codebase and flows into an
|
||||
* external library (or in general, any external codebase).
|
||||
*
|
||||
* For example, in the assignment `exports.plusOne = (x) => x+1`, the function expression
|
||||
* `(x) => x+1` is the right-hand side of the definition of the member `plusOne` of
|
||||
* the enclosing module, and the expression `x+1` is the right-had side of the definition of
|
||||
* its result.
|
||||
* Concretely, this is either an argument passed to a call to external code,
|
||||
* or the right-hand side of a property write on an object flowing into such a call.
|
||||
*
|
||||
* Note that for parameters, it is the arguments flowing into that parameter that count as
|
||||
* right-hand sides of the definition, not the declaration of the parameter itself.
|
||||
* Consequently, in `require('fs').readFileSync(file)`, `file` is the right-hand
|
||||
* side of a definition of the first parameter of `readFileSync` from the `fs` module.
|
||||
* For example:
|
||||
* ```js
|
||||
* // 'x' is matched by API::moduleImport("foo").getParameter(0).asSink()
|
||||
* require('foo')(x);
|
||||
*
|
||||
* // 'x' is matched by API::moduleImport("foo").getParameter(0).getMember("prop").asSink()
|
||||
* require('foo')({
|
||||
* prop: x
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
DataFlow::Node getARhs() { Impl::rhs(this, result) }
|
||||
DataFlow::Node asSink() { Impl::rhs(this, result) }
|
||||
|
||||
/**
|
||||
* Gets a data-flow node that may interprocedurally flow to the right-hand side of a definition
|
||||
* of the API component represented by this node.
|
||||
* Get a data-flow node that transitively flows to an external library (or in general, any external codebase).
|
||||
*
|
||||
* This is similar to `asSink()` but additionally includes nodes that transitively reach a sink by data flow.
|
||||
* See `asSink()` for examples.
|
||||
*/
|
||||
DataFlow::Node getAValueReachingRhs() { result = Impl::trackDefNode(this.getARhs()) }
|
||||
DataFlow::Node getAValueReachingSink() { result = Impl::trackDefNode(this.asSink()) }
|
||||
|
||||
/** DEPRECATED. This predicate has been renamed to `asSink`. */
|
||||
deprecated DataFlow::Node getARhs() { result = this.asSink() }
|
||||
|
||||
/** DEPRECATED. This predicate has been renamed to `getAValueReachingSink`. */
|
||||
deprecated DataFlow::Node getAValueReachingRhs() { result = this.getAValueReachingSink() }
|
||||
|
||||
/**
|
||||
* Gets a node representing member `m` of this API component.
|
||||
@@ -334,7 +451,7 @@ module API {
|
||||
* In other words, the value of a use of `that` may flow into the right-hand side of a
|
||||
* definition of this node.
|
||||
*/
|
||||
predicate refersTo(Node that) { this.getARhs() = that.getAUse() }
|
||||
predicate refersTo(Node that) { this.asSink() = that.getAValueReachableFromSource() }
|
||||
|
||||
/**
|
||||
* Gets the data-flow node that gives rise to this node, if any.
|
||||
@@ -416,8 +533,9 @@ module API {
|
||||
|
||||
/** Gets a node corresponding to an import of module `m`. */
|
||||
Node moduleImport(string m) {
|
||||
result = Impl::MkModuleImport(m) or
|
||||
result = Impl::MkModuleImport(m).(Node).getMember("default")
|
||||
result = Internal::getAModuleImportRaw(m)
|
||||
or
|
||||
result = ModelOutput::getATypeNode(m, "")
|
||||
}
|
||||
|
||||
/** Gets a node corresponding to an export of module `m`. */
|
||||
@@ -427,6 +545,22 @@ module API {
|
||||
module Node {
|
||||
/** Gets a node whose type has the given qualified name. */
|
||||
Node ofType(string moduleName, string exportedName) {
|
||||
result = Internal::getANodeOfTypeRaw(moduleName, exportedName)
|
||||
or
|
||||
result = ModelOutput::getATypeNode(moduleName, exportedName)
|
||||
}
|
||||
}
|
||||
|
||||
/** Provides access to API graph nodes without taking into account types from models. */
|
||||
module Internal {
|
||||
/** Gets a node corresponding to an import of module `m` without taking into account types from models. */
|
||||
Node getAModuleImportRaw(string m) {
|
||||
result = Impl::MkModuleImport(m) or
|
||||
result = Impl::MkModuleImport(m).(Node).getMember("default")
|
||||
}
|
||||
|
||||
/** Gets a node whose type has the given qualified name, not including types from models. */
|
||||
Node getANodeOfTypeRaw(string moduleName, string exportedName) {
|
||||
result = Impl::MkTypeUse(moduleName, exportedName).(Node).getInstance()
|
||||
}
|
||||
}
|
||||
@@ -445,11 +579,17 @@ module API {
|
||||
bindingset[this]
|
||||
EntryPoint() { any() }
|
||||
|
||||
/** Gets a data-flow node that uses this entry point. */
|
||||
abstract DataFlow::SourceNode getAUse();
|
||||
/** DEPRECATED. This predicate has been renamed to `getASource`. */
|
||||
deprecated DataFlow::SourceNode getAUse() { none() }
|
||||
|
||||
/** Gets a data-flow node that defines this entry point. */
|
||||
abstract DataFlow::Node getARhs();
|
||||
/** DEPRECATED. This predicate has been renamed to `getASink`. */
|
||||
deprecated DataFlow::SourceNode getARhs() { none() }
|
||||
|
||||
/** Gets a data-flow node where a value enters the current codebase through this entry-point. */
|
||||
DataFlow::SourceNode getASource() { none() }
|
||||
|
||||
/** Gets a data-flow node where a value leaves the current codebase through this entry-point. */
|
||||
DataFlow::Node getASink() { none() }
|
||||
|
||||
/** Gets an API-node for this entry point. */
|
||||
API::Node getANode() { result = root().getASuccessor(Label::entryPoint(this)) }
|
||||
@@ -523,7 +663,14 @@ module API {
|
||||
or
|
||||
any(Type t).hasUnderlyingType(m, _)
|
||||
} or
|
||||
MkClassInstance(DataFlow::ClassNode cls) { cls = trackDefNode(_) and hasSemantics(cls) } or
|
||||
MkClassInstance(DataFlow::ClassNode cls) {
|
||||
hasSemantics(cls) and
|
||||
(
|
||||
cls = trackDefNode(_)
|
||||
or
|
||||
cls.getAnInstanceReference() = trackDefNode(_)
|
||||
)
|
||||
} or
|
||||
MkAsyncFuncResult(DataFlow::FunctionNode f) {
|
||||
f = trackDefNode(_) and f.getFunction().isAsync() and hasSemantics(f)
|
||||
} or
|
||||
@@ -567,7 +714,7 @@ module API {
|
||||
base = MkRoot() and
|
||||
exists(EntryPoint e |
|
||||
lbl = Label::entryPoint(e) and
|
||||
rhs = e.getARhs()
|
||||
rhs = e.getASink()
|
||||
)
|
||||
or
|
||||
exists(string m, string prop |
|
||||
@@ -615,16 +762,6 @@ module API {
|
||||
.getStaticMember(name, DataFlow::MemberKind::getter())
|
||||
.getAReturn()
|
||||
)
|
||||
or
|
||||
// If `new C()` escapes, generate edges to its instance members
|
||||
exists(DataFlow::ClassNode cls, string name |
|
||||
pred = cls.getAClassReference().getAnInstantiation() and
|
||||
lbl = Label::member(name)
|
||||
|
|
||||
rhs = cls.getInstanceMethod(name)
|
||||
or
|
||||
rhs = cls.getInstanceMember(name, DataFlow::MemberKind::getter()).getAReturn()
|
||||
)
|
||||
)
|
||||
or
|
||||
exists(DataFlow::ClassNode cls, string name |
|
||||
@@ -744,7 +881,7 @@ module API {
|
||||
base = MkRoot() and
|
||||
exists(EntryPoint e |
|
||||
lbl = Label::entryPoint(e) and
|
||||
ref = e.getAUse()
|
||||
ref = e.getASource()
|
||||
)
|
||||
or
|
||||
// property reads
|
||||
@@ -1113,9 +1250,13 @@ module API {
|
||||
succ = MkUse(ref)
|
||||
)
|
||||
or
|
||||
exists(DataFlow::Node rhs |
|
||||
rhs(pred, lbl, rhs) and
|
||||
exists(DataFlow::Node rhs | rhs(pred, lbl, rhs) |
|
||||
succ = MkDef(rhs)
|
||||
or
|
||||
exists(DataFlow::ClassNode cls |
|
||||
cls.getAnInstanceReference() = rhs and
|
||||
succ = MkClassInstance(cls)
|
||||
)
|
||||
)
|
||||
or
|
||||
exists(DataFlow::Node def |
|
||||
@@ -1178,8 +1319,8 @@ module API {
|
||||
API::Node callee;
|
||||
|
||||
InvokeNode() {
|
||||
this = callee.getReturn().getAnImmediateUse() or
|
||||
this = callee.getInstance().getAnImmediateUse() or
|
||||
this = callee.getReturn().asSource() or
|
||||
this = callee.getInstance().asSource() or
|
||||
this = Impl::getAPromisifiedInvocation(callee, _, _)
|
||||
}
|
||||
|
||||
@@ -1194,7 +1335,7 @@ module API {
|
||||
* Gets an API node where a RHS of the node is the `i`th argument to this call.
|
||||
*/
|
||||
pragma[noinline]
|
||||
private Node getAParameterCandidate(int i) { result.getARhs() = this.getArgument(i) }
|
||||
private Node getAParameterCandidate(int i) { result.asSink() = this.getArgument(i) }
|
||||
|
||||
/** Gets the API node for a parameter of this invocation. */
|
||||
Node getAParameter() { result = this.getParameter(_) }
|
||||
@@ -1205,13 +1346,13 @@ module API {
|
||||
/** Gets the API node for the return value of this call. */
|
||||
Node getReturn() {
|
||||
result = callee.getReturn() and
|
||||
result.getAnImmediateUse() = this
|
||||
result.asSource() = this
|
||||
}
|
||||
|
||||
/** Gets the API node for the object constructed by this invocation. */
|
||||
Node getInstance() {
|
||||
result = callee.getInstance() and
|
||||
result.getAnImmediateUse() = this
|
||||
result.asSource() = this
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1354,7 +1495,7 @@ module API {
|
||||
/** Gets the EntryPoint associated with this label. */
|
||||
API::EntryPoint getEntryPoint() { result = e }
|
||||
|
||||
override string toString() { result = "getASuccessor(Label::entryPoint(\"" + e + "\"))" }
|
||||
override string toString() { result = "entryPoint(\"" + e + "\")" }
|
||||
}
|
||||
|
||||
/** A label that gets a promised value. */
|
||||
|
||||
@@ -36,12 +36,18 @@ module ArrayTaintTracking {
|
||||
succ = call
|
||||
)
|
||||
or
|
||||
// `array.filter(x => x)` keeps the taint
|
||||
// `array.filter(x => x)` and `array.filter(x => !!x)` keeps the taint
|
||||
call.(DataFlow::MethodCallNode).getMethodName() = "filter" and
|
||||
pred = call.getReceiver() and
|
||||
succ = call and
|
||||
exists(DataFlow::FunctionNode callback | callback = call.getArgument(0).getAFunctionValue() |
|
||||
callback.getParameter(0).getALocalUse() = callback.getAReturn()
|
||||
exists(DataFlow::FunctionNode callback, DataFlow::Node param, DataFlow::Node ret |
|
||||
callback = call.getArgument(0).getAFunctionValue() and
|
||||
param = callback.getParameter(0).getALocalUse() and
|
||||
ret = callback.getAReturn()
|
||||
|
|
||||
param = ret
|
||||
or
|
||||
param = DataFlow::exprNode(ret.asExpr().(LogNotExpr).getOperand().(LogNotExpr).getOperand())
|
||||
)
|
||||
or
|
||||
// `array.reduce` with tainted value in callback
|
||||
@@ -75,7 +81,7 @@ module ArrayTaintTracking {
|
||||
succ.(DataFlow::SourceNode).getAMethodCall("splice") = call
|
||||
or
|
||||
// `e = array.pop()`, `e = array.shift()`, or similar: if `array` is tainted, then so is `e`.
|
||||
call.(DataFlow::MethodCallNode).calls(pred, ["pop", "shift", "slice", "splice"]) and
|
||||
call.(DataFlow::MethodCallNode).calls(pred, ["pop", "shift", "slice", "splice", "at"]) and
|
||||
succ = call
|
||||
or
|
||||
// `e = Array.from(x)`: if `x` is tainted, then so is `e`.
|
||||
@@ -199,13 +205,13 @@ private module ArrayDataFlow {
|
||||
}
|
||||
|
||||
/**
|
||||
* A step for retrieving an element from an array using `.pop()` or `.shift()`.
|
||||
* A step for retrieving an element from an array using `.pop()`, `.shift()`, or `.at()`.
|
||||
* E.g. `array.pop()`.
|
||||
*/
|
||||
private class ArrayPopStep extends DataFlow::SharedFlowStep {
|
||||
override predicate loadStep(DataFlow::Node obj, DataFlow::Node element, string prop) {
|
||||
exists(DataFlow::MethodCallNode call |
|
||||
call.getMethodName() = ["pop", "shift"] and
|
||||
call.getMethodName() = ["pop", "shift", "at"] and
|
||||
prop = arrayElement() and
|
||||
obj = call.getReceiver() and
|
||||
element = call
|
||||
@@ -255,14 +261,12 @@ private module ArrayDataFlow {
|
||||
/**
|
||||
* A step for creating an array and storing the elements in the array.
|
||||
*/
|
||||
private class ArrayCreationStep extends DataFlow::SharedFlowStep {
|
||||
private class ArrayCreationStep extends PreCallGraphStep {
|
||||
override predicate storeStep(DataFlow::Node element, DataFlow::SourceNode obj, string prop) {
|
||||
exists(DataFlow::ArrayCreationNode array, int i |
|
||||
element = array.getElement(i) and
|
||||
obj = array and
|
||||
if array = any(PromiseAllCreation c).getArrayNode()
|
||||
then prop = arrayElement(i)
|
||||
else prop = arrayElement()
|
||||
prop = arrayElement(i)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -340,6 +344,14 @@ private module ArrayLibraries {
|
||||
result = DataFlow::globalVarRef("Array").getAMemberCall("from")
|
||||
or
|
||||
result = DataFlow::moduleImport("array-from").getACall()
|
||||
or
|
||||
// Array.prototype.slice.call acts the same as Array.from, and is sometimes used with e.g. the arguments object.
|
||||
result =
|
||||
DataFlow::globalVarRef("Array")
|
||||
.getAPropertyRead("prototype")
|
||||
.getAPropertyRead("slice")
|
||||
.getAMethodCall("call") and
|
||||
result.getNumArgument() = 1
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -146,7 +146,7 @@ class BasicBlock extends @cfg_node, NodeInStmtContainer {
|
||||
/** Holds if this basic block uses variable `v` in its `i`th node `u`. */
|
||||
predicate useAt(int i, Variable v, VarUse u) { useAt(this, i, v, u) }
|
||||
|
||||
/** Holds if this basic block defines variable `v` in its `i`th node `u`. */
|
||||
/** Holds if this basic block defines variable `v` in its `i`th node `d`. */
|
||||
predicate defAt(int i, Variable v, VarDef d) { defAt(this, i, v, d) }
|
||||
|
||||
/**
|
||||
|
||||
@@ -75,7 +75,7 @@ module CharacterEscapes {
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a character in `n` that is preceded by a single useless backslash, resulting in a likely regular expression mistake explained by `mistake`.
|
||||
* Gets a character in `src` 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`.
|
||||
*/
|
||||
|
||||
@@ -172,7 +172,7 @@ class ClassDefinition extends @class_definition, ClassOrInterface, AST::ValueNod
|
||||
/** Gets the expression denoting the super class of the defined class, if any. */
|
||||
override Expr getSuperClass() { result = this.getChildExpr(1) }
|
||||
|
||||
/** Gets the `n`th type from the `implements` clause of this class, starting at 0. */
|
||||
/** Gets the `i`th type from the `implements` clause of this class, starting at 0. */
|
||||
override TypeExpr getSuperInterface(int i) {
|
||||
// AST indices for super interfaces: -1, -4, -7, ...
|
||||
exists(int astIndex | typeexprs(result, _, this, astIndex, _) |
|
||||
@@ -493,6 +493,9 @@ class MemberDeclaration extends @property, Documentable {
|
||||
*/
|
||||
predicate isStatic() { is_static(this) }
|
||||
|
||||
/** Gets a boolean indicating if this member is static. */
|
||||
boolean getStaticAsBool() { if this.isStatic() then result = true else result = false }
|
||||
|
||||
/**
|
||||
* Holds if this member is abstract.
|
||||
*
|
||||
@@ -694,10 +697,10 @@ class MethodDeclaration extends MemberDeclaration {
|
||||
* the overload index is defined as if only one of them was concrete.
|
||||
*/
|
||||
int getOverloadIndex() {
|
||||
exists(ClassOrInterface type, string name |
|
||||
exists(ClassOrInterface type, string name, boolean static |
|
||||
this =
|
||||
rank[result + 1](MethodDeclaration method, int i |
|
||||
methodDeclaredInType(type, name, i, method)
|
||||
methodDeclaredInType(type, name, static, i, method)
|
||||
|
|
||||
method order by i
|
||||
)
|
||||
@@ -718,10 +721,11 @@ class MethodDeclaration extends MemberDeclaration {
|
||||
* Holds if the `index`th member of `type` is `method`, which has the given `name`.
|
||||
*/
|
||||
private predicate methodDeclaredInType(
|
||||
ClassOrInterface type, string name, int index, MethodDeclaration method
|
||||
ClassOrInterface type, string name, boolean static, int index, MethodDeclaration method
|
||||
) {
|
||||
not method instanceof ConstructorDeclaration and // distinguish methods named "constructor" from the constructor
|
||||
type.getMemberByIndex(index) = method and
|
||||
static = method.getStaticAsBool() and
|
||||
method.getName() = name
|
||||
}
|
||||
|
||||
|
||||
@@ -9,118 +9,6 @@ private import semmle.javascript.dataflow.internal.StepSummary
|
||||
private import semmle.javascript.dataflow.internal.PreCallGraphStep
|
||||
private import DataFlow::PseudoProperties
|
||||
|
||||
/**
|
||||
* DEPRECATED. Exists only to support other deprecated elements.
|
||||
*
|
||||
* Type-tracking now automatically determines the set of pseudo-properties to include
|
||||
* ased on which properties are contributed by `SharedTaintStep`s.
|
||||
*/
|
||||
deprecated private class PseudoProperty extends string {
|
||||
PseudoProperty() {
|
||||
this = [arrayLikeElement(), "1"] or // the "1" is required for the `ForOfStep`.
|
||||
this =
|
||||
[
|
||||
mapValue(any(DataFlow::CallNode c | c.getCalleeName() = "set").getArgument(0)),
|
||||
mapValueAll()
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* DEPRECATED. Use `SharedFlowStep` or `SharedTaintTrackingStep` instead.
|
||||
*/
|
||||
abstract deprecated class CollectionFlowStep extends DataFlow::AdditionalFlowStep {
|
||||
final override predicate step(DataFlow::Node pred, DataFlow::Node succ) { none() }
|
||||
|
||||
final override predicate step(
|
||||
DataFlow::Node p, DataFlow::Node s, DataFlow::FlowLabel pl, DataFlow::FlowLabel sl
|
||||
) {
|
||||
none()
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if the property `prop` of the object `pred` should be loaded into `succ`.
|
||||
*/
|
||||
predicate load(DataFlow::Node pred, DataFlow::Node succ, PseudoProperty prop) { none() }
|
||||
|
||||
final override predicate loadStep(DataFlow::Node pred, DataFlow::Node succ, string prop) {
|
||||
this.load(pred, succ, prop)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `pred` should be stored in the object `succ` under the property `prop`.
|
||||
*/
|
||||
predicate store(DataFlow::Node pred, DataFlow::SourceNode succ, PseudoProperty prop) { none() }
|
||||
|
||||
final override predicate storeStep(DataFlow::Node pred, DataFlow::SourceNode succ, string prop) {
|
||||
this.store(pred, succ, prop)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if the property `prop` should be copied from the object `pred` to the object `succ`.
|
||||
*/
|
||||
predicate loadStore(DataFlow::Node pred, DataFlow::Node succ, PseudoProperty prop) { none() }
|
||||
|
||||
final override predicate loadStoreStep(DataFlow::Node pred, DataFlow::Node succ, string prop) {
|
||||
this.loadStore(pred, succ, prop, prop)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if the property `loadProp` should be copied from the object `pred` to the property `storeProp` of object `succ`.
|
||||
*/
|
||||
predicate loadStore(
|
||||
DataFlow::Node pred, DataFlow::Node succ, PseudoProperty loadProp, PseudoProperty storeProp
|
||||
) {
|
||||
none()
|
||||
}
|
||||
|
||||
final override predicate loadStoreStep(
|
||||
DataFlow::Node pred, DataFlow::Node succ, string loadProp, string storeProp
|
||||
) {
|
||||
this.loadStore(pred, succ, loadProp, storeProp)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* DEPRECATED. These steps are now included in the default type tracking steps,
|
||||
* in most cases one can simply use those instead.
|
||||
*/
|
||||
deprecated module CollectionsTypeTracking {
|
||||
/**
|
||||
* Gets the result from a single step through a collection, from `pred` to `result` summarized by `summary`.
|
||||
*/
|
||||
pragma[inline]
|
||||
DataFlow::SourceNode collectionStep(DataFlow::Node pred, StepSummary summary) {
|
||||
exists(PseudoProperty field |
|
||||
summary = LoadStep(field) and
|
||||
DataFlow::SharedTypeTrackingStep::loadStep(pred, result, field) and
|
||||
not field = mapValueUnknownKey() // prune unknown reads in type-tracking
|
||||
or
|
||||
summary = StoreStep(field) and
|
||||
DataFlow::SharedTypeTrackingStep::storeStep(pred, result, field)
|
||||
or
|
||||
summary = CopyStep(field) and
|
||||
DataFlow::SharedTypeTrackingStep::loadStoreStep(pred, result, field)
|
||||
or
|
||||
exists(PseudoProperty toField | summary = LoadStoreStep(field, toField) |
|
||||
DataFlow::SharedTypeTrackingStep::loadStoreStep(pred, result, field, toField)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the result from a single step through a collection, from `pred` with tracker `t2` to `result` with tracker `t`.
|
||||
*/
|
||||
pragma[inline]
|
||||
DataFlow::SourceNode collectionStep(
|
||||
DataFlow::SourceNode pred, DataFlow::TypeTracker t, DataFlow::TypeTracker t2
|
||||
) {
|
||||
exists(DataFlow::Node mid, StepSummary summary | pred.flowsTo(mid) and t = t2.append(summary) |
|
||||
result = collectionStep(mid, summary)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A module for data-flow steps related standard library collection implementations.
|
||||
*/
|
||||
|
||||
@@ -54,7 +54,7 @@ private predicate hasNamedExports(ES2015Module mod) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if this module contains a `default` export.
|
||||
* Holds if this module contains a default export.
|
||||
*/
|
||||
private predicate hasDefaultExport(ES2015Module mod) {
|
||||
// export default foo;
|
||||
@@ -337,7 +337,7 @@ class BulkReExportDeclaration extends ReExportDeclaration, @export_all_declarati
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if the given bulk export should not re-export `name` because there is an explicit export
|
||||
* Holds if the given bulk export `reExport` should not re-export `name` because there is an explicit export
|
||||
* of that name in the same module.
|
||||
*
|
||||
* At compile time, shadowing works across declaration spaces.
|
||||
|
||||
@@ -167,7 +167,7 @@ class Expr extends @expr, ExprOrStmt, ExprOrType, AST::ValueNode {
|
||||
/**
|
||||
* Holds if this expression may refer to the initial value of parameter `p`.
|
||||
*/
|
||||
predicate mayReferToParameter(Parameter p) { this.flow().mayReferToParameter(p) }
|
||||
predicate mayReferToParameter(Parameter p) { DataFlow::parameterNode(p).flowsToExpr(this) }
|
||||
|
||||
/**
|
||||
* Gets the static type of this expression, as determined by the TypeScript type system.
|
||||
|
||||
@@ -175,6 +175,15 @@ class Folder extends Container, @folder {
|
||||
result.getExtension() = extension
|
||||
}
|
||||
|
||||
/** Like `getFile` except `d.ts` is treated as a single extension. */
|
||||
private File getFileLongExtension(string stem, string extension) {
|
||||
not (stem.matches("%.d") and extension = "ts") and
|
||||
result = this.getFile(stem, extension)
|
||||
or
|
||||
extension = "d.ts" and
|
||||
result = this.getFile(stem + ".d", "ts")
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the file in this folder that has the given `stem` and any of the supported JavaScript extensions.
|
||||
*
|
||||
@@ -188,7 +197,11 @@ class Folder extends Container, @folder {
|
||||
*/
|
||||
File getJavaScriptFile(string stem) {
|
||||
result =
|
||||
min(int p, string ext | p = getFileExtensionPriority(ext) | this.getFile(stem, ext) order by p)
|
||||
min(int p, string ext |
|
||||
p = getFileExtensionPriority(ext)
|
||||
|
|
||||
this.getFileLongExtension(stem, ext) order by p
|
||||
)
|
||||
}
|
||||
|
||||
/** Gets a subfolder contained in this folder. */
|
||||
@@ -221,8 +234,6 @@ class File extends Container, @file {
|
||||
/** Gets a toplevel piece of JavaScript code in this file. */
|
||||
TopLevel getATopLevel() { result.getFile() = this }
|
||||
|
||||
override string toString() { result = Container.super.toString() }
|
||||
|
||||
/** Gets the URL of this file. */
|
||||
override string getURL() { result = "file://" + this.getAbsolutePath() + ":0:0:0:0" }
|
||||
|
||||
|
||||
@@ -178,7 +178,7 @@ predicate isGeneratedFileName(File f) {
|
||||
predicate isGenerated(TopLevel tl) {
|
||||
tl.isMinified() or
|
||||
isBundle(tl) or
|
||||
tl instanceof GWTGeneratedTopLevel or
|
||||
tl instanceof GwtGeneratedTopLevel or
|
||||
tl instanceof DartGeneratedTopLevel or
|
||||
exists(GeneratedCodeMarkerComment gcmc | tl = gcmc.getTopLevel()) or
|
||||
hasManyInvocations(tl) or
|
||||
|
||||
@@ -54,7 +54,9 @@ class JsonValue extends @json_value, Locatable {
|
||||
int getIntValue() { result = this.(JsonNumber).getValue().toInt() }
|
||||
|
||||
/** If this is a boolean constant, gets its boolean value. */
|
||||
boolean getBooleanValue() { result.toString() = this.(JsonBoolean).getValue() }
|
||||
boolean getBooleanValue() {
|
||||
result.toString() = this.(JsonBoolean).getValue() and result = [true, false]
|
||||
}
|
||||
|
||||
override string getAPrimaryQlClass() { result = "JsonValue" }
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ private class PlainJsonParserCall extends JsonParserCall {
|
||||
callee =
|
||||
DataFlow::moduleMember(["json3", "json5", "flatted", "teleport-javascript", "json-cycle"],
|
||||
"parse") or
|
||||
callee = API::moduleImport("replicator").getInstance().getMember("decode").getAnImmediateUse() or
|
||||
callee = API::moduleImport("replicator").getInstance().getMember("decode").asSource() or
|
||||
callee = DataFlow::moduleImport("parse-json") or
|
||||
callee = DataFlow::moduleImport("json-parse-better-errors") or
|
||||
callee = DataFlow::moduleImport("json-safe-parse") or
|
||||
|
||||
@@ -134,7 +134,7 @@ module JsonSchema {
|
||||
.ref()
|
||||
.getMember(["addSchema", "validate", "compile", "compileAsync"])
|
||||
.getParameter(0)
|
||||
.getARhs()
|
||||
.asSink()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -184,7 +184,7 @@ module JsonSchema {
|
||||
override boolean getPolarity() { none() }
|
||||
|
||||
override DataFlow::Node getAValidationResultAccess(boolean polarity) {
|
||||
result = this.getReturn().getMember("error").getAnImmediateUse() and
|
||||
result = this.getReturn().getMember("error").asSource() and
|
||||
polarity = false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ class JsonStringifyCall extends DataFlow::CallNode {
|
||||
callee =
|
||||
DataFlow::moduleMember(["json3", "json5", "flatted", "teleport-javascript", "json-cycle"],
|
||||
"stringify") or
|
||||
callee = API::moduleImport("replicator").getInstance().getMember("encode").getAnImmediateUse() or
|
||||
callee = API::moduleImport("replicator").getInstance().getMember("encode").asSource() or
|
||||
callee =
|
||||
DataFlow::moduleImport([
|
||||
"json-stringify-safe", "json-stable-stringify", "stringify-object",
|
||||
@@ -43,7 +43,7 @@ class JsonStringifyCall extends DataFlow::CallNode {
|
||||
/**
|
||||
* A taint step through the [`json2csv`](https://www.npmjs.com/package/json2csv) library.
|
||||
*/
|
||||
class JSON2CSVTaintStep extends TaintTracking::SharedTaintStep {
|
||||
class Json2CsvTaintStep extends TaintTracking::SharedTaintStep {
|
||||
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
exists(API::CallNode call |
|
||||
call =
|
||||
@@ -59,6 +59,9 @@ class JSON2CSVTaintStep extends TaintTracking::SharedTaintStep {
|
||||
}
|
||||
}
|
||||
|
||||
/** DEPRECATED: Alias for Json2CsvTaintStep */
|
||||
deprecated class JSON2CSVTaintStep = Json2CsvTaintStep;
|
||||
|
||||
/**
|
||||
* A step through the [`prettyjson`](https://www.npmjs.com/package/prettyjson) library.
|
||||
* This is not quite a `JSON.stringify` call, as it e.g. does not wrap keys in double quotes.
|
||||
|
||||
@@ -229,10 +229,10 @@ module MembershipCandidate {
|
||||
membersNode = inExpr.getRightOperand()
|
||||
)
|
||||
or
|
||||
exists(MethodCallExpr hasOwn |
|
||||
this = hasOwn.getArgument(0).flow() and
|
||||
test = hasOwn and
|
||||
hasOwn.calls(membersNode, "hasOwnProperty")
|
||||
exists(HasOwnPropertyCall hasOwn |
|
||||
this = hasOwn.getProperty() and
|
||||
test = hasOwn.asExpr() and
|
||||
membersNode = hasOwn.getObject().asExpr()
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -50,8 +50,23 @@ class PackageJson extends JsonObject {
|
||||
/** Gets a file for this package. */
|
||||
string getAFile() { result = this.getFiles().getElementStringValue(_) }
|
||||
|
||||
/** Gets the main module of this package. */
|
||||
string getMain() { result = MainModulePath::of(this).getValue() }
|
||||
/**
|
||||
* Gets the main module of this package.
|
||||
*
|
||||
* This can be given by the `main` or `module` property, or via the
|
||||
* `exports` property with the relative path `"."`.
|
||||
*/
|
||||
string getMain() { result = this.getExportedPath(".") }
|
||||
|
||||
/**
|
||||
* Gets the path to the file exported with the given relative path.
|
||||
*
|
||||
* This can be given by the `exports` property, but also considers `main` and
|
||||
* `module` paths to be exported under the relative path `"."`.
|
||||
*/
|
||||
string getExportedPath(string relativePath) {
|
||||
result = MainModulePath::of(this, relativePath).getValue()
|
||||
}
|
||||
|
||||
/** Gets the path of a command defined for this package. */
|
||||
string getBin(string cmd) {
|
||||
@@ -153,18 +168,24 @@ class PackageJson extends JsonObject {
|
||||
JsonArray getCPUs() { result = this.getPropValue("cpu") }
|
||||
|
||||
/** Gets a platform supported by this package. */
|
||||
string getWhitelistedCPU() {
|
||||
string getWhitelistedCpu() {
|
||||
result = this.getCPUs().getElementStringValue(_) and
|
||||
not result.matches("!%")
|
||||
}
|
||||
|
||||
/** DEPRECATED: Alias for getWhitelistedCpu */
|
||||
deprecated string getWhitelistedCPU() { result = this.getWhitelistedCpu() }
|
||||
|
||||
/** Gets a platform not supported by this package. */
|
||||
string getBlacklistedCPU() {
|
||||
string getBlacklistedCpu() {
|
||||
exists(string str | str = this.getCPUs().getElementStringValue(_) |
|
||||
result = str.regexpCapture("!(.*)", 1)
|
||||
)
|
||||
}
|
||||
|
||||
/** DEPRECATED: Alias for getBlacklistedCpu */
|
||||
deprecated string getBlacklistedCPU() { result = this.getBlacklistedCpu() }
|
||||
|
||||
/** Holds if this package prefers to be installed globally. */
|
||||
predicate isPreferGlobal() { this.getPropValue("preferGlobal").(JsonBoolean).getValue() = "true" }
|
||||
|
||||
@@ -180,6 +201,47 @@ class PackageJson extends JsonObject {
|
||||
Module getMainModule() {
|
||||
result = min(Module m, int prio | m.getFile() = resolveMainModule(this, prio) | m order by prio)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the module exported under the given relative path.
|
||||
*
|
||||
* The main module is considered exported under the path `"."`.
|
||||
*/
|
||||
Module getExportedModule(string relativePath) {
|
||||
relativePath = "." and
|
||||
result = this.getMainModule()
|
||||
or
|
||||
result.getFile() = MainModulePath::of(this, relativePath).resolve()
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the `types` or `typings` field of this package.
|
||||
*/
|
||||
string getTypings() { result = this.getPropStringValue(["types", "typings"]) }
|
||||
|
||||
/**
|
||||
* Gets the file containing the typings of this package, which can either be from the `types` or
|
||||
* `typings` field, or derived from the `main` or `module` fields.
|
||||
*/
|
||||
File getTypingsFile() {
|
||||
result =
|
||||
TypingsModulePathString::of(this).resolve(this.getFile().getParentContainer()).getContainer()
|
||||
or
|
||||
not exists(TypingsModulePathString::of(this)) and
|
||||
exists(File mainFile |
|
||||
mainFile = this.getMainModule().getFile() and
|
||||
result =
|
||||
mainFile
|
||||
.getParentContainer()
|
||||
.getFile(mainFile.getStem().regexpReplaceAll("\\.d$", "") + ".d.ts")
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the module containing the typings of this package, which can either be from the `types` or
|
||||
* `typings` field, or derived from the `main` or `module` fields.
|
||||
*/
|
||||
Module getTypingsModule() { result.getFile() = this.getTypingsFile() }
|
||||
}
|
||||
|
||||
/** DEPRECATED: Alias for PackageJson */
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import javascript
|
||||
private import NodeModuleResolutionImpl
|
||||
private import semmle.javascript.DynamicPropertyAccess as DynamicPropertyAccess
|
||||
private import semmle.javascript.internal.CachedStages
|
||||
|
||||
/**
|
||||
* A Node.js module.
|
||||
@@ -113,6 +114,7 @@ class NodeModule extends Module {
|
||||
}
|
||||
|
||||
override DataFlow::Node getABulkExportedNode() {
|
||||
Stages::Imports::ref() and
|
||||
exists(DataFlow::PropWrite write |
|
||||
write.getBase().asExpr() = this.getModuleVariable().getAnAccess() and
|
||||
write.getPropertyName() = "exports" and
|
||||
|
||||
@@ -33,6 +33,8 @@ int getFileExtensionPriority(string ext) {
|
||||
ext = "json" and result = 8
|
||||
or
|
||||
ext = "node" and result = 9
|
||||
or
|
||||
ext = "d.ts" and result = 10
|
||||
}
|
||||
|
||||
int prioritiesPerCandidate() { result = 3 * (numberOfExtensions() + 1) }
|
||||
@@ -88,10 +90,11 @@ bindingset[name]
|
||||
private string getStem(string name) { result = name.regexpCapture("(.+?)(?:\\.([^.]+))?", 1) }
|
||||
|
||||
/**
|
||||
* Gets the main module described by `pkg` with the given `priority`.
|
||||
* Gets a file that a main module from `pkg` exported as `mainPath` with the given `priority`.
|
||||
* `mainPath` is "." if it's the main module of the package.
|
||||
*/
|
||||
File resolveMainModule(PackageJson pkg, int priority) {
|
||||
exists(PathExpr main | main = MainModulePath::of(pkg) |
|
||||
private File resolveMainPath(PackageJson pkg, string mainPath, int priority) {
|
||||
exists(PathExpr main | main = MainModulePath::of(pkg, mainPath) |
|
||||
result = main.resolve() and priority = 0
|
||||
or
|
||||
result = tryExtensions(main.resolve(), "index", priority)
|
||||
@@ -100,6 +103,26 @@ File resolveMainModule(PackageJson pkg, int priority) {
|
||||
exists(int n | n = main.getNumComponent() |
|
||||
result = tryExtensions(main.resolveUpTo(n - 1), getStem(main.getComponent(n - 1)), priority)
|
||||
)
|
||||
or
|
||||
// assuming the files get moved from one dir to another during compilation:
|
||||
not exists(main.resolve()) and // didn't resolve
|
||||
count(int i, string comp | comp = main.getComponent(i) and not comp = "." | i) = 2 and // is down one folder
|
||||
exists(Folder subFolder | subFolder = pkg.getFile().getParentContainer().getAFolder() |
|
||||
// is in one folder below the package.json, and has the right basename
|
||||
result =
|
||||
tryExtensions(subFolder, getStem(main.getComponent(main.getNumComponent() - 1)),
|
||||
priority - 999) // very high priority, to make sure everything else is tried first
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the main module described by `pkg` with the given `priority`.
|
||||
*/
|
||||
File resolveMainModule(PackageJson pkg, int priority) {
|
||||
exists(int subPriority, string mainPath |
|
||||
result = resolveMainPath(pkg, mainPath, subPriority) and
|
||||
if mainPath = "." then subPriority = priority else priority = subPriority + 1000
|
||||
)
|
||||
or
|
||||
exists(Folder folder, Folder child |
|
||||
@@ -140,17 +163,28 @@ File resolveMainModule(PackageJson pkg, int priority) {
|
||||
private string getASrcFolderName() { result = ["ts", "js", "src", "lib"] }
|
||||
|
||||
/**
|
||||
* A JSON string in a `package.json` file specifying the path of the main
|
||||
* module of the package.
|
||||
* A JSON string in a `package.json` file specifying the path of one of the exported
|
||||
* modules of the package.
|
||||
*/
|
||||
class MainModulePath extends PathExpr, @json_string {
|
||||
PackageJson pkg;
|
||||
|
||||
MainModulePath() { this = pkg.getPropValue(["main", "module"]) }
|
||||
MainModulePath() {
|
||||
this = pkg.getPropValue(["main", "module"])
|
||||
or
|
||||
this = getAPartOfExportsSection(pkg)
|
||||
}
|
||||
|
||||
/** Gets the `package.json` file in which this path occurs. */
|
||||
PackageJson getPackageJson() { result = pkg }
|
||||
|
||||
/** Gets the relative path under which this is exported, usually starting with a `.`. */
|
||||
string getRelativePath() {
|
||||
result = getExportRelativePath(this)
|
||||
or
|
||||
not exists(getExportRelativePath(this)) and result = "."
|
||||
}
|
||||
|
||||
/** DEPRECATED: Alias for getPackageJson */
|
||||
deprecated PackageJSON getPackageJSON() { result = getPackageJson() }
|
||||
|
||||
@@ -162,8 +196,38 @@ class MainModulePath extends PathExpr, @json_string {
|
||||
}
|
||||
}
|
||||
|
||||
private JsonValue getAPartOfExportsSection(PackageJson pkg) {
|
||||
result = pkg.getPropValue("exports")
|
||||
or
|
||||
result = getAPartOfExportsSection(pkg).getPropValue(_)
|
||||
}
|
||||
|
||||
/** Gets the text of one of the conditions or paths enclosing the given `part` of an `exports` section. */
|
||||
private string getAnEnclosingExportProperty(JsonValue part) {
|
||||
exists(JsonObject parent, string prop |
|
||||
parent = getAPartOfExportsSection(_) and
|
||||
part = parent.getPropValue(prop)
|
||||
|
|
||||
result = prop
|
||||
or
|
||||
result = getAnEnclosingExportProperty(parent)
|
||||
)
|
||||
}
|
||||
|
||||
private string getExportRelativePath(JsonValue part) {
|
||||
result = getAnEnclosingExportProperty(part) and
|
||||
result.matches(".%")
|
||||
}
|
||||
|
||||
module MainModulePath {
|
||||
MainModulePath of(PackageJson pkg) { result.getPackageJson() = pkg }
|
||||
/** Gets the path to the main entry point of `pkg`. */
|
||||
MainModulePath of(PackageJson pkg) { result = of(pkg, ".") }
|
||||
|
||||
/** Gets the path to the file exported from `pkg` as `relativePath`. */
|
||||
MainModulePath of(PackageJson pkg, string relativePath) {
|
||||
result.getPackageJson() = pkg and
|
||||
result.getRelativePath() = relativePath
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -176,7 +240,7 @@ private class FilesPath extends PathExpr, @json_string {
|
||||
|
||||
FilesPath() {
|
||||
this = pkg.getPropValue("files").(JsonArray).getElementValue(_) and
|
||||
not exists(MainModulePath::of(pkg))
|
||||
not exists(MainModulePath::of(pkg, _))
|
||||
}
|
||||
|
||||
/** Gets the `package.json` file in which this path occurs. */
|
||||
@@ -196,3 +260,29 @@ private class FilesPath extends PathExpr, @json_string {
|
||||
private module FilesPath {
|
||||
FilesPath of(PackageJson pkg) { result.getPackageJson() = pkg }
|
||||
}
|
||||
|
||||
/**
|
||||
* A JSON string in a `package.json` file specifying the path of the
|
||||
* TypeScript typings entry point.
|
||||
*/
|
||||
class TypingsModulePathString extends PathString {
|
||||
PackageJson pkg;
|
||||
|
||||
TypingsModulePathString() {
|
||||
this = pkg.getTypings()
|
||||
or
|
||||
not exists(pkg.getTypings()) and
|
||||
this = pkg.getMain().regexpReplaceAll("\\.[mc]?js$", ".d.ts")
|
||||
}
|
||||
|
||||
/** Gets the `package.json` file containing this path. */
|
||||
PackageJson getPackageJson() { result = pkg }
|
||||
|
||||
override Folder getARootFolder() { result = pkg.getFile().getParentContainer() }
|
||||
}
|
||||
|
||||
/** Companion module to the `TypingsModulePathString` class. */
|
||||
module TypingsModulePathString {
|
||||
/** Get the typings path for the given `package.json` file. */
|
||||
TypingsModulePathString of(PackageJson pkg) { result.getPackageJson() = pkg }
|
||||
}
|
||||
|
||||
@@ -11,36 +11,14 @@ private import semmle.javascript.internal.CachedStages
|
||||
* Gets a parameter that is a library input to a top-level package.
|
||||
*/
|
||||
cached
|
||||
DataFlow::SourceNode getALibraryInputParameter() {
|
||||
DataFlow::Node getALibraryInputParameter() {
|
||||
Stages::Taint::ref() and
|
||||
exists(int bound, DataFlow::FunctionNode func |
|
||||
func = getAValueExportedByPackage().getABoundFunctionValue(bound)
|
||||
|
|
||||
result = func.getParameter(any(int arg | arg >= bound))
|
||||
or
|
||||
result = getAnArgumentsRead(func.getFunction())
|
||||
)
|
||||
}
|
||||
|
||||
private DataFlow::SourceNode getAnArgumentsRead(Function func) {
|
||||
exists(DataFlow::PropRead read |
|
||||
not read.getPropertyName() = "length" and
|
||||
result = read
|
||||
|
|
||||
read.getBase() = func.getArgumentsVariable().getAnAccess().flow()
|
||||
or
|
||||
exists(DataFlow::MethodCallNode call |
|
||||
call =
|
||||
DataFlow::globalVarRef("Array")
|
||||
.getAPropertyRead("prototype")
|
||||
.getAPropertyRead("slice")
|
||||
.getAMethodCall("call")
|
||||
or
|
||||
call = DataFlow::globalVarRef("Array").getAMethodCall("from")
|
||||
|
|
||||
call.getArgument(0) = func.getArgumentsVariable().getAnAccess().flow() and
|
||||
call.flowsTo(read.getBase())
|
||||
)
|
||||
result = func.getFunction().getArgumentsVariable().getAnAccess().flow()
|
||||
)
|
||||
}
|
||||
|
||||
@@ -52,7 +30,7 @@ private import NodeModuleResolutionImpl as NodeModule
|
||||
private DataFlow::Node getAValueExportedByPackage() {
|
||||
// The base case, an export from a named `package.json` file.
|
||||
result =
|
||||
getAnExportFromModule(any(PackageJson pack | exists(pack.getPackageName())).getMainModule())
|
||||
getAnExportFromModule(any(PackageJson pack | exists(pack.getPackageName())).getExportedModule(_))
|
||||
or
|
||||
// module.exports.bar.baz = result;
|
||||
exists(DataFlow::PropWrite write |
|
||||
@@ -74,6 +52,16 @@ private DataFlow::Node getAValueExportedByPackage() {
|
||||
not isPrivateMethodDeclaration(result)
|
||||
)
|
||||
or
|
||||
// module.exports.foo = function () {
|
||||
// return new Foo(); // <- result
|
||||
// };
|
||||
exists(DataFlow::FunctionNode func, DataFlow::NewNode inst, DataFlow::ClassNode clz |
|
||||
func = getAValueExportedByPackage().getALocalSource() and inst = unique( | | func.getAReturn())
|
||||
|
|
||||
clz.getAnInstanceReference() = inst and
|
||||
result = clz.getAnInstanceMember(_)
|
||||
)
|
||||
or
|
||||
result = getAValueExportedByPackage().getALocalSource()
|
||||
or
|
||||
// Nested property reads.
|
||||
|
||||
@@ -180,7 +180,7 @@ private Path resolveUpTo(PathString p, int n, Folder root, boolean inTS) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the `i`th component of the path `str`, where `base` is the resolved path one level up.
|
||||
* Gets the `n`th component of the path `str`, where `base` is the resolved path one level up.
|
||||
* Supports that the root directory might be compiled output from TypeScript.
|
||||
* `inTS` is true if the result is TypeScript that is compiled into the path specified by `str`.
|
||||
*/
|
||||
@@ -227,7 +227,7 @@ private module TypeScriptOutDir {
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the `outDir` option from a tsconfig file from the folder `parent`.
|
||||
* Gets the "outDir" option from a `tsconfig` file from the folder `parent`.
|
||||
*/
|
||||
private string getOutDir(JsonObject tsconfig, Folder parent) {
|
||||
tsconfig.getFile().getBaseName().regexpMatch("tsconfig.*\\.json") and
|
||||
|
||||
@@ -36,7 +36,7 @@ private predicate isNotNeeded(Locatable el) {
|
||||
el.getLocation().getStartLine() = 0 and
|
||||
el.getLocation().getStartColumn() = 0
|
||||
or
|
||||
// relaxing aggresive type inference.
|
||||
// relaxing aggressive type inference.
|
||||
none()
|
||||
}
|
||||
|
||||
@@ -64,8 +64,8 @@ private newtype TPrintAstNode =
|
||||
// JSON
|
||||
TJsonNode(JsonValue value) { shouldPrint(value, _) and not isNotNeeded(value) } or
|
||||
// YAML
|
||||
TYamlNode(YAMLNode n) { shouldPrint(n, _) and not isNotNeeded(n) } or
|
||||
TYamlMappingNode(YAMLMapping mapping, int i) {
|
||||
TYamlNode(YamlNode n) { shouldPrint(n, _) and not isNotNeeded(n) } or
|
||||
TYamlMappingNode(YamlMapping mapping, int i) {
|
||||
shouldPrint(mapping, _) and not isNotNeeded(mapping) and exists(mapping.getKeyNode(i))
|
||||
} or
|
||||
// HTML
|
||||
@@ -195,7 +195,7 @@ private module PrintJavaScript {
|
||||
* Gets the `i`th child of `element`.
|
||||
* Can be overridden in subclasses to get more specific behavior for `getChild()`.
|
||||
*/
|
||||
AstNode getChildNode(int childIndex) { result = getLocationSortedChild(element, childIndex) }
|
||||
AstNode getChildNode(int i) { result = getLocationSortedChild(element, i) }
|
||||
}
|
||||
|
||||
/** Provides predicates for pretty printing `AstNode`s. */
|
||||
@@ -628,7 +628,7 @@ module PrintYaml {
|
||||
* A print node representing a YAML value in a .yml file.
|
||||
*/
|
||||
class YamlNodeNode extends PrintAstNode, TYamlNode {
|
||||
YAMLNode node;
|
||||
YamlNode node;
|
||||
|
||||
YamlNodeNode() { this = TYamlNode(node) }
|
||||
|
||||
@@ -639,10 +639,10 @@ module PrintYaml {
|
||||
/**
|
||||
* Gets the `YAMLNode` represented by this node.
|
||||
*/
|
||||
final YAMLNode getValue() { result = node }
|
||||
final YamlNode getValue() { result = node }
|
||||
|
||||
override PrintAstNode getChild(int childIndex) {
|
||||
exists(YAMLNode child | result.(YamlNodeNode).getValue() = child |
|
||||
exists(YamlNode child | result.(YamlNodeNode).getValue() = child |
|
||||
child = node.getChildNode(childIndex)
|
||||
)
|
||||
}
|
||||
@@ -657,7 +657,7 @@ module PrintYaml {
|
||||
* Each child of this node aggregates the key and value of a mapping.
|
||||
*/
|
||||
class YamlMappingNode extends YamlNodeNode {
|
||||
override YAMLMapping node;
|
||||
override YamlMapping node;
|
||||
|
||||
override PrintAstNode getChild(int childIndex) {
|
||||
exists(YamlMappingMapNode map | map = result | map.maps(node, childIndex))
|
||||
@@ -671,21 +671,21 @@ module PrintYaml {
|
||||
* A print node representing the `i`th mapping in `mapping`.
|
||||
*/
|
||||
class YamlMappingMapNode extends PrintAstNode, TYamlMappingNode {
|
||||
YAMLMapping mapping;
|
||||
YamlMapping mapping;
|
||||
int i;
|
||||
|
||||
YamlMappingMapNode() { this = TYamlMappingNode(mapping, i) }
|
||||
|
||||
override string toString() {
|
||||
result = "(Mapping " + i + ")" and not exists(mapping.getKeyNode(i).(YAMLScalar).getValue())
|
||||
result = "(Mapping " + i + ")" and not exists(mapping.getKeyNode(i).(YamlScalar).getValue())
|
||||
or
|
||||
result = "(Mapping " + i + ") " + mapping.getKeyNode(i).(YAMLScalar).getValue() + ":"
|
||||
result = "(Mapping " + i + ") " + mapping.getKeyNode(i).(YamlScalar).getValue() + ":"
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if this print node represents the `index`th mapping of `m`.
|
||||
*/
|
||||
predicate maps(YAMLMapping m, int index) {
|
||||
predicate maps(YamlMapping m, int index) {
|
||||
m = mapping and
|
||||
index = i
|
||||
}
|
||||
|
||||
@@ -189,6 +189,13 @@ module Promises {
|
||||
* Gets the pseudo-field used to describe rejected values in a promise.
|
||||
*/
|
||||
string errorProp() { result = "$PromiseRejectField$" }
|
||||
|
||||
/** A property set containing the pseudo-properites of a promise object. */
|
||||
class PromiseProps extends DataFlow::PropertySet {
|
||||
PromiseProps() { this = "PromiseProps" }
|
||||
|
||||
override string getAProperty() { result = [valueProp(), errorProp()] }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -274,6 +281,24 @@ private class PromiseStep extends PreCallGraphStep {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A step from `p -> await p` for the case where `p` is not a promise.
|
||||
*
|
||||
* In this case, `await p` just returns `p` itself. We block flow of the promise-related
|
||||
* pseudo properties through this edge.
|
||||
*/
|
||||
private class RawAwaitStep extends DataFlow::SharedTypeTrackingStep {
|
||||
override predicate withoutPropStep(
|
||||
DataFlow::Node pred, DataFlow::Node succ, DataFlow::PropertySet props
|
||||
) {
|
||||
exists(AwaitExpr await |
|
||||
pred = await.getOperand().flow() and
|
||||
succ = await.flow() and
|
||||
props instanceof Promises::PromiseProps
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This module defines how data-flow propagates into and out of a Promise.
|
||||
* The data-flow is based on pseudo-properties rather than tainting the Promise object (which is what `PromiseTaintStep` does).
|
||||
|
||||
@@ -260,7 +260,7 @@ module RangeAnalysis {
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if the given comparison can be modeled as `A <op> B + bias` where `<op>` is the comparison operator,
|
||||
* Holds if the given `comparison` can be modeled as `A <op> B + bias` where `<op>` is the comparison operator,
|
||||
* and `A` is `a * asign` and likewise `B` is `b * bsign`.
|
||||
*/
|
||||
predicate linearComparison(
|
||||
@@ -310,18 +310,18 @@ module RangeAnalysis {
|
||||
* Holds if `guard` asserts that the outcome of `A <op> B + bias` is true, where `<op>` is a comparison operator.
|
||||
*/
|
||||
predicate linearComparisonGuard(
|
||||
ConditionGuardNode guard, DataFlow::Node a, int asign, string operator, DataFlow::Node b,
|
||||
int bsign, Bias bias
|
||||
ConditionGuardNode guard, DataFlow::Node a, int asign, string op, DataFlow::Node b, int bsign,
|
||||
Bias bias
|
||||
) {
|
||||
exists(Comparison compare |
|
||||
compare = guard.getTest().flow().getImmediatePredecessor*().asExpr() and
|
||||
linearComparison(compare, a, asign, b, bsign, bias) and
|
||||
(
|
||||
guard.getOutcome() = true and operator = compare.getOperator()
|
||||
guard.getOutcome() = true and op = compare.getOperator()
|
||||
or
|
||||
not hasNaNIndicator(guard.getContainer()) and
|
||||
guard.getOutcome() = false and
|
||||
operator = negateOperator(compare.getOperator())
|
||||
op = negateOperator(compare.getOperator())
|
||||
)
|
||||
)
|
||||
}
|
||||
@@ -657,13 +657,13 @@ module RangeAnalysis {
|
||||
*/
|
||||
pragma[noopt]
|
||||
private predicate reachableByNegativeEdges(
|
||||
DataFlow::Node a, int asign, DataFlow::Node b, int bsign, ControlFlowNode cfg
|
||||
DataFlow::Node src, int asign, DataFlow::Node dst, int bsign, ControlFlowNode cfg
|
||||
) {
|
||||
negativeEdge(a, asign, b, bsign, cfg)
|
||||
negativeEdge(src, asign, dst, bsign, cfg)
|
||||
or
|
||||
exists(DataFlow::Node mid, int midx, ControlFlowNode midcfg |
|
||||
reachableByNegativeEdges(a, asign, mid, midx, cfg) and
|
||||
negativeEdge(mid, midx, b, bsign, midcfg) and
|
||||
reachableByNegativeEdges(src, asign, mid, midx, cfg) and
|
||||
negativeEdge(mid, midx, dst, bsign, midcfg) and
|
||||
exists(BasicBlock bb, int i, int j |
|
||||
bb.getNode(i) = midcfg and
|
||||
bb.getNode(j) = cfg and
|
||||
@@ -676,8 +676,8 @@ module RangeAnalysis {
|
||||
DataFlow::Node mid, int midx, ControlFlowNode midcfg, BasicBlock midBB,
|
||||
ReachableBasicBlock midRBB, BasicBlock cfgBB
|
||||
|
|
||||
reachableByNegativeEdges(a, asign, mid, midx, cfg) and
|
||||
negativeEdge(mid, midx, b, bsign, midcfg) and
|
||||
reachableByNegativeEdges(src, asign, mid, midx, cfg) and
|
||||
negativeEdge(mid, midx, dst, bsign, midcfg) and
|
||||
midBB = midcfg.getBasicBlock() and
|
||||
midRBB = midBB.(ReachableBasicBlock) and
|
||||
cfgBB = cfg.getBasicBlock() and
|
||||
|
||||
@@ -1005,7 +1005,10 @@ module RegExpPatterns {
|
||||
* Gets a pattern that matches common top-level domain names in lower case.
|
||||
* DEPRECATED: use `getACommonTld` instead
|
||||
*/
|
||||
deprecated predicate commonTLD = getACommonTld/0;
|
||||
deprecated predicate commonTld = getACommonTld/0;
|
||||
|
||||
/** DEPRECATED: Alias for commonTld */
|
||||
deprecated predicate commonTLD = commonTld/0;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -148,6 +148,18 @@ module Routing {
|
||||
this instanceof MkRouter
|
||||
}
|
||||
|
||||
/**
|
||||
* Like `mayResumeDispatch` but without the assumption that functions with an unknown
|
||||
* implementation invoke their continuation.
|
||||
*/
|
||||
predicate definitelyResumesDispatch() {
|
||||
this.getLastChild().definitelyResumesDispatch()
|
||||
or
|
||||
exists(this.(RouteHandler).getAContinuationInvocation())
|
||||
or
|
||||
this instanceof MkRouter
|
||||
}
|
||||
|
||||
/** Gets the parent of this node, provided that this node may invoke its continuation. */
|
||||
private Node getContinuationParent() {
|
||||
result = this.getParent() and
|
||||
@@ -229,11 +241,11 @@ module Routing {
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `node` has processed the incoming request strictly prior to this node.
|
||||
* Holds if `guard` has processed the incoming request strictly prior to this node.
|
||||
*/
|
||||
pragma[inline]
|
||||
private predicate isGuardedByNodeInternal(Node guard) {
|
||||
// Look for a common ancestor `fork` whose child leading to `guard` ("base1") preceeds
|
||||
// Look for a common ancestor `fork` whose child leading to `guard` ("base1") precedes
|
||||
// the child leading to `this` ("base2").
|
||||
//
|
||||
// Schematically:
|
||||
|
||||
@@ -501,7 +501,7 @@ class SsaExplicitDefinition extends SsaDefinition, TExplicitDef {
|
||||
}
|
||||
|
||||
/** This SSA definition corresponds to the definition of `v` at `def`. */
|
||||
predicate defines(VarDef d, SsaSourceVariable v) { this = TExplicitDef(_, _, d, v) }
|
||||
predicate defines(VarDef def, SsaSourceVariable v) { this = TExplicitDef(_, _, def, v) }
|
||||
|
||||
/** Gets the variable definition wrapped by this SSA definition. */
|
||||
VarDef getDef() { this = TExplicitDef(_, _, result, _) }
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user