Merge branch 'main' into codeql-ci/js-atm-new-release

This commit is contained in:
Henry Mercer
2022-08-24 14:37:44 +01:00
committed by GitHub
4711 changed files with 374682 additions and 147842 deletions

View File

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

View File

@@ -0,0 +1,4 @@
description: Associate symbols with external module declarations
compatibility: backwards
ast_node_symbol.rel: run ast_node_symbol.qlo

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,4 @@
name: codeql/javascript-downgrades
groups: javascript
downgrades: .
library: true

View File

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

View File

@@ -2,7 +2,7 @@
"name": "typescript-parser-wrapper",
"private": true,
"dependencies": {
"typescript": "4.6.2"
"typescript": "4.7.2"
},
"scripts": {
"build": "tsc --project tsconfig.json",

View File

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

View File

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

View File

@@ -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.7.2:
version "4.7.2"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.7.2.tgz#1f9aa2ceb9af87cca227813b4310fff0b51593c4"
integrity sha512-Mamb1iX2FDUpcTRzltPxgWMKy3fhg0TN378ylbktPGPK/99KbDtMQ4W1hwgsbPAsG3a0xKa1vmw4VKZQbkvz5A==

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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-07-11";
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();

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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);
@@ -1710,7 +1710,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 {...}

View File

@@ -0,0 +1,9 @@
async function foo() {
for await (const call of calls) {
call();
}
}
for await (const call of calls) {
call();
}

View 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")

View File

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

View File

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

View File

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

View File

@@ -144,9 +144,9 @@ private module AccessPaths {
not param = base.getReceiver()
|
result = param and
name = param.getAnImmediateUse().asExpr().(Parameter).getName()
name = param.asSource().asExpr().(Parameter).getName()
or
param.getAnImmediateUse().asExpr() instanceof DestructuringPattern and
param.asSource().asExpr() instanceof DestructuringPattern and
result = param.getMember(name)
)
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -188,10 +188,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)
}

View File

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

View File

@@ -1,3 +1,33 @@
## 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

View File

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

View File

@@ -0,0 +1,5 @@
---
category: deprecated
---
* 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.

View File

@@ -0,0 +1,4 @@
---
category: fix
---
* Fixed that top-level `for await` statements would produce a syntax error. These statements are now parsed correctly.

View File

@@ -0,0 +1,6 @@
---
category: minorAnalysis
---
* Most deprecated predicates/classes/modules that have been deprecated for over a year have been
deleted.

View File

@@ -0,0 +1,5 @@
---
category: deprecated
---
* 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.

View File

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

View File

@@ -0,0 +1 @@
## 0.1.4

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

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

View File

@@ -0,0 +1 @@
## 0.2.2

View File

@@ -0,0 +1 @@
## 0.2.3

View File

@@ -1,2 +1,2 @@
---
lastReleaseVersion: 0.1.2
lastReleaseVersion: 0.2.3

View File

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

View File

@@ -1,5 +1,5 @@
name: codeql/javascript-all
version: 0.1.3-dev
version: 0.2.4-dev
groups: javascript
dbscheme: semmlecode.javascript.dbscheme
extractor: javascript

View File

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

View File

@@ -28,6 +28,9 @@ module Actions {
/** Gets the `jobs` mapping from job IDs to job definitions in this workflow. */
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() }
@@ -129,6 +132,9 @@ 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() }
}
/**

View File

@@ -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.
@@ -445,11 +562,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)) }
@@ -567,7 +690,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 |
@@ -744,7 +867,7 @@ module API {
base = MkRoot() and
exists(EntryPoint e |
lbl = Label::entryPoint(e) and
ref = e.getAUse()
ref = e.getASource()
)
or
// property reads
@@ -1178,8 +1301,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 +1317,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 +1328,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
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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) {
@@ -180,6 +195,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 */

View File

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

View File

@@ -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) }
@@ -91,7 +93,7 @@ private string getStem(string name) { result = name.regexpCapture("(.+?)(?:\\.([
* Gets the main module described by `pkg` with the given `priority`.
*/
File resolveMainModule(PackageJson pkg, int priority) {
exists(PathExpr main | main = MainModulePath::of(pkg) |
exists(PathExpr main | main = MainModulePath::of(pkg, ".") |
result = main.resolve() and priority = 0
or
result = tryExtensions(main.resolve(), "index", priority)
@@ -140,17 +142,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 +175,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 +219,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 +239,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 }
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -192,3 +192,35 @@ class StringSplitCall extends DataFlow::MethodCallNode {
bindingset[i]
DataFlow::Node getASubstringRead(int i) { result = this.getAPropertyRead(i.toString()) }
}
/**
* A call to `Object.prototype.hasOwnProperty`, `Object.hasOwn`, or a library that implements
* the same functionality.
*/
class HasOwnPropertyCall extends DataFlow::Node instanceof DataFlow::CallNode {
DataFlow::Node object;
DataFlow::Node property;
HasOwnPropertyCall() {
// Make sure we handle reflective calls since libraries love to do that.
super.getCalleeNode().getALocalSource().(DataFlow::PropRead).getPropertyName() =
"hasOwnProperty" and
object = super.getReceiver() and
property = super.getArgument(0)
or
this =
[
DataFlow::globalVarRef("Object").getAMemberCall("hasOwn"), //
DataFlow::moduleImport("has").getACall(), //
LodashUnderscore::member("has").getACall()
] and
object = super.getArgument(0) and
property = super.getArgument(1)
}
/** Gets the object whose property is being checked. */
DataFlow::Node getObject() { result = object }
/** Gets the property being checked. */
DataFlow::Node getProperty() { result = property }
}

View File

@@ -751,7 +751,7 @@ class TypeAccess extends @typeaccess, TypeExpr, TypeRef {
}
/**
* Gets a suitable name for the library imported by `import`.
* Gets a suitable name for the library imported by `imprt`.
*
* For relative imports, this is the snapshot-relative path to the imported module.
* For non-relative imports, it is the import path itself.
@@ -896,21 +896,28 @@ class ArrayTypeExpr extends @array_typeexpr, TypeExpr {
override string getAPrimaryQlClass() { result = "ArrayTypeExpr" }
}
private class RawUnionOrIntersectionTypeExpr = @union_typeexpr or @intersection_typeexpr;
/**
* A union type, such as `string|number|boolean`.
* A union or intersection type, such as `string|number|boolean` or `A & B`.
*/
class UnionTypeExpr extends @union_typeexpr, TypeExpr {
/** Gets the `n`th type in the union, starting at 0. */
class UnionOrIntersectionTypeExpr extends RawUnionOrIntersectionTypeExpr, TypeExpr {
/** Gets the `n`th type in the union or intersection, starting at 0. */
TypeExpr getElementType(int n) { result = this.getChildTypeExpr(n) }
/** Gets any of the types in the union. */
/** Gets any of the types in the union or intersection. */
TypeExpr getAnElementType() { result = this.getElementType(_) }
/** Gets the number of types in the union. This is always at least two. */
/** Gets the number of types in the union or intersection. This is always at least two. */
int getNumElementType() { result = count(this.getAnElementType()) }
override TypeExpr getAnUnderlyingType() { result = this.getAnElementType().getAnUnderlyingType() }
}
/**
* A union type, such as `string|number|boolean`.
*/
class UnionTypeExpr extends @union_typeexpr, UnionOrIntersectionTypeExpr {
override string getAPrimaryQlClass() { result = "UnionTypeExpr" }
}
@@ -932,18 +939,7 @@ class IndexedAccessTypeExpr extends @indexed_access_typeexpr, TypeExpr {
*
* In general, there are can more than two operands to an intersection type.
*/
class IntersectionTypeExpr extends @intersection_typeexpr, TypeExpr {
/** Gets the `n`th operand of the intersection type, starting at 0. */
TypeExpr getElementType(int n) { result = this.getChildTypeExpr(n) }
/** Gets any of the operands to the intersection type. */
TypeExpr getAnElementType() { result = this.getElementType(_) }
/** Gets the number of operands to the intersection type. This is always at least two. */
int getNumElementType() { result = count(this.getAnElementType()) }
override TypeExpr getAnUnderlyingType() { result = this.getAnElementType().getAnUnderlyingType() }
class IntersectionTypeExpr extends @intersection_typeexpr, UnionOrIntersectionTypeExpr {
override string getAPrimaryQlClass() { result = "IntersectionTypeExpr" }
}
@@ -1286,6 +1282,8 @@ class ExpressionWithTypeArguments extends @expression_with_type_arguments, Expr
override ControlFlowNode getFirstControlFlowNode() {
result = this.getExpression().getFirstControlFlowNode()
}
override string getAPrimaryQlClass() { result = "ExpressionWithTypeArguments" }
}
/**

View File

@@ -873,6 +873,18 @@ class DeclarationSpace extends string {
DeclarationSpace() { this = "variable" or this = "type" or this = "namespace" }
}
/** Module containing the `DeclarationSpace` constants. */
module DeclarationSpace {
/** Gets the declaration space for variables/values. */
DeclarationSpace variable() { result = "variable" }
/** Gets the declaration space for types. */
DeclarationSpace type() { result = "type" }
/** Gets the declaration space for namespaces. */
DeclarationSpace namespace() { result = "namespace" }
}
/**
* A name that is declared in a particular scope.
*

View File

@@ -8,7 +8,7 @@ private class TXmlLocatable =
@xmldtd or @xmlelement or @xmlattribute or @xmlnamespace or @xmlcomment or @xmlcharacters;
/** An XML element that has a location. */
class XMLLocatable extends @xmllocatable, TXmlLocatable {
class XmlLocatable extends @xmllocatable, TXmlLocatable {
/** Gets the source location for this element. */
Location getLocation() { xmllocations(this, result) }
@@ -32,13 +32,16 @@ class XMLLocatable extends @xmllocatable, TXmlLocatable {
string toString() { none() } // overridden in subclasses
}
/** DEPRECATED: Alias for XmlLocatable */
deprecated class XMLLocatable = XmlLocatable;
/**
* An `XMLParent` is either an `XMLElement` or an `XMLFile`,
* An `XmlParent` is either an `XmlElement` or an `XmlFile`,
* both of which can contain other elements.
*/
class XMLParent extends @xmlparent {
XMLParent() {
// explicitly restrict `this` to be either an `XMLElement` or an `XMLFile`;
class XmlParent extends @xmlparent {
XmlParent() {
// explicitly restrict `this` to be either an `XmlElement` or an `XmlFile`;
// the type `@xmlparent` currently also includes non-XML files
this instanceof @xmlelement or xmlEncoding(this, _)
}
@@ -50,28 +53,28 @@ class XMLParent extends @xmlparent {
string getName() { none() } // overridden in subclasses
/** Gets the file to which this XML parent belongs. */
XMLFile getFile() { result = this or xmlElements(this, _, _, _, result) }
XmlFile getFile() { result = this or xmlElements(this, _, _, _, result) }
/** Gets the child element at a specified index of this XML parent. */
XMLElement getChild(int index) { xmlElements(result, _, this, index, _) }
XmlElement getChild(int index) { xmlElements(result, _, this, index, _) }
/** Gets a child element of this XML parent. */
XMLElement getAChild() { xmlElements(result, _, this, _, _) }
XmlElement getAChild() { xmlElements(result, _, this, _, _) }
/** Gets a child element of this XML parent with the given `name`. */
XMLElement getAChild(string name) { xmlElements(result, _, this, _, _) and result.hasName(name) }
XmlElement getAChild(string name) { xmlElements(result, _, this, _, _) and result.hasName(name) }
/** Gets a comment that is a child of this XML parent. */
XMLComment getAComment() { xmlComments(result, _, this, _) }
XmlComment getAComment() { xmlComments(result, _, this, _) }
/** Gets a character sequence that is a child of this XML parent. */
XMLCharacters getACharactersSet() { xmlChars(result, _, this, _, _, _) }
XmlCharacters getACharactersSet() { xmlChars(result, _, this, _, _, _) }
/** Gets the depth in the tree. (Overridden in XMLElement.) */
/** Gets the depth in the tree. (Overridden in XmlElement.) */
int getDepth() { result = 0 }
/** Gets the number of child XML elements of this XML parent. */
int getNumberOfChildren() { result = count(XMLElement e | xmlElements(e, _, this, _, _)) }
int getNumberOfChildren() { result = count(XmlElement e | xmlElements(e, _, this, _, _)) }
/** Gets the number of places in the body of this XML parent where text occurs. */
int getNumberOfCharacterSets() { result = count(int pos | xmlChars(_, _, this, pos, _, _)) }
@@ -92,9 +95,12 @@ class XMLParent extends @xmlparent {
string toString() { result = this.getName() }
}
/** DEPRECATED: Alias for XmlParent */
deprecated class XMLParent = XmlParent;
/** An XML file. */
class XMLFile extends XMLParent, File {
XMLFile() { xmlEncoding(this, _) }
class XmlFile extends XmlParent, File {
XmlFile() { xmlEncoding(this, _) }
/** Gets a printable representation of this XML file. */
override string toString() { result = this.getName() }
@@ -120,15 +126,21 @@ class XMLFile extends XMLParent, File {
string getEncoding() { xmlEncoding(this, result) }
/** Gets the XML file itself. */
override XMLFile getFile() { result = this }
override XmlFile getFile() { result = this }
/** Gets a top-most element in an XML file. */
XMLElement getARootElement() { result = this.getAChild() }
XmlElement getARootElement() { result = this.getAChild() }
/** Gets a DTD associated with this XML file. */
XMLDTD getADTD() { xmlDTDs(result, _, _, _, this) }
XmlDtd getADtd() { xmlDTDs(result, _, _, _, this) }
/** DEPRECATED: Alias for getADtd */
deprecated XmlDtd getADTD() { result = this.getADtd() }
}
/** DEPRECATED: Alias for XmlFile */
deprecated class XMLFile = XmlFile;
/**
* An XML document type definition (DTD).
*
@@ -140,7 +152,7 @@ class XMLFile extends XMLParent, File {
* <!ELEMENT lastName (#PCDATA)>
* ```
*/
class XMLDTD extends XMLLocatable, @xmldtd {
class XmlDtd extends XmlLocatable, @xmldtd {
/** Gets the name of the root element of this DTD. */
string getRoot() { xmlDTDs(this, result, _, _, _) }
@@ -154,7 +166,7 @@ class XMLDTD extends XMLLocatable, @xmldtd {
predicate isPublic() { not xmlDTDs(this, _, "", _, _) }
/** Gets the parent of this DTD. */
XMLParent getParent() { xmlDTDs(this, _, _, _, result) }
XmlParent getParent() { xmlDTDs(this, _, _, _, result) }
override string toString() {
this.isPublic() and
@@ -165,6 +177,9 @@ class XMLDTD extends XMLLocatable, @xmldtd {
}
}
/** DEPRECATED: Alias for XmlDtd */
deprecated class XMLDTD = XmlDtd;
/**
* An XML element in an XML file.
*
@@ -176,7 +191,7 @@ class XMLDTD extends XMLLocatable, @xmldtd {
* </manifest>
* ```
*/
class XMLElement extends @xmlelement, XMLParent, XMLLocatable {
class XmlElement extends @xmlelement, XmlParent, XmlLocatable {
/** Holds if this XML element has the given `name`. */
predicate hasName(string name) { name = this.getName() }
@@ -184,10 +199,10 @@ class XMLElement extends @xmlelement, XMLParent, XMLLocatable {
override string getName() { xmlElements(this, result, _, _, _) }
/** Gets the XML file in which this XML element occurs. */
override XMLFile getFile() { xmlElements(this, _, _, _, result) }
override XmlFile getFile() { xmlElements(this, _, _, _, result) }
/** Gets the parent of this XML element. */
XMLParent getParent() { xmlElements(this, _, result, _, _) }
XmlParent getParent() { xmlElements(this, _, result, _, _) }
/** Gets the index of this XML element among its parent's children. */
int getIndex() { xmlElements(this, _, _, result, _) }
@@ -196,7 +211,7 @@ class XMLElement extends @xmlelement, XMLParent, XMLLocatable {
predicate hasNamespace() { xmlHasNs(this, _, _) }
/** Gets the namespace of this XML element, if any. */
XMLNamespace getNamespace() { xmlHasNs(this, result, _) }
XmlNamespace getNamespace() { xmlHasNs(this, result, _) }
/** Gets the index of this XML element among its parent's children. */
int getElementPositionIndex() { xmlElements(this, _, _, result, _) }
@@ -205,10 +220,10 @@ class XMLElement extends @xmlelement, XMLParent, XMLLocatable {
override int getDepth() { result = this.getParent().getDepth() + 1 }
/** Gets an XML attribute of this XML element. */
XMLAttribute getAnAttribute() { result.getElement() = this }
XmlAttribute getAnAttribute() { result.getElement() = this }
/** Gets the attribute with the specified `name`, if any. */
XMLAttribute getAttribute(string name) { result.getElement() = this and result.getName() = name }
XmlAttribute getAttribute(string name) { result.getElement() = this and result.getName() = name }
/** Holds if this XML element has an attribute with the specified `name`. */
predicate hasAttribute(string name) { exists(this.getAttribute(name)) }
@@ -220,6 +235,9 @@ class XMLElement extends @xmlelement, XMLParent, XMLLocatable {
override string toString() { result = this.getName() }
}
/** DEPRECATED: Alias for XmlElement */
deprecated class XMLElement = XmlElement;
/**
* An attribute that occurs inside an XML element.
*
@@ -230,18 +248,18 @@ class XMLElement extends @xmlelement, XMLParent, XMLLocatable {
* android:versionCode="1"
* ```
*/
class XMLAttribute extends @xmlattribute, XMLLocatable {
class XmlAttribute extends @xmlattribute, XmlLocatable {
/** Gets the name of this attribute. */
string getName() { xmlAttrs(this, _, result, _, _, _) }
/** Gets the XML element to which this attribute belongs. */
XMLElement getElement() { xmlAttrs(this, result, _, _, _, _) }
XmlElement getElement() { xmlAttrs(this, result, _, _, _, _) }
/** Holds if this attribute has a namespace. */
predicate hasNamespace() { xmlHasNs(this, _, _) }
/** Gets the namespace of this attribute, if any. */
XMLNamespace getNamespace() { xmlHasNs(this, result, _) }
XmlNamespace getNamespace() { xmlHasNs(this, result, _) }
/** Gets the value of this attribute. */
string getValue() { xmlAttrs(this, _, _, result, _, _) }
@@ -250,6 +268,9 @@ class XMLAttribute extends @xmlattribute, XMLLocatable {
override string toString() { result = this.getName() + "=" + this.getValue() }
}
/** DEPRECATED: Alias for XmlAttribute */
deprecated class XMLAttribute = XmlAttribute;
/**
* A namespace used in an XML file.
*
@@ -259,23 +280,29 @@ class XMLAttribute extends @xmlattribute, XMLLocatable {
* xmlns:android="http://schemas.android.com/apk/res/android"
* ```
*/
class XMLNamespace extends XMLLocatable, @xmlnamespace {
class XmlNamespace extends XmlLocatable, @xmlnamespace {
/** Gets the prefix of this namespace. */
string getPrefix() { xmlNs(this, result, _, _) }
/** Gets the URI of this namespace. */
string getURI() { xmlNs(this, _, result, _) }
string getUri() { xmlNs(this, _, result, _) }
/** DEPRECATED: Alias for getUri */
deprecated string getURI() { result = this.getUri() }
/** Holds if this namespace has no prefix. */
predicate isDefault() { this.getPrefix() = "" }
override string toString() {
this.isDefault() and result = this.getURI()
this.isDefault() and result = this.getUri()
or
not this.isDefault() and result = this.getPrefix() + ":" + this.getURI()
not this.isDefault() and result = this.getPrefix() + ":" + this.getUri()
}
}
/** DEPRECATED: Alias for XmlNamespace */
deprecated class XMLNamespace = XmlNamespace;
/**
* A comment in an XML file.
*
@@ -285,17 +312,20 @@ class XMLNamespace extends XMLLocatable, @xmlnamespace {
* <!-- This is a comment. -->
* ```
*/
class XMLComment extends @xmlcomment, XMLLocatable {
class XmlComment extends @xmlcomment, XmlLocatable {
/** Gets the text content of this XML comment. */
string getText() { xmlComments(this, result, _, _) }
/** Gets the parent of this XML comment. */
XMLParent getParent() { xmlComments(this, _, result, _) }
XmlParent getParent() { xmlComments(this, _, result, _) }
/** Gets a printable representation of this XML comment. */
override string toString() { result = this.getText() }
}
/** DEPRECATED: Alias for XmlComment */
deprecated class XMLComment = XmlComment;
/**
* A sequence of characters that occurs between opening and
* closing tags of an XML element, excluding other elements.
@@ -306,12 +336,12 @@ class XMLComment extends @xmlcomment, XMLLocatable {
* <content>This is a sequence of characters.</content>
* ```
*/
class XMLCharacters extends @xmlcharacters, XMLLocatable {
class XmlCharacters extends @xmlcharacters, XmlLocatable {
/** Gets the content of this character sequence. */
string getCharacters() { xmlChars(this, result, _, _, _, _) }
/** Gets the parent of this character sequence. */
XMLParent getParent() { xmlChars(this, _, result, _, _, _) }
XmlParent getParent() { xmlChars(this, _, result, _, _, _) }
/** Holds if this character sequence is CDATA. */
predicate isCDATA() { xmlChars(this, _, _, _, 1, _) }
@@ -319,3 +349,6 @@ class XMLCharacters extends @xmlcharacters, XMLLocatable {
/** Gets a printable representation of this XML character sequence. */
override string toString() { result = this.getCharacters() }
}
/** DEPRECATED: Alias for XmlCharacters */
deprecated class XMLCharacters = XmlCharacters;

View File

@@ -353,7 +353,7 @@ abstract class BarrierGuardNode extends DataFlow::Node {
}
/**
* Holds if data flow node `nd` acts as a barrier for data flow.
* Holds if data flow node `guard` acts as a barrier for data flow.
*
* `label` is bound to the blocked label, or the empty string if all labels should be blocked.
*/
@@ -382,7 +382,7 @@ private predicate barrierGuardIsRelevant(BarrierGuardNode guard) {
}
/**
* Holds if data flow node `nd` acts as a barrier for data flow due to aliasing through
* Holds if data flow node `guard` acts as a barrier for data flow due to aliasing through
* an access path.
*
* `label` is bound to the blocked label, or the empty string if all labels should be blocked.
@@ -523,74 +523,6 @@ abstract class LabeledBarrierGuardNode extends BarrierGuardNode {
override predicate blocks(boolean outcome, Expr e) { none() }
}
/**
* DEPRECATED. Subclasses should extend `SharedFlowStep` instead, unless the subclass
* is part of a query, in which case it should be moved into the `isAdditionalFlowStep` predicate
* of the relevant data-flow configuration.
* Other uses of the predicate in this class should instead reference the predicates in the
* `SharedFlowStep::` module, such as `SharedFlowStep::step`.
*
* A data flow edge that should be added to all data flow configurations in
* addition to standard data flow edges.
*
* Note: For performance reasons, all subclasses of this class should be part
* of the standard library. Override `Configuration::isAdditionalFlowStep`
* for analysis-specific flow steps.
*/
deprecated class AdditionalFlowStep = LegacyAdditionalFlowStep;
// Internal version of AdditionalFlowStep that we can reference without deprecation warnings.
abstract private class LegacyAdditionalFlowStep extends DataFlow::Node {
/**
* Holds if `pred` &rarr; `succ` should be considered a data flow edge.
*/
predicate step(DataFlow::Node pred, DataFlow::Node succ) { none() }
/**
* Holds if `pred` &rarr; `succ` should be considered a data flow edge
* transforming values with label `predlbl` to have label `succlbl`.
*/
predicate step(
DataFlow::Node pred, DataFlow::Node succ, DataFlow::FlowLabel predlbl,
DataFlow::FlowLabel succlbl
) {
none()
}
/**
* EXPERIMENTAL. This API may change in the future.
*
* Holds if `pred` should be stored in the object `succ` under the property `prop`.
* The object `succ` must be a `DataFlow::SourceNode` for the object wherein the value is stored.
*/
predicate storeStep(DataFlow::Node pred, DataFlow::SourceNode succ, string prop) { none() }
/**
* EXPERIMENTAL. This API may change in the future.
*
* Holds if the property `prop` of the object `pred` should be loaded into `succ`.
*/
predicate loadStep(DataFlow::Node pred, DataFlow::Node succ, string prop) { none() }
/**
* EXPERIMENTAL. This API may change in the future.
*
* Holds if the property `prop` should be copied from the object `pred` to the object `succ`.
*/
predicate loadStoreStep(DataFlow::Node pred, DataFlow::Node succ, string prop) { none() }
/**
* EXPERIMENTAL. This API may change in the future.
*
* Holds if the property `loadProp` should be copied from the object `pred` to the property `storeProp` of object `succ`.
*/
predicate loadStoreStep(
DataFlow::Node pred, DataFlow::Node succ, string loadProp, string storeProp
) {
none()
}
}
/**
* A data flow edge that should be added to all data flow configurations in
* addition to standard data flow edges.
@@ -713,40 +645,6 @@ module SharedFlowStep {
}
}
/**
* Contributes subclasses of `AdditionalFlowStep` to `SharedFlowStep`.
*/
private class AdditionalFlowStepAsSharedStep extends SharedFlowStep {
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
any(LegacyAdditionalFlowStep s).step(pred, succ)
}
override predicate step(
DataFlow::Node pred, DataFlow::Node succ, DataFlow::FlowLabel predlbl,
DataFlow::FlowLabel succlbl
) {
any(LegacyAdditionalFlowStep s).step(pred, succ, predlbl, succlbl)
}
override predicate storeStep(DataFlow::Node pred, DataFlow::SourceNode succ, string prop) {
any(LegacyAdditionalFlowStep s).storeStep(pred, succ, prop)
}
override predicate loadStep(DataFlow::Node pred, DataFlow::Node succ, string prop) {
any(LegacyAdditionalFlowStep s).loadStep(pred, succ, prop)
}
override predicate loadStoreStep(DataFlow::Node pred, DataFlow::Node succ, string prop) {
any(LegacyAdditionalFlowStep s).loadStoreStep(pred, succ, prop)
}
override predicate loadStoreStep(
DataFlow::Node pred, DataFlow::Node succ, string loadProp, string storeProp
) {
any(LegacyAdditionalFlowStep s).loadStoreStep(pred, succ, loadProp, storeProp)
}
}
/**
* A collection of pseudo-properties that are used in multiple files.
*
@@ -1155,7 +1053,7 @@ private predicate appendStep(
}
/**
* Holds if a function invoked at `invk` may return an expression into which `input`,
* Holds if a function invoked at `output` may return an expression into which `input`,
* which is either an argument or a definition captured by the function, flows under
* configuration `cfg`, possibly through callees.
*/
@@ -1369,56 +1267,47 @@ private predicate loadStep(
* If `onlyRelevantInCall` is true, the `base` object will not be propagated out of return edges, because
* the flow that originally reached `base.startProp` used a call edge.
*/
pragma[nomagic]
pragma[noopt]
private predicate reachableFromStoreBase(
string startProp, string endProp, DataFlow::Node base, DataFlow::Node nd,
DataFlow::Configuration cfg, PathSummary summary, boolean onlyRelevantInCall
DataFlow::Configuration cfg, TPathSummary summary, boolean onlyRelevantInCall
) {
exists(PathSummary s1, PathSummary s2, DataFlow::Node rhs |
reachableFromSource(rhs, cfg, s1) and
onlyRelevantInCall = s1.hasCall()
or
reachableFromStoreBase(_, _, _, rhs, cfg, s1, onlyRelevantInCall)
|
exists(TPathSummary s1, TPathSummary s2, DataFlow::Node rhs |
storeStep(rhs, nd, startProp, cfg, s2) and
endProp = startProp and
base = nd and
summary =
MkPathSummary(false, s2.hasCall(), DataFlow::FlowLabel::data(), DataFlow::FlowLabel::data())
exists(boolean hasCall, DataFlow::FlowLabel data |
hasCall = hasCall(s2) and
data = DataFlow::FlowLabel::data() and
summary = MkPathSummary(false, hasCall, data, data)
)
|
reachableFromSource(rhs, cfg, s1) and
onlyRelevantInCall = hasCall(s1)
or
reachableFromStoreBase(_, _, _, rhs, cfg, s1, onlyRelevantInCall)
)
or
exists(PathSummary newSummary, PathSummary oldSummary |
reachableFromStoreBaseStep(startProp, endProp, base, nd, cfg, oldSummary, newSummary,
onlyRelevantInCall) and
summary = oldSummary.appendValuePreserving(newSummary)
)
}
/**
* Holds if `base` is the base of a write to property `prop`, and `nd` is reachable
* from `base` under configuration `cfg` (possibly through callees) along a path whose
* last step is summarized by `newSummary`, and the previous steps are summarized
* by `oldSummary`.
*/
pragma[noinline]
private predicate reachableFromStoreBaseStep(
string startProp, string endProp, DataFlow::Node base, DataFlow::Node nd,
DataFlow::Configuration cfg, PathSummary oldSummary, PathSummary newSummary,
boolean onlyRelevantInCall
) {
exists(DataFlow::Node mid |
exists(DataFlow::Node mid, PathSummary oldSummary, PathSummary newSummary |
reachableFromStoreBase(startProp, endProp, base, mid, cfg, oldSummary, onlyRelevantInCall) and
flowStep(mid, cfg, nd, newSummary) and
onlyRelevantInCall.booleanAnd(newSummary.hasReturn()) = false
exists(boolean hasReturn |
hasReturn = newSummary.hasReturn() and
onlyRelevantInCall.booleanAnd(hasReturn) = false
)
or
exists(string midProp |
reachableFromStoreBase(startProp, midProp, base, mid, cfg, oldSummary, onlyRelevantInCall) and
isAdditionalLoadStoreStep(mid, nd, midProp, endProp, cfg) and
newSummary = PathSummary::level()
)
|
summary = oldSummary.appendValuePreserving(newSummary)
)
}
private boolean hasCall(PathSummary summary) { result = summary.hasCall() }
/**
* Holds if the value of `pred` is written to a property of some base object, and that base
* object may flow into the base of property read `succ` under configuration `cfg` along
@@ -1758,7 +1647,7 @@ class PathNode extends TPathNode {
this = MkSinkNode(nd, cfg)
}
/** Holds if this path node wraps data-flow node `nd` and configuration `c`. */
/** Holds if this path node wraps data-flow node `n` and configuration `c`. */
predicate wraps(DataFlow::Node n, DataFlow::Configuration c) { nd = n and cfg = c }
/** Gets the underlying configuration of this path node. */
@@ -1873,7 +1762,7 @@ class MidPathNode extends PathNode, MkMidNode {
MidPathNode() { this = MkMidNode(nd, cfg, summary) }
/** Holds if this path node wraps data-flow node `nd`, configuration `c` and summary `s`. */
/** Holds if this path node wraps data-flow node `n`, configuration `c` and summary `s`. */
predicate wraps(DataFlow::Node n, DataFlow::Configuration c, PathSummary s) {
nd = n and cfg = c and summary = s
}
@@ -2018,6 +1907,7 @@ private class BarrierGuardFunction extends Function {
BarrierGuardNode guard;
boolean guardOutcome;
string label;
int paramIndex;
BarrierGuardFunction() {
barrierGuardIsRelevant(guard) and
@@ -2041,8 +1931,7 @@ private class BarrierGuardFunction extends Function {
sanitizedParameter.flowsToExpr(e) and
barrierGuardBlocksExpr(guard, guardOutcome, e, label)
) and
getNumParameter() = 1 and
sanitizedParameter.getParameter() = getParameter(0)
sanitizedParameter.getParameter() = getParameter(paramIndex)
}
/**
@@ -2050,10 +1939,10 @@ private class BarrierGuardFunction extends Function {
*/
predicate isBarrierCall(DataFlow::CallNode call, Expr e, boolean outcome, string lbl) {
exists(DataFlow::Node arg |
argumentPassing(pragma[only_bind_into](call), pragma[only_bind_into](arg),
pragma[only_bind_into](this), pragma[only_bind_into](sanitizedParameter)) and
arg.asExpr() = e and
arg = call.getArgument(0) and
call.getNumArgument() = 1 and
argumentPassing(call, arg, this, sanitizedParameter) and
arg = call.getArgument(paramIndex) and
outcome = guardOutcome and
lbl = label
)

View File

@@ -1029,6 +1029,32 @@ module DataFlow {
override File getFile() { result = function.getFile() }
}
/**
* A data flow node representing the arguments object given to a function.
*/
class ReflectiveParametersNode extends DataFlow::Node, TReflectiveParametersNode {
Function function;
ReflectiveParametersNode() { this = TReflectiveParametersNode(function) }
override string toString() { result = "'arguments' object of " + function.describe() }
override predicate hasLocationInfo(
string filepath, int startline, int startcolumn, int endline, int endcolumn
) {
function.getLocation().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
}
override BasicBlock getBasicBlock() { result = function.getEntry().getBasicBlock() }
/**
* Gets the function whose `arguments` object is represented by this node.
*/
Function getFunction() { result = function }
override File getFile() { result = function.getFile() }
}
/**
* A data flow node representing the exceptions thrown by the callee of an invocation.
*/
@@ -1627,6 +1653,35 @@ module DataFlow {
exists(Function f | not f.isAsyncOrGenerator() |
DataFlow::functionReturnNode(succ, f) and pred = valueNode(f.getAReturnedExpr())
)
or
// from a reflective params node to a reference to the arguments object.
exists(DataFlow::ReflectiveParametersNode params, Function f | f = params.getFunction() |
succ = f.getArgumentsVariable().getAnAccess().flow() and
pred = params
)
}
/** A load step from a reflective parameter node to each parameter. */
private class ReflectiveParamsStep extends PreCallGraphStep {
override predicate loadStep(DataFlow::Node obj, DataFlow::Node element, string prop) {
exists(DataFlow::ReflectiveParametersNode params, DataFlow::FunctionNode f, int i |
f.getFunction() = params.getFunction() and
obj = params and
prop = i + "" and
element = f.getParameter(i)
)
}
}
/** A taint step from the reflective parameters node to any parameter. */
private class ReflectiveParamsTaintStep extends TaintTracking::SharedTaintStep {
override predicate step(DataFlow::Node obj, DataFlow::Node element) {
exists(DataFlow::ReflectiveParametersNode params, DataFlow::FunctionNode f |
f.getFunction() = params.getFunction() and
obj = params and
element = f.getAParameter()
)
}
}
/**
@@ -1653,7 +1708,7 @@ module DataFlow {
}
/**
* Holds if the flow information for this node is incomplete.
* Holds if the flow information for the node `nd`.
*
* This predicate holds if there may be a source flow node from which data flows into
* this node, but that node is not a result of `getALocalSource()` due to analysis incompleteness.

View File

@@ -498,7 +498,7 @@ private module ReturnPortal {
invk = callee.getAnExitNode(isRemote).getAnInvocation()
}
/** Holds if `ret` is a return node of a function flowing through `callee`. */
/** Holds if `ret` is a return node of a function flowing through `base`. */
predicate returns(Portal base, DataFlow::Node ret, boolean escapes) {
ret = base.getAnEntryNode(escapes).getALocalSource().(DataFlow::FunctionNode).getAReturn()
}

View File

@@ -33,13 +33,7 @@ private import semmle.javascript.internal.CachedStages
* import("fs")
* ```
*/
class SourceNode extends DataFlow::Node {
SourceNode() {
this instanceof SourceNode::Range
or
none() and this instanceof SourceNode::Internal::RecursionGuard
}
class SourceNode extends DataFlow::Node instanceof SourceNode::Range {
/**
* Holds if this node flows into `sink` in zero or more local (that is,
* intra-procedural) steps.
@@ -338,14 +332,10 @@ module SourceNode {
or
// Include return nodes because they model the implicit Promise creation in async functions.
DataFlow::functionReturnNode(this, _)
or
this instanceof DataFlow::ReflectiveParametersNode
}
}
/** INTERNAL. DO NOT USE. */
module Internal {
/** An empty class that some tests are using to enforce that SourceNode is non-recursive. */
abstract class RecursionGuard extends DataFlow::Node { }
}
}
private class NodeModuleSourcesNodes extends SourceNode::Range {

View File

@@ -320,14 +320,6 @@ module TaintTracking {
any(SharedTaintStep step).heuristicStep(pred, succ)
}
/**
* Holds if `pred -> succ` is an edge contributed by an `AdditionalTaintStep` instance.
*/
cached
predicate legacyAdditionalTaintStep(DataFlow::Node pred, DataFlow::Node succ) {
any(InternalAdditionalTaintStep step).step(pred, succ)
}
/**
* Public taint step relations.
*/
@@ -441,7 +433,6 @@ module TaintTracking {
* Holds if `pred -> succ` is an edge used by all taint-tracking configurations.
*/
predicate sharedTaintStep(DataFlow::Node pred, DataFlow::Node succ) {
Cached::legacyAdditionalTaintStep(pred, succ) or
Cached::genericStep(pred, succ) or
Cached::heuristicStep(pred, succ) or
uriStep(pred, succ) or
@@ -456,31 +447,6 @@ module TaintTracking {
promiseStep(pred, succ)
}
/**
* DEPRECATED. Subclasses should extend `SharedTaintStep` instead, unless the subclass
* is part of a query, in which case it should be moved into the `isAdditionalTaintStep` predicate
* of the relevant taint-tracking configuration.
* Other uses of the `step` relation in this class should instead use the `TaintTracking::sharedTaintStep`
* predicate.
*
* A taint-propagating data flow edge that should be added to all taint tracking
* configurations in addition to standard data flow edges.
*
* Note: For performance reasons, all subclasses of this class should be part
* of the standard library. Override `Configuration::isAdditionalTaintStep`
* for analysis-specific taint steps.
*/
deprecated class AdditionalTaintStep = InternalAdditionalTaintStep;
/** Internal version of `AdditionalTaintStep` that won't trigger deprecation warnings. */
abstract private class InternalAdditionalTaintStep extends DataFlow::Node {
/**
* Holds if `pred` &rarr; `succ` should be considered a taint-propagating
* data flow edge.
*/
abstract predicate step(DataFlow::Node pred, DataFlow::Node succ);
}
/** Gets a data flow node referring to the client side URL. */
private DataFlow::SourceNode clientSideUrlRef(DataFlow::TypeTracker t) {
t.start() and
@@ -831,7 +797,7 @@ module TaintTracking {
}
/**
* Holds if the property `loadStep` should be copied from the object `pred` to the property `storeStep` of object `succ`.
* Holds if the property `loadProp` should be copied from the object `pred` to the property `storeProp` of object `succ`.
*
* This step is used to copy the value of our pseudo-property that can later be accessed using a `get` or `getAll` call.
* For an expression `url.searchParams`, the property `hiddenUrlPseudoProperty()` from the `url` object is stored in the property `getableUrlPseudoProperty()` on `url.searchParams`.
@@ -1027,18 +993,16 @@ module TaintTracking {
class WhitelistContainmentCallSanitizer extends AdditionalSanitizerGuardNode,
DataFlow::MethodCallNode {
WhitelistContainmentCallSanitizer() {
exists(string name |
name = "contains" or
name = "has" or
name = "hasOwnProperty"
|
this.getMethodName() = name
)
this.getMethodName() = ["contains", "has", "hasOwnProperty", "hasOwn"]
}
override predicate sanitizes(boolean outcome, Expr e) {
outcome = true and
e = this.getArgument(0).asExpr()
exists(int propertyIndex |
if this.getMethodName() = "hasOwn" then propertyIndex = 1 else propertyIndex = 0
|
outcome = true and
e = this.getArgument(propertyIndex).asExpr()
)
}
override predicate appliesTo(Configuration cfg) { any() }
@@ -1128,6 +1092,19 @@ module TaintTracking {
)
}
/** A test for the value of `typeof x`, restricting the potential types of `x`. */
predicate isStringTypeGuard(EqualityTest test, Expr operand, boolean polarity) {
exists(TypeofTag tag | TaintTracking::isTypeofGuard(test, operand, tag) |
// typeof x === "string" sanitizes `x` when it evaluates to false
tag = "string" and
polarity = test.getPolarity().booleanNot()
or
// typeof x === "object" sanitizes `x` when it evaluates to true
tag != "string" and
polarity = test.getPolarity()
)
}
/** Holds if `guard` is a test that checks if `operand` is a number. */
predicate isNumberGuard(DataFlow::Node guard, Expr operand, boolean polarity) {
exists(DataFlow::CallNode isNaN |

View File

@@ -70,6 +70,12 @@ class TypeTracker extends TTypeTracker {
step = LoadStep(prop) and result = MkTypeTracker(hasCall, "")
or
exists(string p | step = StoreStep(p) and prop = "" and result = MkTypeTracker(hasCall, p))
or
exists(PropertySet props |
step = WithoutPropStep(props) and
not prop = props.getAProperty() and
result = this
)
}
/** Gets a textual representation of this summary. */
@@ -306,7 +312,7 @@ class TypeBackTracker extends TTypeBackTracker {
* result = < some API call >.getArgument(< n >)
* or
* exists (DataFlow::TypeBackTracker t2 |
* t = t2.smallstep(result, myType(t2))
* t2 = t.smallstep(result, myType(t2))
* )
* }
*
@@ -373,6 +379,26 @@ class SharedTypeTrackingStep extends Unit {
) {
none()
}
/**
* Holds if type-tracking should step from `pred` to `succ` but block flow of `props` through here.
*
* This can be seen as taking a copy of the value in `pred` but without the properties in `props`.
*/
predicate withoutPropStep(DataFlow::Node pred, DataFlow::Node succ, PropertySet props) { none() }
}
/**
* A representative for a set of property names.
*
* Currently this is used to denote a set of properties in `withoutPropStep`.
*/
abstract class PropertySet extends string {
bindingset[this]
PropertySet() { any() }
/** Gets a property contained in this property set. */
abstract string getAProperty();
}
/** Provides access to the steps contributed by subclasses of `SharedTypeTrackingStep`. */
@@ -413,59 +439,13 @@ module SharedTypeTrackingStep {
) {
any(SharedTypeTrackingStep s).loadStoreStep(pred, succ, loadProp, storeProp)
}
}
/**
* DEPRECATED. Use `SharedTypeTrackingStep` instead.
*
* A data flow edge that should be followed by type tracking.
*
* Unlike `AdditionalFlowStep`, this type of edge does not affect
* the local data flow graph, and is not used by data-flow configurations.
*
* Note: For performance reasons, all subclasses of this class should be part
* of the standard library. For query-specific steps, consider including the
* custom steps in the type-tracking predicate itself.
*/
deprecated class AdditionalTypeTrackingStep = LegacyTypeTrackingStep;
// Internal version of AdditionalTypeTrackingStep that we can reference without deprecation warnings.
abstract private class LegacyTypeTrackingStep extends DataFlow::Node {
/**
* Holds if type-tracking should step from `pred` to `succ`.
*/
predicate step(DataFlow::Node pred, DataFlow::Node succ) { none() }
/**
* Holds if type-tracking should step from `pred` into the `prop` property of `succ`.
* Holds if type-tracking should step from `pred` to `succ` but block flow of `prop` through here.
*
* This can be seen as taking a copy of the value in `pred` but without the properties in `props`.
*/
predicate storeStep(DataFlow::Node pred, DataFlow::SourceNode succ, string prop) { none() }
/**
* Holds if type-tracking should step from the `prop` property of `pred` to `succ`.
*/
predicate loadStep(DataFlow::Node pred, DataFlow::Node succ, string prop) { none() }
/**
* Holds if type-tracking should step from the `prop` property of `pred` to the same property in `succ`.
*/
predicate loadStoreStep(DataFlow::Node pred, DataFlow::SourceNode succ, string prop) { none() }
}
private class LegacyStepAsSharedTypeTrackingStep extends SharedTypeTrackingStep {
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
any(LegacyTypeTrackingStep s).step(pred, succ)
}
override predicate storeStep(DataFlow::Node pred, DataFlow::SourceNode succ, string prop) {
any(LegacyTypeTrackingStep s).storeStep(pred, succ, prop)
}
override predicate loadStep(DataFlow::Node pred, DataFlow::Node succ, string prop) {
any(LegacyTypeTrackingStep s).loadStep(pred, succ, prop)
}
override predicate loadStoreStep(DataFlow::Node pred, DataFlow::SourceNode succ, string prop) {
any(LegacyTypeTrackingStep s).loadStoreStep(pred, succ, prop)
predicate withoutPropStep(DataFlow::Node pred, DataFlow::Node succ, PropertySet props) {
any(SharedTypeTrackingStep s).withoutPropStep(pred, succ, props)
}
}

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