Merge branch 'main' into codeql-ci/js/ml-powered-pack-release-0.3.3

This commit is contained in:
Henry Mercer
2022-09-13 15:15:56 +01:00
committed by GitHub
5550 changed files with 471715 additions and 181168 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.8.2"
},
"scripts": {
"build": "tsc --project tsconfig.json",

View File

@@ -168,20 +168,9 @@ export function augmentAst(ast: AugmentedSourceFile, code: string, project: Proj
}
}
// Number of conditional type expressions the visitor is currently inside.
// We disable type extraction inside such type expressions, to avoid complications
// with `infer` types.
let insideConditionalTypes = 0;
visitAstNode(ast);
function visitAstNode(node: AugmentedNode) {
if (node.kind === ts.SyntaxKind.ConditionalType) {
++insideConditionalTypes;
}
ts.forEachChild(node, visitAstNode);
if (node.kind === ts.SyntaxKind.ConditionalType) {
--insideConditionalTypes;
}
// fill in line/column info
if ("pos" in node) {
@@ -202,7 +191,7 @@ export function augmentAst(ast: AugmentedSourceFile, code: string, project: Proj
}
}
if (typeChecker != null && insideConditionalTypes === 0) {
if (typeChecker != null) {
if (isTypedNode(node)) {
let contextualType = isContextuallyTypedNode(node)
? typeChecker.getContextualType(node)

View File

@@ -241,7 +241,7 @@ const astProperties: string[] = [
"constructor",
"declarationList",
"declarations",
"decorators",
"illegalDecorators",
"default",
"delete",
"dotDotDotToken",
@@ -670,6 +670,12 @@ function handleOpenProjectCommand(command: OpenProjectCommand) {
if (file.endsWith(".d.ts")) {
return pathlib.basename(file, ".d.ts");
}
if (file.endsWith(".d.mts") || file.endsWith(".d.cts")) {
// We don't extract d.mts or d.cts files, but their symbol can coincide with that of a d.ts file,
// which means any module bindings we generate for it will ultimately be visible in QL.
let base = pathlib.basename(file);
return base.substring(0, base.length - '.d.mts'.length);
}
let base = pathlib.basename(file);
let dot = base.lastIndexOf('.');
return dot === -1 || dot === 0 ? base : base.substring(0, dot);

View File

@@ -947,7 +947,7 @@ export class TypeTable {
* Returns a unique string for the given call/constructor signature.
*/
private getSignatureString(kind: ts.SignatureKind, signature: AugmentedSignature): string {
let modifiers : ts.ModifiersArray = signature.getDeclaration()?.modifiers;
let modifiers = signature.getDeclaration()?.modifiers;
let isAbstract = modifiers && modifiers.filter(modifier => modifier.kind == ts.SyntaxKind.AbstractKeyword).length > 0
let parameters = signature.getParameters();
@@ -1068,6 +1068,7 @@ export class TypeTable {
let superType = this.typeChecker.getTypeFromTypeNode(typeExpr);
if (superType == null) continue;
let baseTypeSymbol = superType.symbol;
baseTypeSymbol = (baseTypeSymbol as any)?.type?.symbol ?? baseTypeSymbol;
if (baseTypeSymbol == null) continue;
let baseId = this.getSymbolId(baseTypeSymbol);
// Note: take care not to perform a recursive call between the two `push` calls.

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.8.2:
version "4.8.2"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.8.2.tgz#e3b33d5ccfb5914e4eeab6699cf208adee3fd790"
integrity sha512-C0I1UsrrDHo2fYI5oaCGbSejwX4ch+9Y5jTQELvovfmFkK3HHSZJB8MSJcWLmCUBzQBchCrZ9rMRV6GuNrvGtw==

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-08-25";
public static final Pattern NEWLINE = Pattern.compile("\n");
@@ -61,8 +59,6 @@ public class Main {
private static final String P_PLATFORM = "--platform";
private static final String P_QUIET = "--quiet";
private static final String P_SOURCE_TYPE = "--source-type";
private static final String P_TRAP_CACHE = "--trap-cache";
private static final String P_TRAP_CACHE_BOUND = "--trap-cache-bound";
private static final String P_TYPESCRIPT = "--typescript";
private static final String P_TYPESCRIPT_FULL = "--typescript-full";
private static final String P_TYPESCRIPT_RAM = "--typescript-ram";
@@ -112,22 +108,7 @@ public class Main {
ap.parse();
extractorConfig = parseJSOptions(ap);
ITrapCache trapCache;
if (ap.has(P_TRAP_CACHE)) {
Long sizeBound = null;
if (ap.has(P_TRAP_CACHE_BOUND)) {
String tcb = ap.getString(P_TRAP_CACHE_BOUND);
sizeBound = DefaultTrapCache.asFileSize(tcb);
if (sizeBound == null) ap.error("Invalid TRAP cache size bound: " + tcb);
}
trapCache = new DefaultTrapCache(ap.getString(P_TRAP_CACHE), sizeBound, EXTRACTOR_VERSION);
} else {
if (ap.has(P_TRAP_CACHE_BOUND))
ap.error(
P_TRAP_CACHE_BOUND + " should only be specified together with " + P_TRAP_CACHE + ".");
trapCache = new DummyTrapCache();
}
fileExtractor = new FileExtractor(extractorConfig, extractorOutputConfig, trapCache);
fileExtractor = new FileExtractor(extractorConfig, extractorOutputConfig, ITrapCache.fromExtractorOptions());
setupMatchers(ap);
@@ -153,7 +134,7 @@ public class Main {
ensureFileIsExtracted(file, ap);
}
}
TypeScriptParser tsParser = extractorState.getTypeScriptParser();
tsParser.setTypescriptRam(extractorConfig.getTypeScriptRam());
if (containsTypeScriptFiles()) {
@@ -432,12 +413,6 @@ public class Main {
argsParser.addToleratedFlag(P_TOLERATE_PARSE_ERRORS, 0);
argsParser.addFlag(
P_ABORT_ON_PARSE_ERRORS, 0, "Abort extraction if a parse error is encountered.");
argsParser.addFlag(P_TRAP_CACHE, 1, "Use the given directory as the TRAP cache.");
argsParser.addFlag(
P_TRAP_CACHE_BOUND,
1,
"A (soft) upper limit on the size of the TRAP cache, "
+ "in standard size units (e.g., 'g' for gigabytes).");
argsParser.addFlag(P_DEFAULT_ENCODING, 1, "The encoding to use; default is UTF-8.");
argsParser.addFlag(P_TYPESCRIPT, 0, "Enable basic TypesScript support.");
argsParser.addFlag(
@@ -460,7 +435,7 @@ public class Main {
if (ap.has(P_TYPESCRIPT)) return TypeScriptMode.BASIC;
return TypeScriptMode.NONE;
}
private Path inferSourceRoot(ArgsParser ap) {
List<File> files = getFilesArg(ap);
Path sourceRoot = files.iterator().next().toPath().toAbsolutePath().getParent();

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);
@@ -976,8 +976,9 @@ public class TypeScriptASTConverter {
hasDeclareKeyword,
hasAbstractKeyword);
attachSymbolInformation(classDecl.getClassDef(), node);
if (node.has("decorators")) {
classDecl.addDecorators(convertChildren(node, "decorators"));
List<Decorator> decorators = getDecorators(node);
if (!decorators.isEmpty()) {
classDecl.addDecorators(decorators);
advanceUntilAfter(loc, classDecl.getDecorators());
}
Node exportedDecl = fixExports(loc, classDecl);
@@ -989,6 +990,17 @@ public class TypeScriptASTConverter {
return exportedDecl;
}
List<Decorator> getDecorators(JsonObject node) throws ParseError {
List<Decorator> result = new ArrayList<>();
for (JsonElement elt : getChildIterable(node, "modifiers")) {
JsonObject modifier = elt.getAsJsonObject();
if (hasKind(modifier, "Decorator")) {
result.add((Decorator) convertNode(modifier));
}
}
return result;
}
private Node convertCommaListExpression(JsonObject node, SourceLocation loc) throws ParseError {
return new SequenceExpression(loc, convertChildren(node, "elements"));
}
@@ -1041,7 +1053,7 @@ public class TypeScriptASTConverter {
private List<DecoratorList> convertParameterDecorators(JsonObject function) throws ParseError {
List<DecoratorList> decoratorLists = new ArrayList<>();
for (JsonElement parameter : getProperParameters(function)) {
decoratorLists.add(makeDecoratorList(parameter.getAsJsonObject().get("decorators")));
decoratorLists.add(makeDecoratorList(parameter.getAsJsonObject().get("modifiers")));
}
return decoratorLists;
}
@@ -1139,7 +1151,7 @@ public class TypeScriptASTConverter {
loc,
hasModifier(node, "ConstKeyword"),
hasModifier(node, "DeclareKeyword"),
convertChildrenNotNull(node, "decorators"),
convertChildrenNotNull(node, "illegalDecorators"), // as of https://github.com/microsoft/TypeScript/pull/50343/ the property is called `illegalDecorators` instead of `decorators`
convertChild(node, "name"),
convertChildren(node, "members"));
attachSymbolInformation(enumDeclaration, node);
@@ -1664,8 +1676,9 @@ public class TypeScriptASTConverter {
FunctionExpression method = convertImplicitFunction(node, loc);
MethodDefinition methodDefinition =
new MethodDefinition(loc, flags, methodKind, convertChild(node, "name"), method);
if (node.has("decorators")) {
methodDefinition.addDecorators(convertChildren(node, "decorators"));
List<Decorator> decorators = getDecorators(node);
if (!decorators.isEmpty()) {
methodDefinition.addDecorators(decorators);
advanceUntilAfter(loc, methodDefinition.getDecorators());
}
return methodDefinition;
@@ -1710,7 +1723,9 @@ public class TypeScriptASTConverter {
}
if (nameNode instanceof Literal) {
// Declaration of form: declare module "X" {...}
return new ExternalModuleDeclaration(loc, (Literal) nameNode, body);
ExternalModuleDeclaration decl = new ExternalModuleDeclaration(loc, (Literal) nameNode, body);
attachSymbolInformation(decl, node);
return decl;
}
if (hasFlag(node, "GlobalAugmentation")) {
// Declaration of form: declare global {...}
@@ -2077,8 +2092,9 @@ public class TypeScriptASTConverter {
convertChild(node, "name"),
convertChild(node, "initializer"),
convertChildAsType(node, "type"));
if (node.has("decorators")) {
fieldDefinition.addDecorators(convertChildren(node, "decorators"));
List<Decorator> decorators = getDecorators(node);
if (!decorators.isEmpty()) {
fieldDefinition.addDecorators(decorators);
advanceUntilAfter(loc, fieldDefinition.getDecorators());
}
return fieldDefinition;

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

@@ -175,7 +175,7 @@ predicate isOtherModeledArgument(DataFlow::Node n, FilteringReason reason) {
or
n instanceof CryptographicKey and reason instanceof CryptographicKeyReason
or
any(CryptographicOperation op).getInput().flow() = n and
any(CryptographicOperation op).getInput() = n and
reason instanceof CryptographicOperationFlowReason
or
exists(DataFlow::CallNode call | n = call.getAnArgument() |

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().(DataFlow::ParameterNode).getName()
or
param.getAnImmediateUse().asExpr() instanceof DestructuringPattern and
param.asSource().asExpr() instanceof DestructuringPattern and
result = param.getMember(name)
)
}

View File

@@ -42,10 +42,10 @@ module SinkEndpointFilter {
result = "modeled database access"
or
// Remove calls to APIs that aren't relevant to NoSQL injection
call.getReceiver().asExpr() instanceof HTTP::RequestExpr and
call.getReceiver() instanceof HTTP::RequestNode and
result = "receiver is a HTTP request expression"
or
call.getReceiver().asExpr() instanceof HTTP::ResponseExpr and
call.getReceiver() instanceof HTTP::ResponseNode and
result = "receiver is a HTTP response expression"
)
or
@@ -115,7 +115,7 @@ predicate isBaseAdditionalFlowStep(
inlbl = TaintedObject::label() and
outlbl = TaintedObject::label() and
exists(NoSql::Query query, DataFlow::SourceNode queryObj |
queryObj.flowsToExpr(query) and
queryObj.flowsTo(query) and
queryObj.flowsTo(trg) and
src = queryObj.getAPropertyWrite().getRhs()
)

View File

@@ -15,16 +15,16 @@ import extraction.ExtractEndpointData
string getAReasonSinkExcluded(DataFlow::Node sinkCandidate, Query query) {
query instanceof NosqlInjectionQuery and
result = NosqlInjectionATM::SinkEndpointFilter::getAReasonSinkExcluded(sinkCandidate)
result = NosqlInjectionAtm::SinkEndpointFilter::getAReasonSinkExcluded(sinkCandidate)
or
query instanceof SqlInjectionQuery and
result = SqlInjectionATM::SinkEndpointFilter::getAReasonSinkExcluded(sinkCandidate)
result = SqlInjectionAtm::SinkEndpointFilter::getAReasonSinkExcluded(sinkCandidate)
or
query instanceof TaintedPathQuery and
result = TaintedPathATM::SinkEndpointFilter::getAReasonSinkExcluded(sinkCandidate)
result = TaintedPathAtm::SinkEndpointFilter::getAReasonSinkExcluded(sinkCandidate)
or
query instanceof XssQuery and
result = XssATM::SinkEndpointFilter::getAReasonSinkExcluded(sinkCandidate)
result = XssAtm::SinkEndpointFilter::getAReasonSinkExcluded(sinkCandidate)
}
pragma[inline]

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

@@ -14,10 +14,26 @@ import experimental.adaptivethreatmodeling.EndpointFeatures as EndpointFeatures
import experimental.adaptivethreatmodeling.EndpointScoring as EndpointScoring
import experimental.adaptivethreatmodeling.EndpointTypes
import experimental.adaptivethreatmodeling.FilteringReasons
import experimental.adaptivethreatmodeling.NosqlInjectionATM as NosqlInjectionATM
import experimental.adaptivethreatmodeling.SqlInjectionATM as SqlInjectionATM
import experimental.adaptivethreatmodeling.TaintedPathATM as TaintedPathATM
import experimental.adaptivethreatmodeling.XssATM as XssATM
import experimental.adaptivethreatmodeling.NosqlInjectionATM as NosqlInjectionAtm
/** DEPRECATED: Alias for NosqlInjectionAtm */
deprecated module NosqlInjectionATM = NosqlInjectionAtm;
import experimental.adaptivethreatmodeling.SqlInjectionATM as SqlInjectionAtm
/** DEPRECATED: Alias for SqlInjectionAtm */
deprecated module SqlInjectionATM = SqlInjectionAtm;
import experimental.adaptivethreatmodeling.TaintedPathATM as TaintedPathAtm
/** DEPRECATED: Alias for TaintedPathAtm */
deprecated module TaintedPathATM = TaintedPathAtm;
import experimental.adaptivethreatmodeling.XssATM as XssAtm
/** DEPRECATED: Alias for XssAtm */
deprecated module XssATM = XssAtm;
import Labels
import NoFeaturizationRestrictionsConfig
import Queries
@@ -25,13 +41,13 @@ import Queries
/** Gets the ATM configuration object for the specified query. */
AtmConfig getAtmCfg(Query query) {
query instanceof NosqlInjectionQuery and
result instanceof NosqlInjectionATM::NosqlInjectionAtmConfig
result instanceof NosqlInjectionAtm::NosqlInjectionAtmConfig
or
query instanceof SqlInjectionQuery and result instanceof SqlInjectionATM::SqlInjectionAtmConfig
query instanceof SqlInjectionQuery and result instanceof SqlInjectionAtm::SqlInjectionAtmConfig
or
query instanceof TaintedPathQuery and result instanceof TaintedPathATM::TaintedPathAtmConfig
query instanceof TaintedPathQuery and result instanceof TaintedPathAtm::TaintedPathAtmConfig
or
query instanceof XssQuery and result instanceof XssATM::DomBasedXssAtmConfig
query instanceof XssQuery and result instanceof XssAtm::DomBasedXssAtmConfig
}
/** DEPRECATED: Alias for getAtmCfg */
@@ -39,13 +55,13 @@ deprecated ATMConfig getATMCfg(Query query) { result = getAtmCfg(query) }
/** Gets the ATM data flow configuration for the specified query. */
DataFlow::Configuration getDataFlowCfg(Query query) {
query instanceof NosqlInjectionQuery and result instanceof NosqlInjectionATM::Configuration
query instanceof NosqlInjectionQuery and result instanceof NosqlInjectionAtm::Configuration
or
query instanceof SqlInjectionQuery and result instanceof SqlInjectionATM::Configuration
query instanceof SqlInjectionQuery and result instanceof SqlInjectionAtm::Configuration
or
query instanceof TaintedPathQuery and result instanceof TaintedPathATM::Configuration
query instanceof TaintedPathQuery and result instanceof TaintedPathAtm::Configuration
or
query instanceof XssQuery and result instanceof XssATM::Configuration
query instanceof XssQuery and result instanceof XssAtm::Configuration
}
/** Gets a known sink for the specified query. */
@@ -188,10 +204,10 @@ module FlowFromSource {
Query getQuery() { result = q }
/** The sinks are the endpoints we're extracting. */
/** Holds if `sink` is an endpoint we're extracting. */
override predicate isSink(DataFlow::Node sink) { sink = getAnEndpoint(q) }
/** The sinks are the endpoints we're extracting. */
/** Holds if `sink` is an endpoint we're extracting. */
override predicate isSink(DataFlow::Node sink, DataFlow::FlowLabel lbl) {
sink = getAnEndpoint(q) and exists(lbl)
}

View File

@@ -4,25 +4,25 @@
* Maps ML-powered queries to their `EndpointType` for clearer labelling while evaluating ML model during training.
*/
import experimental.adaptivethreatmodeling.SqlInjectionATM as SqlInjectionATM
import experimental.adaptivethreatmodeling.NosqlInjectionATM as NosqlInjectionATM
import experimental.adaptivethreatmodeling.TaintedPathATM as TaintedPathATM
import experimental.adaptivethreatmodeling.XssATM as XssATM
import experimental.adaptivethreatmodeling.SqlInjectionATM as SqlInjectionAtm
import experimental.adaptivethreatmodeling.NosqlInjectionATM as NosqlInjectionAtm
import experimental.adaptivethreatmodeling.TaintedPathATM as TaintedPathAtm
import experimental.adaptivethreatmodeling.XssATM as XssAtm
import experimental.adaptivethreatmodeling.AdaptiveThreatModeling
from string queryName, AtmConfig c, EndpointType e
where
(
queryName = "SqlInjection" and
c instanceof SqlInjectionATM::SqlInjectionAtmConfig
c instanceof SqlInjectionAtm::SqlInjectionAtmConfig
or
queryName = "NosqlInjection" and
c instanceof NosqlInjectionATM::NosqlInjectionAtmConfig
c instanceof NosqlInjectionAtm::NosqlInjectionAtmConfig
or
queryName = "TaintedPath" and
c instanceof TaintedPathATM::TaintedPathAtmConfig
c instanceof TaintedPathAtm::TaintedPathAtmConfig
or
queryName = "Xss" and c instanceof XssATM::DomBasedXssAtmConfig
queryName = "Xss" and c instanceof XssAtm::DomBasedXssAtmConfig
) and
e = c.getASinkEndpointType()
select queryName, e.getEncoding() as label

View File

@@ -7,20 +7,20 @@
*/
import javascript
import experimental.adaptivethreatmodeling.NosqlInjectionATM as NosqlInjectionATM
import experimental.adaptivethreatmodeling.SqlInjectionATM as SqlInjectionATM
import experimental.adaptivethreatmodeling.TaintedPathATM as TaintedPathATM
import experimental.adaptivethreatmodeling.XssATM as XssATM
import experimental.adaptivethreatmodeling.NosqlInjectionATM as NosqlInjectionAtm
import experimental.adaptivethreatmodeling.SqlInjectionATM as SqlInjectionAtm
import experimental.adaptivethreatmodeling.TaintedPathATM as TaintedPathAtm
import experimental.adaptivethreatmodeling.XssATM as XssAtm
import experimental.adaptivethreatmodeling.EndpointFeatures as EndpointFeatures
import experimental.adaptivethreatmodeling.StandardEndpointFilters as StandardEndpointFilters
import extraction.NoFeaturizationRestrictionsConfig
query predicate tokenFeatures(DataFlow::Node endpoint, string featureName, string featureValue) {
(
not exists(NosqlInjectionATM::SinkEndpointFilter::getAReasonSinkExcluded(endpoint)) or
not exists(SqlInjectionATM::SinkEndpointFilter::getAReasonSinkExcluded(endpoint)) or
not exists(TaintedPathATM::SinkEndpointFilter::getAReasonSinkExcluded(endpoint)) or
not exists(XssATM::SinkEndpointFilter::getAReasonSinkExcluded(endpoint)) or
not exists(NosqlInjectionAtm::SinkEndpointFilter::getAReasonSinkExcluded(endpoint)) or
not exists(SqlInjectionAtm::SinkEndpointFilter::getAReasonSinkExcluded(endpoint)) or
not exists(TaintedPathAtm::SinkEndpointFilter::getAReasonSinkExcluded(endpoint)) or
not exists(XssAtm::SinkEndpointFilter::getAReasonSinkExcluded(endpoint)) or
StandardEndpointFilters::isArgumentToModeledFunction(endpoint)
) and
EndpointFeatures::tokenFeatures(endpoint, featureName, featureValue)

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.
*
@@ -17,31 +17,31 @@ import semmle.javascript.security.dataflow.SqlInjectionCustomizations
import semmle.javascript.security.dataflow.TaintedPathCustomizations
import semmle.javascript.security.dataflow.DomBasedXssCustomizations
import experimental.adaptivethreatmodeling.StandardEndpointFilters as StandardEndpointFilters
import experimental.adaptivethreatmodeling.NosqlInjectionATM as NosqlInjectionATM
import experimental.adaptivethreatmodeling.SqlInjectionATM as SqlInjectionATM
import experimental.adaptivethreatmodeling.TaintedPathATM as TaintedPathATM
import experimental.adaptivethreatmodeling.XssATM as XssATM
import experimental.adaptivethreatmodeling.NosqlInjectionATM as NosqlInjectionAtm
import experimental.adaptivethreatmodeling.SqlInjectionATM as SqlInjectionAtm
import experimental.adaptivethreatmodeling.TaintedPathATM as TaintedPathAtm
import experimental.adaptivethreatmodeling.XssATM as XssAtm
query predicate nosqlFilteredTruePositives(DataFlow::Node endpoint, string reason) {
endpoint instanceof NosqlInjection::Sink and
reason = NosqlInjectionATM::SinkEndpointFilter::getAReasonSinkExcluded(endpoint) and
reason = NosqlInjectionAtm::SinkEndpointFilter::getAReasonSinkExcluded(endpoint) and
not reason = ["argument to modeled function", "modeled sink", "modeled database access"]
}
query predicate sqlFilteredTruePositives(DataFlow::Node endpoint, string reason) {
endpoint instanceof SqlInjection::Sink and
reason = SqlInjectionATM::SinkEndpointFilter::getAReasonSinkExcluded(endpoint) and
reason = SqlInjectionAtm::SinkEndpointFilter::getAReasonSinkExcluded(endpoint) and
reason != "argument to modeled function"
}
query predicate taintedPathFilteredTruePositives(DataFlow::Node endpoint, string reason) {
endpoint instanceof TaintedPath::Sink and
reason = TaintedPathATM::SinkEndpointFilter::getAReasonSinkExcluded(endpoint) and
reason = TaintedPathAtm::SinkEndpointFilter::getAReasonSinkExcluded(endpoint) and
reason != "argument to modeled function"
}
query predicate xssFilteredTruePositives(DataFlow::Node endpoint, string reason) {
endpoint instanceof DomBasedXss::Sink and
reason = XssATM::SinkEndpointFilter::getAReasonSinkExcluded(endpoint) and
reason = XssAtm::SinkEndpointFilter::getAReasonSinkExcluded(endpoint) and
reason != "argument to modeled function"
}

View File

@@ -1,6 +1,6 @@
import javascript
import experimental.adaptivethreatmodeling.NosqlInjectionATM as NosqlInjectionATM
import experimental.adaptivethreatmodeling.NosqlInjectionATM as NosqlInjectionAtm
query predicate effectiveSinks(DataFlow::Node node) {
not exists(NosqlInjectionATM::SinkEndpointFilter::getAReasonSinkExcluded(node))
not exists(NosqlInjectionAtm::SinkEndpointFilter::getAReasonSinkExcluded(node))
}

View File

@@ -1,3 +1,50 @@
## 0.2.4
### Deprecated APIs
* Many classes/predicates/modules with upper-case acronyms in their name have been renamed to follow our style-guide.
The old name still exists as a deprecated alias.
* The utility files previously in the `semmle.javascript.security.performance` package have been moved to the `semmle.javascript.security.regexp` package.
The previous files still exist as deprecated aliases.
### Minor Analysis Improvements
* Most deprecated predicates/classes/modules that have been deprecated for over a year have been deleted.
### Bug Fixes
* Fixed that top-level `for await` statements would produce a syntax error. These statements are now parsed correctly.
## 0.2.3
## 0.2.2
## 0.2.1
### Minor Analysis Improvements
* The `chownr` library is now modeled as a sink for the `js/path-injection` query.
* Improved modeling of sensitive data sources, so common words like `certain` and `secretary` are no longer considered a certificate and a secret (respectively).
* The `gray-matter` library is now modeled as a sink for the `js/code-injection` query.
## 0.2.0
### Major Analysis Improvements
* Added support for TypeScript 4.7.
### Minor Analysis Improvements
* All new ECMAScript 2022 features are now supported.
## 0.1.4
## 0.1.3
### Minor Analysis Improvements
* The `isLibaryFile` predicate from `ClassifyFiles.qll` has been renamed to `isLibraryFile` to fix a typo.
## 0.1.2
### Deprecated APIs

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,57 @@
---
category: breaking
---
* Many library models have been rewritten to use dataflow nodes instead of the AST.
The types of some classes have been changed, and these changes may break existing code.
Other classes and predicates have been renamed, in these cases the old name is still available as a deprecated feature.
* The basetype of the following list of classes has changed from an expression to a dataflow node, and thus code using these classes might break.
The fix to these breakages is usually to use `asExpr()` to get an expression from a dataflow node, or to use `.flow()` to get a dataflow node from an expression.
- DOM.qll#WebStorageWrite
- CryptoLibraries.qll#CryptographicOperation
- Express.qll#Express::RequestBodyAccess
- HTTP.qll#HTTP::ResponseBody
- HTTP.qll#HTTP::CookieDefinition
- HTTP.qll#HTTP::ServerDefinition
- HTTP.qll#HTTP::RouteSetup
- NoSQL.qll#NoSql::Query
- SQL.qll#SQL::SqlString
- SQL.qll#SQL::SqlSanitizer
- HTTP.qll#ResponseBody
- HTTP.qll#CookieDefinition
- HTTP.qll#ServerDefinition
- HTTP.qll#RouteSetup
- HTTP.qll#HTTP::RedirectInvocation
- HTTP.qll#RedirectInvocation
- Express.qll#Express::RouterDefinition
- AngularJSCore.qll#LinkFunction
- Connect.qll#Connect::StandardRouteHandler
- CryptoLibraries.qll#CryptographicKeyCredentialsExpr
- AWS.qll#AWS::Credentials
- Azure.qll#Azure::Credentials
- Connect.qll#Connect::Credentials
- DigitalOcean.qll#DigitalOcean::Credentials
- Express.qll#Express::Credentials
- NodeJSLib.qll#NodeJSLib::Credentials
- PkgCloud.qll#PkgCloud::Credentials
- Request.qll#Request::Credentials
- ServiceDefinitions.qll#InjectableFunctionServiceRequest
- SensitiveActions.qll#SensitiveVariableAccess
- SensitiveActions.qll#CleartextPasswordExpr
- Connect.qll#Connect::ServerDefinition
- Restify.qll#Restify::ServerDefinition
- Connect.qll#Connect::RouteSetup
- Express.qll#Express::RouteSetup
- Fastify.qll#Fastify::RouteSetup
- Hapi.qll#Hapi::RouteSetup
- Koa.qll#Koa::RouteSetup
- Restify.qll#Restify::RouteSetup
- NodeJSLib.qll#NodeJSLib::RouteSetup
- Express.qll#Express::StandardRouteHandler
- Express.qll#Express::SetCookie
- Hapi.qll#Hapi::RouteHandler
- HTTP.qll#HTTP::Servers::StandardHeaderDefinition
- HTTP.qll#Servers::StandardHeaderDefinition
- Hapi.qll#Hapi::ServerDefinition
- Koa.qll#Koa::AppDefinition
- SensitiveActions.qll#SensitiveCall

View File

@@ -0,0 +1,4 @@
---
category: majorAnalysis
---
* Added support for TypeScript 4.8.

View File

@@ -0,0 +1,4 @@
---
category: minorAnalysis
---
* A model for the `mermaid` library has been added. XSS queries can now detect flow through the `render` method of the `mermaid` library.

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

@@ -0,0 +1,16 @@
## 0.2.4
### Deprecated APIs
* Many classes/predicates/modules with upper-case acronyms in their name have been renamed to follow our style-guide.
The old name still exists as a deprecated alias.
* The utility files previously in the `semmle.javascript.security.performance` package have been moved to the `semmle.javascript.security.regexp` package.
The previous files still exist as deprecated aliases.
### Minor Analysis Improvements
* Most deprecated predicates/classes/modules that have been deprecated for over a year have been deleted.
### Bug Fixes
* Fixed that top-level `for await` statements would produce a syntax error. These statements are now parsed correctly.

View File

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

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

@@ -11,7 +11,7 @@ import javascript
*/
module Actions {
/** A YAML node in a GitHub Actions workflow file. */
private class Node extends YAMLNode {
private class Node extends YamlNode {
Node() {
this.getLocation()
.getFile()
@@ -24,9 +24,12 @@ module Actions {
* An Actions workflow. This is a mapping at the top level of an Actions YAML workflow file.
* See https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions.
*/
class Workflow extends Node, YAMLDocument, YAMLMapping {
class Workflow extends Node, YamlDocument, YamlMapping {
/** Gets the `jobs` mapping from job IDs to job definitions in this workflow. */
YAMLMapping getJobs() { result = this.lookup("jobs") }
YamlMapping getJobs() { result = this.lookup("jobs") }
/** Gets the name of the workflow. */
string getName() { result = this.lookup("name").(YamlString).getValue() }
/** Gets the name of the workflow file. */
string getFileName() { result = this.getFile().getBaseName() }
@@ -42,7 +45,7 @@ module Actions {
* An Actions On trigger within a workflow.
* See https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#on.
*/
class On extends YAMLNode, YAMLMappingLikeNode {
class On extends YamlNode, YamlMappingLikeNode {
Workflow workflow;
On() { workflow.lookup("on") = this }
@@ -55,7 +58,7 @@ module Actions {
* An Actions job within a workflow.
* See https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobs.
*/
class Job extends YAMLNode, YAMLMapping {
class Job extends YamlNode, YamlMapping {
string jobId;
Workflow workflow;
@@ -71,19 +74,19 @@ module Actions {
* Gets the ID of this job, as a YAML scalar node.
* This is the job's key within the `jobs` mapping.
*/
YAMLString getIdNode() { workflow.getJobs().maps(result, this) }
YamlString getIdNode() { workflow.getJobs().maps(result, this) }
/** Gets the human-readable name of this job, if any, as a string. */
string getName() { result = this.getNameNode().getValue() }
/** Gets the human-readable name of this job, if any, as a YAML scalar node. */
YAMLString getNameNode() { result = this.lookup("name") }
YamlString getNameNode() { result = this.lookup("name") }
/** Gets the step at the given index within this job. */
Step getStep(int index) { result.getJob() = this and result.getIndex() = index }
/** Gets the sequence of `steps` within this job. */
YAMLSequence getSteps() { result = this.lookup("steps") }
YamlSequence getSteps() { result = this.lookup("steps") }
/** Gets the workflow this job belongs to. */
Workflow getWorkflow() { result = workflow }
@@ -96,7 +99,7 @@ module Actions {
* An `if` within a job.
* See https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idif.
*/
class JobIf extends YAMLNode, YAMLScalar {
class JobIf extends YamlNode, YamlScalar {
Job job;
JobIf() { job.lookup("if") = this }
@@ -109,7 +112,7 @@ module Actions {
* A step within an Actions job.
* See https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idsteps.
*/
class Step extends YAMLNode, YAMLMapping {
class Step extends YamlNode, YamlMapping {
int index;
Job job;
@@ -129,13 +132,16 @@ module Actions {
/** Gets the value of the `if` field in this step, if any. */
StepIf getIf() { result.getStep() = this }
/** Gets the ID of this step, if any. */
string getId() { result = this.lookup("id").(YamlString).getValue() }
}
/**
* An `if` within a step.
* See https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idstepsif.
*/
class StepIf extends YAMLNode, YAMLScalar {
class StepIf extends YamlNode, YamlScalar {
Step step;
StepIf() { step.lookup("if") = this }
@@ -164,7 +170,7 @@ module Actions {
*
* Does not handle local repository references, e.g. `.github/actions/action-name`.
*/
class Uses extends YAMLNode, YAMLScalar {
class Uses extends YamlNode, YamlScalar {
Step step;
Uses() { step.lookup("uses") = this }
@@ -194,7 +200,7 @@ module Actions {
* arg2: abc
* ```
*/
class With extends YAMLNode, YAMLMapping {
class With extends YamlNode, YamlMapping {
Step step;
With() { step.lookup("with") = this }
@@ -213,7 +219,7 @@ module Actions {
* ref: ${{ github.event.pull_request.head.sha }}
* ```
*/
class Ref extends YAMLNode, YAMLString {
class Ref extends YamlNode, YamlString {
With with;
Ref() { with.lookup("ref") = this }
@@ -226,7 +232,7 @@ module Actions {
* A `run` field within an Actions job step, which runs command-line programs using an operating system shell.
* See https://docs.github.com/en/free-pro-team@latest/actions/reference/workflow-syntax-for-github-actions#jobsjob_idstepsrun.
*/
class Run extends YAMLNode, YAMLString {
class Run extends YamlNode, YamlString {
Step step;
Run() { step.lookup("run") = this }

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.
@@ -416,8 +533,9 @@ module API {
/** Gets a node corresponding to an import of module `m`. */
Node moduleImport(string m) {
result = Impl::MkModuleImport(m) or
result = Impl::MkModuleImport(m).(Node).getMember("default")
result = Internal::getAModuleImportRaw(m)
or
result = ModelOutput::getATypeNode(m, "")
}
/** Gets a node corresponding to an export of module `m`. */
@@ -427,6 +545,22 @@ module API {
module Node {
/** Gets a node whose type has the given qualified name. */
Node ofType(string moduleName, string exportedName) {
result = Internal::getANodeOfTypeRaw(moduleName, exportedName)
or
result = ModelOutput::getATypeNode(moduleName, exportedName)
}
}
/** Provides access to API graph nodes without taking into account types from models. */
module Internal {
/** Gets a node corresponding to an import of module `m` without taking into account types from models. */
Node getAModuleImportRaw(string m) {
result = Impl::MkModuleImport(m) or
result = Impl::MkModuleImport(m).(Node).getMember("default")
}
/** Gets a node whose type has the given qualified name, not including types from models. */
Node getANodeOfTypeRaw(string moduleName, string exportedName) {
result = Impl::MkTypeUse(moduleName, exportedName).(Node).getInstance()
}
}
@@ -445,11 +579,17 @@ module API {
bindingset[this]
EntryPoint() { any() }
/** Gets a data-flow node that uses this entry point. */
abstract DataFlow::SourceNode getAUse();
/** DEPRECATED. This predicate has been renamed to `getASource`. */
deprecated DataFlow::SourceNode getAUse() { none() }
/** Gets a data-flow node that defines this entry point. */
abstract DataFlow::Node getARhs();
/** DEPRECATED. This predicate has been renamed to `getASink`. */
deprecated DataFlow::SourceNode getARhs() { none() }
/** Gets a data-flow node where a value enters the current codebase through this entry-point. */
DataFlow::SourceNode getASource() { none() }
/** Gets a data-flow node where a value leaves the current codebase through this entry-point. */
DataFlow::Node getASink() { none() }
/** Gets an API-node for this entry point. */
API::Node getANode() { result = root().getASuccessor(Label::entryPoint(this)) }
@@ -523,7 +663,14 @@ module API {
or
any(Type t).hasUnderlyingType(m, _)
} or
MkClassInstance(DataFlow::ClassNode cls) { cls = trackDefNode(_) and hasSemantics(cls) } or
MkClassInstance(DataFlow::ClassNode cls) {
hasSemantics(cls) and
(
cls = trackDefNode(_)
or
cls.getAnInstanceReference() = trackDefNode(_)
)
} or
MkAsyncFuncResult(DataFlow::FunctionNode f) {
f = trackDefNode(_) and f.getFunction().isAsync() and hasSemantics(f)
} or
@@ -567,7 +714,7 @@ module API {
base = MkRoot() and
exists(EntryPoint e |
lbl = Label::entryPoint(e) and
rhs = e.getARhs()
rhs = e.getASink()
)
or
exists(string m, string prop |
@@ -615,16 +762,6 @@ module API {
.getStaticMember(name, DataFlow::MemberKind::getter())
.getAReturn()
)
or
// If `new C()` escapes, generate edges to its instance members
exists(DataFlow::ClassNode cls, string name |
pred = cls.getAClassReference().getAnInstantiation() and
lbl = Label::member(name)
|
rhs = cls.getInstanceMethod(name)
or
rhs = cls.getInstanceMember(name, DataFlow::MemberKind::getter()).getAReturn()
)
)
or
exists(DataFlow::ClassNode cls, string name |
@@ -744,7 +881,7 @@ module API {
base = MkRoot() and
exists(EntryPoint e |
lbl = Label::entryPoint(e) and
ref = e.getAUse()
ref = e.getASource()
)
or
// property reads
@@ -1113,9 +1250,13 @@ module API {
succ = MkUse(ref)
)
or
exists(DataFlow::Node rhs |
rhs(pred, lbl, rhs) and
exists(DataFlow::Node rhs | rhs(pred, lbl, rhs) |
succ = MkDef(rhs)
or
exists(DataFlow::ClassNode cls |
cls.getAnInstanceReference() = rhs and
succ = MkClassInstance(cls)
)
)
or
exists(DataFlow::Node def |
@@ -1178,8 +1319,8 @@ module API {
API::Node callee;
InvokeNode() {
this = callee.getReturn().getAnImmediateUse() or
this = callee.getInstance().getAnImmediateUse() or
this = callee.getReturn().asSource() or
this = callee.getInstance().asSource() or
this = Impl::getAPromisifiedInvocation(callee, _, _)
}
@@ -1194,7 +1335,7 @@ module API {
* Gets an API node where a RHS of the node is the `i`th argument to this call.
*/
pragma[noinline]
private Node getAParameterCandidate(int i) { result.getARhs() = this.getArgument(i) }
private Node getAParameterCandidate(int i) { result.asSink() = this.getArgument(i) }
/** Gets the API node for a parameter of this invocation. */
Node getAParameter() { result = this.getParameter(_) }
@@ -1205,13 +1346,13 @@ module API {
/** Gets the API node for the return value of this call. */
Node getReturn() {
result = callee.getReturn() and
result.getAnImmediateUse() = this
result.asSource() = this
}
/** Gets the API node for the object constructed by this invocation. */
Node getInstance() {
result = callee.getInstance() and
result.getAnImmediateUse() = this
result.asSource() = this
}
}
@@ -1354,7 +1495,7 @@ module API {
/** Gets the EntryPoint associated with this label. */
API::EntryPoint getEntryPoint() { result = e }
override string toString() { result = "getASuccessor(Label::entryPoint(\"" + e + "\"))" }
override string toString() { result = "entryPoint(\"" + e + "\")" }
}
/** A label that gets a promised value. */

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, _) |
@@ -493,6 +493,9 @@ class MemberDeclaration extends @property, Documentable {
*/
predicate isStatic() { is_static(this) }
/** Gets a boolean indicating if this member is static. */
boolean getStaticAsBool() { if this.isStatic() then result = true else result = false }
/**
* Holds if this member is abstract.
*
@@ -694,10 +697,10 @@ class MethodDeclaration extends MemberDeclaration {
* the overload index is defined as if only one of them was concrete.
*/
int getOverloadIndex() {
exists(ClassOrInterface type, string name |
exists(ClassOrInterface type, string name, boolean static |
this =
rank[result + 1](MethodDeclaration method, int i |
methodDeclaredInType(type, name, i, method)
methodDeclaredInType(type, name, static, i, method)
|
method order by i
)
@@ -718,10 +721,11 @@ class MethodDeclaration extends MemberDeclaration {
* Holds if the `index`th member of `type` is `method`, which has the given `name`.
*/
private predicate methodDeclaredInType(
ClassOrInterface type, string name, int index, MethodDeclaration method
ClassOrInterface type, string name, boolean static, int index, MethodDeclaration method
) {
not method instanceof ConstructorDeclaration and // distinguish methods named "constructor" from the constructor
type.getMemberByIndex(index) = method and
static = method.getStaticAsBool() and
method.getName() = name
}

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

@@ -167,7 +167,7 @@ class Expr extends @expr, ExprOrStmt, ExprOrType, AST::ValueNode {
/**
* Holds if this expression may refer to the initial value of parameter `p`.
*/
predicate mayReferToParameter(Parameter p) { this.flow().mayReferToParameter(p) }
predicate mayReferToParameter(Parameter p) { DataFlow::parameterNode(p).flowsToExpr(this) }
/**
* Gets the static type of this expression, as determined by the TypeScript type system.

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

@@ -178,7 +178,7 @@ predicate isGeneratedFileName(File f) {
predicate isGenerated(TopLevel tl) {
tl.isMinified() or
isBundle(tl) or
tl instanceof GWTGeneratedTopLevel or
tl instanceof GwtGeneratedTopLevel or
tl instanceof DartGeneratedTopLevel or
exists(GeneratedCodeMarkerComment gcmc | tl = gcmc.getTopLevel()) or
hasManyInvocations(tl) or

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",
@@ -43,7 +43,7 @@ class JsonStringifyCall extends DataFlow::CallNode {
/**
* A taint step through the [`json2csv`](https://www.npmjs.com/package/json2csv) library.
*/
class JSON2CSVTaintStep extends TaintTracking::SharedTaintStep {
class Json2CsvTaintStep extends TaintTracking::SharedTaintStep {
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
exists(API::CallNode call |
call =
@@ -59,6 +59,9 @@ class JSON2CSVTaintStep extends TaintTracking::SharedTaintStep {
}
}
/** DEPRECATED: Alias for Json2CsvTaintStep */
deprecated class JSON2CSVTaintStep = Json2CsvTaintStep;
/**
* A step through the [`prettyjson`](https://www.npmjs.com/package/prettyjson) library.
* This is not quite a `JSON.stringify` call, as it e.g. does not wrap keys in double quotes.

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) {
@@ -153,18 +168,24 @@ class PackageJson extends JsonObject {
JsonArray getCPUs() { result = this.getPropValue("cpu") }
/** Gets a platform supported by this package. */
string getWhitelistedCPU() {
string getWhitelistedCpu() {
result = this.getCPUs().getElementStringValue(_) and
not result.matches("!%")
}
/** DEPRECATED: Alias for getWhitelistedCpu */
deprecated string getWhitelistedCPU() { result = this.getWhitelistedCpu() }
/** Gets a platform not supported by this package. */
string getBlacklistedCPU() {
string getBlacklistedCpu() {
exists(string str | str = this.getCPUs().getElementStringValue(_) |
result = str.regexpCapture("!(.*)", 1)
)
}
/** DEPRECATED: Alias for getBlacklistedCpu */
deprecated string getBlacklistedCPU() { result = this.getBlacklistedCpu() }
/** Holds if this package prefers to be installed globally. */
predicate isPreferGlobal() { this.getPropValue("preferGlobal").(JsonBoolean).getValue() = "true" }
@@ -180,6 +201,47 @@ class PackageJson extends JsonObject {
Module getMainModule() {
result = min(Module m, int prio | m.getFile() = resolveMainModule(this, prio) | m order by prio)
}
/**
* Gets the module exported under the given relative path.
*
* The main module is considered exported under the path `"."`.
*/
Module getExportedModule(string relativePath) {
relativePath = "." and
result = this.getMainModule()
or
result.getFile() = MainModulePath::of(this, relativePath).resolve()
}
/**
* Gets the `types` or `typings` field of this package.
*/
string getTypings() { result = this.getPropStringValue(["types", "typings"]) }
/**
* Gets the file containing the typings of this package, which can either be from the `types` or
* `typings` field, or derived from the `main` or `module` fields.
*/
File getTypingsFile() {
result =
TypingsModulePathString::of(this).resolve(this.getFile().getParentContainer()).getContainer()
or
not exists(TypingsModulePathString::of(this)) and
exists(File mainFile |
mainFile = this.getMainModule().getFile() and
result =
mainFile
.getParentContainer()
.getFile(mainFile.getStem().regexpReplaceAll("\\.d$", "") + ".d.ts")
)
}
/**
* Gets the module containing the typings of this package, which can either be from the `types` or
* `typings` field, or derived from the `main` or `module` fields.
*/
Module getTypingsModule() { result.getFile() = this.getTypingsFile() }
}
/** DEPRECATED: Alias for PackageJson */

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) }
@@ -88,10 +90,11 @@ bindingset[name]
private string getStem(string name) { result = name.regexpCapture("(.+?)(?:\\.([^.]+))?", 1) }
/**
* Gets the main module described by `pkg` with the given `priority`.
* Gets a file that a main module from `pkg` exported as `mainPath` with the given `priority`.
* `mainPath` is "." if it's the main module of the package.
*/
File resolveMainModule(PackageJson pkg, int priority) {
exists(PathExpr main | main = MainModulePath::of(pkg) |
private File resolveMainPath(PackageJson pkg, string mainPath, int priority) {
exists(PathExpr main | main = MainModulePath::of(pkg, mainPath) |
result = main.resolve() and priority = 0
or
result = tryExtensions(main.resolve(), "index", priority)
@@ -100,6 +103,26 @@ File resolveMainModule(PackageJson pkg, int priority) {
exists(int n | n = main.getNumComponent() |
result = tryExtensions(main.resolveUpTo(n - 1), getStem(main.getComponent(n - 1)), priority)
)
or
// assuming the files get moved from one dir to another during compilation:
not exists(main.resolve()) and // didn't resolve
count(int i, string comp | comp = main.getComponent(i) and not comp = "." | i) = 2 and // is down one folder
exists(Folder subFolder | subFolder = pkg.getFile().getParentContainer().getAFolder() |
// is in one folder below the package.json, and has the right basename
result =
tryExtensions(subFolder, getStem(main.getComponent(main.getNumComponent() - 1)),
priority - 999) // very high priority, to make sure everything else is tried first
)
)
}
/**
* Gets the main module described by `pkg` with the given `priority`.
*/
File resolveMainModule(PackageJson pkg, int priority) {
exists(int subPriority, string mainPath |
result = resolveMainPath(pkg, mainPath, subPriority) and
if mainPath = "." then subPriority = priority else priority = subPriority + 1000
)
or
exists(Folder folder, Folder child |
@@ -140,17 +163,28 @@ File resolveMainModule(PackageJson pkg, int priority) {
private string getASrcFolderName() { result = ["ts", "js", "src", "lib"] }
/**
* A JSON string in a `package.json` file specifying the path of the main
* module of the package.
* A JSON string in a `package.json` file specifying the path of one of the exported
* modules of the package.
*/
class MainModulePath extends PathExpr, @json_string {
PackageJson pkg;
MainModulePath() { this = pkg.getPropValue(["main", "module"]) }
MainModulePath() {
this = pkg.getPropValue(["main", "module"])
or
this = getAPartOfExportsSection(pkg)
}
/** Gets the `package.json` file in which this path occurs. */
PackageJson getPackageJson() { result = pkg }
/** Gets the relative path under which this is exported, usually starting with a `.`. */
string getRelativePath() {
result = getExportRelativePath(this)
or
not exists(getExportRelativePath(this)) and result = "."
}
/** DEPRECATED: Alias for getPackageJson */
deprecated PackageJSON getPackageJSON() { result = getPackageJson() }
@@ -162,8 +196,38 @@ class MainModulePath extends PathExpr, @json_string {
}
}
private JsonValue getAPartOfExportsSection(PackageJson pkg) {
result = pkg.getPropValue("exports")
or
result = getAPartOfExportsSection(pkg).getPropValue(_)
}
/** Gets the text of one of the conditions or paths enclosing the given `part` of an `exports` section. */
private string getAnEnclosingExportProperty(JsonValue part) {
exists(JsonObject parent, string prop |
parent = getAPartOfExportsSection(_) and
part = parent.getPropValue(prop)
|
result = prop
or
result = getAnEnclosingExportProperty(parent)
)
}
private string getExportRelativePath(JsonValue part) {
result = getAnEnclosingExportProperty(part) and
result.matches(".%")
}
module MainModulePath {
MainModulePath of(PackageJson pkg) { result.getPackageJson() = pkg }
/** Gets the path to the main entry point of `pkg`. */
MainModulePath of(PackageJson pkg) { result = of(pkg, ".") }
/** Gets the path to the file exported from `pkg` as `relativePath`. */
MainModulePath of(PackageJson pkg, string relativePath) {
result.getPackageJson() = pkg and
result.getRelativePath() = relativePath
}
}
/**
@@ -176,7 +240,7 @@ private class FilesPath extends PathExpr, @json_string {
FilesPath() {
this = pkg.getPropValue("files").(JsonArray).getElementValue(_) and
not exists(MainModulePath::of(pkg))
not exists(MainModulePath::of(pkg, _))
}
/** Gets the `package.json` file in which this path occurs. */
@@ -196,3 +260,29 @@ private class FilesPath extends PathExpr, @json_string {
private module FilesPath {
FilesPath of(PackageJson pkg) { result.getPackageJson() = pkg }
}
/**
* A JSON string in a `package.json` file specifying the path of the
* TypeScript typings entry point.
*/
class TypingsModulePathString extends PathString {
PackageJson pkg;
TypingsModulePathString() {
this = pkg.getTypings()
or
not exists(pkg.getTypings()) and
this = pkg.getMain().regexpReplaceAll("\\.[mc]?js$", ".d.ts")
}
/** Gets the `package.json` file containing this path. */
PackageJson getPackageJson() { result = pkg }
override Folder getARootFolder() { result = pkg.getFile().getParentContainer() }
}
/** Companion module to the `TypingsModulePathString` class. */
module TypingsModulePathString {
/** Get the typings path for the given `package.json` file. */
TypingsModulePathString of(PackageJson pkg) { result.getPackageJson() = pkg }
}

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 |
@@ -74,6 +52,16 @@ private DataFlow::Node getAValueExportedByPackage() {
not isPrivateMethodDeclaration(result)
)
or
// module.exports.foo = function () {
// return new Foo(); // <- result
// };
exists(DataFlow::FunctionNode func, DataFlow::NewNode inst, DataFlow::ClassNode clz |
func = getAValueExportedByPackage().getALocalSource() and inst = unique( | | func.getAReturn())
|
clz.getAnInstanceReference() = inst and
result = clz.getAnInstanceMember(_)
)
or
result = getAValueExportedByPackage().getALocalSource()
or
// Nested property reads.

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()
}
@@ -64,8 +64,8 @@ private newtype TPrintAstNode =
// JSON
TJsonNode(JsonValue value) { shouldPrint(value, _) and not isNotNeeded(value) } or
// YAML
TYamlNode(YAMLNode n) { shouldPrint(n, _) and not isNotNeeded(n) } or
TYamlMappingNode(YAMLMapping mapping, int i) {
TYamlNode(YamlNode n) { shouldPrint(n, _) and not isNotNeeded(n) } or
TYamlMappingNode(YamlMapping mapping, int i) {
shouldPrint(mapping, _) and not isNotNeeded(mapping) and exists(mapping.getKeyNode(i))
} or
// HTML
@@ -195,7 +195,7 @@ private module PrintJavaScript {
* Gets the `i`th child of `element`.
* Can be overridden in subclasses to get more specific behavior for `getChild()`.
*/
AstNode getChildNode(int childIndex) { result = getLocationSortedChild(element, childIndex) }
AstNode getChildNode(int i) { result = getLocationSortedChild(element, i) }
}
/** Provides predicates for pretty printing `AstNode`s. */
@@ -628,7 +628,7 @@ module PrintYaml {
* A print node representing a YAML value in a .yml file.
*/
class YamlNodeNode extends PrintAstNode, TYamlNode {
YAMLNode node;
YamlNode node;
YamlNodeNode() { this = TYamlNode(node) }
@@ -639,10 +639,10 @@ module PrintYaml {
/**
* Gets the `YAMLNode` represented by this node.
*/
final YAMLNode getValue() { result = node }
final YamlNode getValue() { result = node }
override PrintAstNode getChild(int childIndex) {
exists(YAMLNode child | result.(YamlNodeNode).getValue() = child |
exists(YamlNode child | result.(YamlNodeNode).getValue() = child |
child = node.getChildNode(childIndex)
)
}
@@ -657,7 +657,7 @@ module PrintYaml {
* Each child of this node aggregates the key and value of a mapping.
*/
class YamlMappingNode extends YamlNodeNode {
override YAMLMapping node;
override YamlMapping node;
override PrintAstNode getChild(int childIndex) {
exists(YamlMappingMapNode map | map = result | map.maps(node, childIndex))
@@ -671,21 +671,21 @@ module PrintYaml {
* A print node representing the `i`th mapping in `mapping`.
*/
class YamlMappingMapNode extends PrintAstNode, TYamlMappingNode {
YAMLMapping mapping;
YamlMapping mapping;
int i;
YamlMappingMapNode() { this = TYamlMappingNode(mapping, i) }
override string toString() {
result = "(Mapping " + i + ")" and not exists(mapping.getKeyNode(i).(YAMLScalar).getValue())
result = "(Mapping " + i + ")" and not exists(mapping.getKeyNode(i).(YamlScalar).getValue())
or
result = "(Mapping " + i + ") " + mapping.getKeyNode(i).(YAMLScalar).getValue() + ":"
result = "(Mapping " + i + ") " + mapping.getKeyNode(i).(YamlScalar).getValue() + ":"
}
/**
* Holds if this print node represents the `index`th mapping of `m`.
*/
predicate maps(YAMLMapping m, int index) {
predicate maps(YamlMapping m, int index) {
m = mapping and
index = i
}

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

@@ -1005,7 +1005,10 @@ module RegExpPatterns {
* Gets a pattern that matches common top-level domain names in lower case.
* DEPRECATED: use `getACommonTld` instead
*/
deprecated predicate commonTLD = getACommonTld/0;
deprecated predicate commonTld = getACommonTld/0;
/** DEPRECATED: Alias for commonTld */
deprecated predicate commonTLD = commonTld/0;
}
/**

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

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