Merge remote-tracking branch 'upstream/master' into exceptionXss

This commit is contained in:
Erik Krogh Kristensen
2019-11-26 10:54:04 +01:00
228 changed files with 5669 additions and 1550 deletions

View File

@@ -8,7 +8,8 @@
"build": "tsc --project tsconfig.json",
"check": "tsc --noEmit --project . && tslint --project .",
"lint": "tslint --project .",
"lint-fix": "tslint --project . --fix"
"lint-fix": "tslint --project . --fix",
"watch": "tsc -p . -w --sourceMap"
},
"devDependencies": {
"@types/node": "12.7.11",

View File

@@ -198,7 +198,9 @@ export function augmentAst(ast: AugmentedSourceFile, code: string, project: Proj
: null;
let type = contextualType || typeChecker.getTypeAtLocation(node);
if (type != null) {
let id = typeTable.buildType(type);
let parent = node.parent;
let unfoldAlias = ts.isTypeAliasDeclaration(parent) && node === parent.type;
let id = typeTable.buildType(type, unfoldAlias);
if (id != null) {
node.$type = id;
}

View File

@@ -253,7 +253,7 @@ function handleOpenProjectCommand(command: OpenProjectCommand) {
fileExists: (path: string) => fs.existsSync(path),
readFile: ts.sys.readFile,
};
let config = ts.parseJsonConfigFileContent(tsConfig, parseConfigHost, basePath);
let config = ts.parseJsonConfigFileContent(tsConfig.config, parseConfigHost, basePath);
let project = new Project(tsConfigFilename, config, state.typeTable);
project.load();
@@ -272,7 +272,9 @@ function handleOpenProjectCommand(command: OpenProjectCommand) {
});
for (let typeRoot of typeRoots || []) {
traverseTypeRoot(typeRoot, "");
if (fs.existsSync(typeRoot) && fs.statSync(typeRoot).isDirectory()) {
traverseTypeRoot(typeRoot, "");
}
}
for (let sourceFile of program.getSourceFiles()) {

View File

@@ -92,6 +92,20 @@ function isTypeofCandidateSymbol(symbol: ts.Symbol) {
const signatureKinds = [ts.SignatureKind.Call, ts.SignatureKind.Construct];
/**
* Bitmask of flags set on a signature, but not exposed in the public API.
*/
const enum InternalSignatureFlags {
HasRestParameter = 1
}
/**
* Signature interface with some internal properties exposed.
*/
interface AugmentedSignature extends ts.Signature {
flags?: InternalSignatureFlags;
}
/**
* Encodes property lookup tuples `(baseType, name, property)` as three
* staggered arrays.
@@ -102,6 +116,16 @@ interface PropertyLookupTable {
propertyTypes: number[];
}
/**
* Encodes `(aliasType, underlyingType)` tuples as two staggered arrays.
*
* Such a tuple denotes that `aliasType` is an alias for `underlyingType`.
*/
interface TypeAliasTable {
aliasTypes: number[];
underlyingTypes: number[];
}
/**
* Encodes type signature tuples `(baseType, kind, index, signature)` as four
* staggered arrays.
@@ -287,6 +311,11 @@ export class TypeTable {
propertyTypes: [],
};
private typeAliases: TypeAliasTable = {
aliasTypes: [],
underlyingTypes: [],
};
private signatureMappings: SignatureTable = {
baseTypes: [],
kinds: [],
@@ -304,7 +333,7 @@ export class TypeTable {
propertyTypes: [],
};
private buildTypeWorklist: [ts.Type, number][] = [];
private buildTypeWorklist: [ts.Type, number, boolean][] = [];
private expansiveTypes: Map<number, boolean> = new Map();
@@ -372,9 +401,9 @@ export class TypeTable {
/**
* Gets the canonical ID for the given type, generating a fresh ID if necessary.
*/
public buildType(type: ts.Type): number | null {
public buildType(type: ts.Type, unfoldAlias: boolean): number | null {
this.isInShallowTypeContext = false;
let id = this.getId(type);
let id = this.getId(type, unfoldAlias);
this.iterateBuildTypeWorklist();
if (id == null) return null;
return id;
@@ -385,7 +414,7 @@ export class TypeTable {
*
* Returns `null` if we do not support extraction of this type.
*/
public getId(type: ts.Type): number | null {
public getId(type: ts.Type, unfoldAlias: boolean): number | null {
if (this.typeRecursionDepth > 100) {
// Ignore infinitely nested anonymous types, such as `{x: {x: {x: ... }}}`.
// Such a type can't be written directly with TypeScript syntax (as it would need to be named),
@@ -397,19 +426,19 @@ export class TypeTable {
type = this.typeChecker.getBaseTypeOfLiteralType(type);
}
++this.typeRecursionDepth;
let content = this.getTypeString(type);
let content = this.getTypeString(type, unfoldAlias);
--this.typeRecursionDepth;
if (content == null) return null; // Type not supported.
let id = this.typeIds.get(content);
if (id == null) {
let stringValue = this.stringifyType(type);
let stringValue = this.stringifyType(type, unfoldAlias);
if (stringValue == null) {
return null; // Type not supported.
}
id = this.typeIds.size;
this.typeIds.set(content, id);
this.typeToStringValues.push(stringValue);
this.buildTypeWorklist.push([type, id]);
this.buildTypeWorklist.push([type, id, unfoldAlias]);
this.typeExtractionState.push(
this.isInShallowTypeContext ? TypeExtractionState.PendingShallow : TypeExtractionState.PendingFull);
// If the type is the self-type for a named type (not a generic instantiation of it),
@@ -426,20 +455,20 @@ export class TypeTable {
this.typeExtractionState[id] = TypeExtractionState.PendingFull;
} else if (state === TypeExtractionState.DoneShallow) {
this.typeExtractionState[id] = TypeExtractionState.PendingFull;
this.buildTypeWorklist.push([type, id]);
this.buildTypeWorklist.push([type, id, unfoldAlias]);
}
}
return id;
}
private stringifyType(type: ts.Type): string {
private stringifyType(type: ts.Type, unfoldAlias: boolean): string {
let formatFlags = unfoldAlias
? ts.TypeFormatFlags.InTypeAlias
: ts.TypeFormatFlags.UseAliasDefinedOutsideCurrentScope;
let toStringValue: string;
// Some types can't be stringified. Just discard the type if we can't stringify it.
try {
toStringValue = this.typeChecker.typeToString(
type,
undefined,
ts.TypeFormatFlags.UseAliasDefinedOutsideCurrentScope);
toStringValue = this.typeChecker.typeToString(type, undefined, formatFlags);
} catch (e) {
console.warn("Recovered from a compiler crash while stringifying a type. Discarding the type.");
console.warn(e.stack);
@@ -477,9 +506,9 @@ export class TypeTable {
/**
* Gets a string representing the kind and contents of the given type.
*/
private getTypeString(type: AugmentedType): string | null {
private getTypeString(type: AugmentedType, unfoldAlias: boolean): string | null {
// Reference to a type alias.
if (type.aliasSymbol != null) {
if (!unfoldAlias && type.aliasSymbol != null) {
let tag = "reference;" + this.getSymbolId(type.aliasSymbol);
return type.aliasTypeArguments == null
? tag
@@ -499,7 +528,7 @@ export class TypeTable {
if (flags & ts.TypeFlags.TypeVariable) {
let enclosingType = getEnclosingTypeOfThisType(type);
if (enclosingType != null) {
return "this;" + this.getId(enclosingType);
return "this;" + this.getId(enclosingType, false);
} else if (symbol.parent == null) {
// The type variable is bound on a call signature. Only extract it by name.
return "lextypevar;" + symbol.name;
@@ -730,7 +759,7 @@ export class TypeTable {
private makeTypeStringVector(tag: string, types: ReadonlyArray<ts.Type>, length = types.length): string | null {
let hash = tag;
for (let i = 0; i < length; ++i) {
let id = this.getId(types[i]);
let id = this.getId(types[i], false);
if (id == null) return null;
hash += ";" + id;
}
@@ -748,7 +777,7 @@ export class TypeTable {
for (let property of type.getProperties()) {
let propertyType = this.typeChecker.getTypeOfSymbolAtLocation(property, this.arbitraryAstNode);
if (propertyType == null) return null;
let propertyTypeId = this.getId(propertyType);
let propertyTypeId = this.getId(propertyType, false);
if (propertyTypeId == null) return null;
hash += ";p" + this.getSymbolId(property) + ';' + propertyTypeId;
}
@@ -761,13 +790,13 @@ export class TypeTable {
}
let indexType = type.getStringIndexType();
if (indexType != null) {
let indexTypeId = this.getId(indexType);
let indexTypeId = this.getId(indexType, false);
if (indexTypeId == null) return null;
hash += ";s" + indexTypeId;
}
indexType = type.getNumberIndexType();
if (indexType != null) {
let indexTypeId = this.getId(indexType);
let indexTypeId = this.getId(indexType, false);
if (indexTypeId == null) return null;
hash += ";i" + indexTypeId;
}
@@ -789,6 +818,7 @@ export class TypeTable {
typeStrings: Array.from(this.typeIds.keys()),
typeToStringValues: this.typeToStringValues,
propertyLookups: this.propertyLookups,
typeAliases: this.typeAliases,
symbolStrings: Array.from(this.symbolIds.keys()),
moduleMappings: this.moduleMappings,
globalMappings: this.globalMappings,
@@ -812,10 +842,17 @@ export class TypeTable {
let worklist = this.buildTypeWorklist;
let typeExtractionState = this.typeExtractionState;
while (worklist.length > 0) {
let [type, id] = worklist.pop();
let [type, id, unfoldAlias] = worklist.pop();
let isShallowContext = typeExtractionState[id] === TypeExtractionState.PendingShallow;
if (isShallowContext && !isTypeAlwaysSafeToExpand(type)) {
typeExtractionState[id] = TypeExtractionState.DoneShallow;
} else if (type.aliasSymbol != null && !unfoldAlias) {
typeExtractionState[id] = TypeExtractionState.DoneFull;
let underlyingTypeId = this.getId(type, true);
if (underlyingTypeId != null) {
this.typeAliases.aliasTypes.push(id);
this.typeAliases.underlyingTypes.push(underlyingTypeId);
}
} else {
typeExtractionState[id] = TypeExtractionState.DoneFull;
this.isInShallowTypeContext = isShallowContext || this.isExpansiveTypeReference(type);
@@ -847,7 +884,7 @@ export class TypeTable {
for (let symbol of props) {
let propertyType = this.typeChecker.getTypeOfSymbolAtLocation(symbol, this.arbitraryAstNode);
if (propertyType == null) continue;
let propertyTypeId = this.getId(propertyType);
let propertyTypeId = this.getId(propertyType, false);
if (propertyTypeId == null) continue;
this.propertyLookups.baseTypes.push(id);
this.propertyLookups.names.push(symbol.name);
@@ -879,7 +916,7 @@ export class TypeTable {
/**
* Returns a unique string for the given call/constructor signature.
*/
private getSignatureString(kind: ts.SignatureKind, signature: ts.Signature): string {
private getSignatureString(kind: ts.SignatureKind, signature: AugmentedSignature): string {
let parameters = signature.getParameters();
let numberOfTypeParameters = signature.typeParameters == null
? 0
@@ -892,27 +929,51 @@ export class TypeTable {
break;
}
}
let returnTypeId = this.getId(signature.getReturnType());
let hasRestParam = (signature.flags & InternalSignatureFlags.HasRestParameter) !== 0;
let restParameterTag = '';
if (hasRestParam) {
if (requiredParameters === parameters.length) {
// Do not count the rest parameter as a required parameter
requiredParameters = parameters.length - 1;
}
if (parameters.length === 0) return null;
let restParameter = parameters[parameters.length - 1];
let restParameterType = this.typeChecker.getTypeOfSymbolAtLocation(restParameter, this.arbitraryAstNode);
if (restParameterType == null) return null;
let restParameterTypeId = this.getId(restParameterType, false);
if (restParameterTypeId == null) return null;
restParameterTag = '' + restParameterTypeId;
}
let returnTypeId = this.getId(signature.getReturnType(), false);
if (returnTypeId == null) {
return null;
}
let tag = `${kind};${numberOfTypeParameters};${requiredParameters};${returnTypeId}`;
let tag = `${kind};${numberOfTypeParameters};${requiredParameters};${restParameterTag};${returnTypeId}`;
for (let typeParameter of signature.typeParameters || []) {
tag += ";" + typeParameter.symbol.name;
let constraint = typeParameter.getConstraint();
let constraintId: number;
if (constraint == null || (constraintId = this.getId(constraint)) == null) {
if (constraint == null || (constraintId = this.getId(constraint, false)) == null) {
tag += ";";
} else {
tag += ";" + constraintId;
}
}
for (let parameter of parameters) {
for (let paramIndex = 0; paramIndex < parameters.length; ++paramIndex) {
let parameter = parameters[paramIndex];
let parameterType = this.typeChecker.getTypeOfSymbolAtLocation(parameter, this.arbitraryAstNode);
if (parameterType == null) {
return null;
}
let parameterTypeId = this.getId(parameterType);
let isRestParameter = hasRestParam && (paramIndex === parameters.length - 1);
if (isRestParameter) {
// The type of the rest parameter is the array type, but we wish to extract the non-array type.
if (!isTypeReference(parameterType)) return null;
let typeArguments = parameterType.typeArguments;
if (typeArguments == null || typeArguments.length === 0) return null;
parameterType = typeArguments[0];
}
let parameterTypeId = this.getId(parameterType, false);
if (parameterTypeId == null) {
return null;
}
@@ -946,7 +1007,7 @@ export class TypeTable {
private extractIndexer(baseType: number, indexType: ts.Type, table: IndexerTable) {
if (indexType == null) return;
let indexTypeId = this.getId(indexType);
let indexTypeId = this.getId(indexType, false);
if (indexTypeId == null) return;
table.baseTypes.push(baseType);
table.propertyTypes.push(indexTypeId);
@@ -1017,7 +1078,7 @@ export class TypeTable {
let selfType = this.getSelfType(type);
if (selfType != null) {
this.checkExpansiveness(selfType);
let id = this.getId(selfType);
let id = this.getId(selfType, false);
return this.expansiveTypes.get(id);
}
return false;
@@ -1086,7 +1147,7 @@ export class TypeTable {
search(type, 0);
function search(type: ts.TypeReference, expansionDepth: number): number | null {
let id = typeTable.getId(type);
let id = typeTable.getId(type, false);
if (id == null) return null;
let index = indexTable.get(id);

View File

@@ -3,6 +3,7 @@ package com.semmle.js.ast;
import com.semmle.ts.ast.DecoratorList;
import com.semmle.ts.ast.ITypeExpression;
import com.semmle.ts.ast.TypeParameter;
import com.semmle.util.data.IntList;
import java.util.ArrayList;
import java.util.List;
@@ -18,6 +19,9 @@ public class AFunction<B> {
private final List<ITypeExpression> parameterTypes;
private final ITypeExpression thisParameterType;
private final List<DecoratorList> parameterDecorators;
private final IntList optionalParameterIndices;
public static final IntList noOptionalParams = IntList.create(0, 0);
public AFunction(
Identifier id,
@@ -29,7 +33,8 @@ public class AFunction<B> {
List<ITypeExpression> parameterTypes,
List<DecoratorList> parameterDecorators,
ITypeExpression returnType,
ITypeExpression thisParameterType) {
ITypeExpression thisParameterType,
IntList optionalParameterIndices) {
this.id = id;
this.params = new ArrayList<IPattern>(params.size());
this.defaults = new ArrayList<Expression>(params.size());
@@ -42,6 +47,7 @@ public class AFunction<B> {
this.returnType = returnType;
this.thisParameterType = thisParameterType;
this.parameterDecorators = parameterDecorators;
this.optionalParameterIndices = optionalParameterIndices;
IPattern rest = null;
for (Expression param : params) {
@@ -143,4 +149,8 @@ public class AFunction<B> {
public List<DecoratorList> getParameterDecorators() {
return parameterDecorators;
}
public IntList getOptionalParmaeterIndices() {
return optionalParameterIndices;
}
}

View File

@@ -3,6 +3,7 @@ package com.semmle.js.ast;
import com.semmle.ts.ast.DecoratorList;
import com.semmle.ts.ast.ITypeExpression;
import com.semmle.ts.ast.TypeParameter;
import com.semmle.util.data.IntList;
import java.util.List;
/**
@@ -26,7 +27,8 @@ public abstract class AFunctionExpression extends Expression implements IFunctio
List<ITypeExpression> parameterTypes,
List<DecoratorList> parameterDecorators,
ITypeExpression returnType,
ITypeExpression thisParameterType) {
ITypeExpression thisParameterType,
IntList optionalParameterIndices) {
super(type, loc);
this.fn =
new AFunction<Node>(
@@ -39,7 +41,8 @@ public abstract class AFunctionExpression extends Expression implements IFunctio
parameterTypes,
parameterDecorators,
returnType,
thisParameterType);
thisParameterType,
optionalParameterIndices);
}
public AFunctionExpression(String type, SourceLocation loc, AFunction<? extends Node> fn) {
@@ -155,4 +158,8 @@ public abstract class AFunctionExpression extends Expression implements IFunctio
public void setDeclaredSignatureId(int id) {
declaredSignature = id;
}
public IntList getOptionalParameterIndices() {
return fn.getOptionalParmaeterIndices();
}
}

View File

@@ -2,6 +2,7 @@ package com.semmle.js.ast;
import com.semmle.ts.ast.ITypeExpression;
import com.semmle.ts.ast.TypeParameter;
import com.semmle.util.data.IntList;
import java.util.Collections;
import java.util.List;
@@ -21,7 +22,8 @@ public class ArrowFunctionExpression extends AFunctionExpression {
Collections.emptyList(),
Collections.emptyList(),
null,
null);
null,
AFunction.noOptionalParams);
}
public ArrowFunctionExpression(
@@ -32,7 +34,8 @@ public class ArrowFunctionExpression extends AFunctionExpression {
Boolean async,
List<TypeParameter> typeParameters,
List<ITypeExpression> parameterTypes,
ITypeExpression returnType) {
ITypeExpression returnType,
IntList optionalParameterIndices) {
super(
"ArrowFunctionExpression",
loc,
@@ -45,7 +48,8 @@ public class ArrowFunctionExpression extends AFunctionExpression {
parameterTypes,
Collections.emptyList(),
returnType,
null);
null,
optionalParameterIndices);
}
@Override

View File

@@ -3,6 +3,7 @@ package com.semmle.js.ast;
import com.semmle.ts.ast.DecoratorList;
import com.semmle.ts.ast.ITypeExpression;
import com.semmle.ts.ast.TypeParameter;
import com.semmle.util.data.IntList;
import java.util.Collections;
import java.util.List;
@@ -41,7 +42,8 @@ public class FunctionDeclaration extends Statement implements IFunction {
Collections.emptyList(),
Collections.emptyList(),
null,
null),
null,
AFunction.noOptionalParams),
false);
}
@@ -56,7 +58,8 @@ public class FunctionDeclaration extends Statement implements IFunction {
List<TypeParameter> typeParameters,
List<ITypeExpression> parameterTypes,
ITypeExpression returnType,
ITypeExpression thisParameterType) {
ITypeExpression thisParameterType,
IntList optionalParameterIndices) {
this(
loc,
new AFunction<>(
@@ -69,7 +72,8 @@ public class FunctionDeclaration extends Statement implements IFunction {
parameterTypes,
Collections.emptyList(),
returnType,
thisParameterType),
thisParameterType,
optionalParameterIndices),
hasDeclareKeyword);
}
@@ -207,4 +211,8 @@ public class FunctionDeclaration extends Statement implements IFunction {
public void setDeclaredSignatureId(int id) {
declaredSignature = id;
}
public IntList getOptionalParameterIndices() {
return fn.getOptionalParmaeterIndices();
}
}

View File

@@ -3,6 +3,7 @@ package com.semmle.js.ast;
import com.semmle.ts.ast.DecoratorList;
import com.semmle.ts.ast.ITypeExpression;
import com.semmle.ts.ast.TypeParameter;
import com.semmle.util.data.IntList;
import java.util.Collections;
import java.util.List;
@@ -27,7 +28,8 @@ public class FunctionExpression extends AFunctionExpression {
Collections.emptyList(),
Collections.emptyList(),
null,
null);
null,
AFunction.noOptionalParams);
}
public FunctionExpression(
@@ -41,7 +43,8 @@ public class FunctionExpression extends AFunctionExpression {
List<ITypeExpression> parameterTypes,
List<DecoratorList> parameterDecorators,
ITypeExpression returnType,
ITypeExpression thisParameterType) {
ITypeExpression thisParameterType,
IntList optionalParameterIndices) {
super(
"FunctionExpression",
loc,
@@ -54,7 +57,8 @@ public class FunctionExpression extends AFunctionExpression {
parameterTypes,
parameterDecorators,
returnType,
thisParameterType);
thisParameterType,
optionalParameterIndices);
}
public FunctionExpression(SourceLocation loc, AFunction<? extends Node> fn) {

View File

@@ -5,6 +5,7 @@ import com.semmle.ts.ast.INodeWithSymbol;
import com.semmle.ts.ast.ITypeExpression;
import com.semmle.ts.ast.ITypedAstNode;
import com.semmle.ts.ast.TypeParameter;
import com.semmle.util.data.IntList;
import java.util.List;
/** A function declaration or expression. */
@@ -75,4 +76,6 @@ public interface IFunction extends IStatementContainer, INodeWithSymbol, ITypedA
public int getDeclaredSignatureId();
public void setDeclaredSignatureId(int id);
public IntList getOptionalParameterIndices();
}

View File

@@ -45,6 +45,7 @@ import com.semmle.ts.ast.TypeParameter;
import com.semmle.ts.ast.TypeofTypeExpr;
import com.semmle.ts.ast.UnaryTypeExpr;
import com.semmle.ts.ast.UnionTypeExpr;
import com.semmle.util.data.IntList;
import java.util.ArrayList;
import java.util.List;
@@ -70,6 +71,10 @@ public class NodeCopier implements Visitor<Void, INode> {
return result;
}
private IntList copy(IntList list) {
return new IntList(list);
}
@Override
public AssignmentExpression visit(AssignmentExpression nd, Void q) {
return new AssignmentExpression(
@@ -138,7 +143,8 @@ public class NodeCopier implements Visitor<Void, INode> {
copy(nd.getTypeParameters()),
copy(nd.getParameterTypes()),
copy(nd.getReturnType()),
copy(nd.getThisParameterType()));
copy(nd.getThisParameterType()),
copy(nd.getOptionalParameterIndices()));
}
@Override
@@ -367,7 +373,8 @@ public class NodeCopier implements Visitor<Void, INode> {
copy(nd.getParameterTypes()),
copy(nd.getParameterDecorators()),
copy(nd.getReturnType()),
copy(nd.getThisParameterType()));
copy(nd.getThisParameterType()),
copy(nd.getOptionalParameterIndices()));
}
@Override
@@ -427,7 +434,8 @@ public class NodeCopier implements Visitor<Void, INode> {
nd.isAsync(),
copy(nd.getTypeParameters()),
copy(nd.getParameterTypes()),
copy(nd.getReturnType()));
copy(nd.getReturnType()),
copy(nd.getOptionalParameterIndices()));
}
@Override

View File

@@ -520,7 +520,7 @@ public class ASTExtractor {
OffsetTranslation offsets = new OffsetTranslation();
offsets.set(0, 1); // skip the initial '/'
regexpExtractor.extract(source.substring(1, source.lastIndexOf('/')), offsets, nd, false);
} else if (nd.isStringLiteral() && !c.isInsideType()) {
} else if (nd.isStringLiteral() && !c.isInsideType() && nd.getRaw().length() < 1000) {
regexpExtractor.extract(valueString, makeStringLiteralOffsets(nd.getRaw()), nd, true);
}
return key;
@@ -900,7 +900,12 @@ public class ASTExtractor {
for (IPattern param : nd.getAllParams()) {
scopeManager.addNames(
scopeManager.collectDeclaredNames(param, isStrict, false, DeclKind.var));
visit(param, key, i, IdContext.varDecl);
Label paramKey = visit(param, key, i, IdContext.varDecl);
// Extract optional parameters
if (nd.getOptionalParameterIndices().contains(i)) {
trapwriter.addTuple("isOptionalParameterDeclaration", paramKey);
}
++i;
}
@@ -1393,7 +1398,8 @@ public class ASTExtractor {
Collections.emptyList(),
Collections.emptyList(),
null,
null);
null,
AFunction.noOptionalParams);
String fnSrc = hasSuperClass ? "(...args) { super(...args); }" : "() {}";
SourceLocation fnloc = fakeLoc(fnSrc, loc);
FunctionExpression fn = new FunctionExpression(fnloc, fndef);

View File

@@ -578,6 +578,11 @@ public class AutoBuild {
for (File sourceFile : project.getSourceFiles()) {
Path sourcePath = sourceFile.toPath();
if (!files.contains(normalizePath(sourcePath))) continue;
if (!FileType.TYPESCRIPT.getExtensions().contains(FileUtil.extension(sourcePath))) {
// For the time being, skip non-TypeScript files, even if the TypeScript
// compiler can parse them for us.
continue;
}
if (!extractedFiles.contains(sourcePath)) {
typeScriptFiles.add(sourcePath.toFile());
}

View File

@@ -37,7 +37,7 @@ public class Main {
* A version identifier that should be updated every time the extractor changes in such a way that
* it may produce different tuples for the same file under the same {@link ExtractorConfig}.
*/
public static final String EXTRACTOR_VERSION = "2019-10-23";
public static final String EXTRACTOR_VERSION = "2019-11-19";
public static final Pattern NEWLINE = Pattern.compile("\n");
@@ -147,7 +147,8 @@ public class Main {
List<File> filesToExtract = new ArrayList<>();
for (File sourceFile : project.getSourceFiles()) {
if (files.contains(normalizeFile(sourceFile))
&& !extractedFiles.contains(sourceFile.getAbsoluteFile())) {
&& !extractedFiles.contains(sourceFile.getAbsoluteFile())
&& FileType.TYPESCRIPT.getExtensions().contains(FileUtil.extension(sourceFile))) {
filesToExtract.add(sourceFile);
}
}

View File

@@ -143,6 +143,7 @@ import com.semmle.ts.ast.TypeofTypeExpr;
import com.semmle.ts.ast.UnaryTypeExpr;
import com.semmle.ts.ast.UnionTypeExpr;
import com.semmle.util.collections.CollectionUtil;
import com.semmle.util.data.IntList;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
@@ -804,7 +805,8 @@ public class TypeScriptASTConverter {
hasModifier(node, "AsyncKeyword"),
convertChildrenNotNull(node, "typeParameters"),
convertParameterTypes(node),
convertChildAsType(node, "type"));
convertChildAsType(node, "type"),
getOptionalParameterIndices(node));
attachDeclaredSignature(function, node);
return function;
}
@@ -1063,7 +1065,8 @@ public class TypeScriptASTConverter {
paramTypes,
paramDecorators,
null,
null);
null,
getOptionalParameterIndices(node));
attachSymbolInformation(value, node);
attachStaticType(value, node);
attachDeclaredSignature(value, node);
@@ -1262,7 +1265,8 @@ public class TypeScriptASTConverter {
typeParameters,
paramTypes,
returnType,
thisParam);
thisParam,
getOptionalParameterIndices(node));
attachSymbolInformation(function, node);
attachStaticType(function, node);
attachDeclaredSignature(function, node);
@@ -1291,7 +1295,8 @@ public class TypeScriptASTConverter {
paramTypes,
paramDecorators,
returnType,
thisParam);
thisParam,
getOptionalParameterIndices(node));
attachStaticType(function, node);
attachDeclaredSignature(function, node);
return function;
@@ -1645,7 +1650,8 @@ public class TypeScriptASTConverter {
paramTypes,
paramDecorators,
returnType,
thisType);
thisType,
getOptionalParameterIndices(node));
attachSymbolInformation(function, node);
attachStaticType(function, node);
attachDeclaredSignature(function, node);
@@ -1890,6 +1896,18 @@ public class TypeScriptASTConverter {
return result;
}
private IntList getOptionalParameterIndices(JsonObject function) throws ParseError {
IntList list = IntList.create(0);
int index = -1;
for (JsonElement param : getProperParameters(function)) {
++index;
if (param.getAsJsonObject().has("questionToken")) {
list.add(index);
}
}
return list;
}
private List<FieldDefinition> convertParameterFields(JsonObject function) throws ParseError {
List<FieldDefinition> result = new ArrayList<>();
int index = -1;

View File

@@ -291,10 +291,10 @@ public class TypeScriptParser {
LogbackUtils.getLogger(AbstractProcessBuilder.class).setLevel(Level.INFO);
String explicitPath = Env.systemEnv().get(PARSER_WRAPPER_PATH_ENV_VAR);
String semmleDistVar = Env.systemEnv().get(Env.Var.SEMMLE_DIST.name());
if (semmleDistVar != null && !semmleDistVar.isEmpty()) {
parserWrapper = new File(semmleDistVar, "tools/typescript-parser-wrapper/main.js");
} else if (explicitPath != null) {
if (explicitPath != null) {
parserWrapper = new File(explicitPath);
} else if (semmleDistVar != null && !semmleDistVar.isEmpty()) {
parserWrapper = new File(semmleDistVar, "tools/typescript-parser-wrapper/main.js");
} else {
throw new CatastrophicError(
"Could not find TypeScript parser: " + Env.Var.SEMMLE_DIST.name() + " is not set.");

View File

@@ -81,6 +81,7 @@ public class TypeExtractor {
extractType(i);
}
extractPropertyLookups(table.getPropertyLookups());
extractTypeAliases(table.getTypeAliases());
for (int i = 0; i < table.getNumberOfSymbols(); ++i) {
extractSymbol(i);
}
@@ -161,6 +162,19 @@ public class TypeExtractor {
}
}
private void extractTypeAliases(JsonObject aliases) {
JsonArray aliasTypes = aliases.get("aliasTypes").getAsJsonArray();
JsonArray underlyingTypes = aliases.get("underlyingTypes").getAsJsonArray();
for (int i = 0; i < aliasTypes.size(); ++i) {
int aliasType = aliasTypes.get(i).getAsInt();
int underlyingType = underlyingTypes.get(i).getAsInt();
trapWriter.addTuple(
"type_alias",
trapWriter.globalID("type;" + aliasType),
trapWriter.globalID("type;" + underlyingType));
}
}
private void extractSymbol(int index) {
// Format is: kind;decl;parent;name
String[] parts = split(table.getSymbolString(index), 4);
@@ -187,13 +201,18 @@ public class TypeExtractor {
private void extractSignature(int index) {
// Format is:
// kind;numTypeParams;requiredParams;returnType(;paramName;paramType)*
// kind;numTypeParams;requiredParams;restParamType;returnType(;paramName;paramType)*
String[] parts = split(table.getSignatureString(index));
Label label = trapWriter.globalID("signature;" + index);
int kind = Integer.parseInt(parts[0]);
int numberOfTypeParameters = Integer.parseInt(parts[1]);
int requiredParameters = Integer.parseInt(parts[2]);
Label returnType = trapWriter.globalID("type;" + parts[3]);
String restParamTypeTag = parts[3];
if (!restParamTypeTag.isEmpty()) {
trapWriter.addTuple(
"signature_rest_parameter", label, trapWriter.globalID("type;" + restParamTypeTag));
}
Label returnType = trapWriter.globalID("type;" + parts[4]);
trapWriter.addTuple(
"signature_types",
label,
@@ -202,9 +221,9 @@ public class TypeExtractor {
numberOfTypeParameters,
requiredParameters);
trapWriter.addTuple("signature_contains_type", returnType, label, -1);
int numberOfParameters = (parts.length - 4) / 2; // includes type parameters
int numberOfParameters = (parts.length - 5) / 2; // includes type parameters
for (int i = 0; i < numberOfParameters; ++i) {
int partIndex = 4 + (2 * i);
int partIndex = 5 + (2 * i);
String paramName = parts[partIndex];
String paramTypeId = parts[partIndex + 1];
if (paramTypeId.length() > 0) { // Unconstrained type parameters have an empty type ID.

View File

@@ -12,6 +12,7 @@ public class TypeTable {
private final JsonArray typeStrings;
private final JsonArray typeToStringValues;
private final JsonObject propertyLookups;
private final JsonObject typeAliases;
private final JsonArray symbolStrings;
private final JsonObject moduleMappings;
private final JsonObject globalMappings;
@@ -27,6 +28,7 @@ public class TypeTable {
this.typeStrings = typeTable.get("typeStrings").getAsJsonArray();
this.typeToStringValues = typeTable.get("typeToStringValues").getAsJsonArray();
this.propertyLookups = typeTable.get("propertyLookups").getAsJsonObject();
this.typeAliases = typeTable.get("typeAliases").getAsJsonObject();
this.symbolStrings = typeTable.get("symbolStrings").getAsJsonArray();
this.moduleMappings = typeTable.get("moduleMappings").getAsJsonObject();
this.globalMappings = typeTable.get("globalMappings").getAsJsonObject();
@@ -51,6 +53,10 @@ public class TypeTable {
return propertyLookups;
}
public JsonObject getTypeAliases() {
return typeAliases;
}
public int getNumberOfTypes() {
return typeStrings.size();
}

View File

@@ -14,47 +14,51 @@ import javascript
* Holds if the receiver of `method` is bound.
*/
private predicate isBoundInMethod(MethodDeclaration method) {
exists(DataFlow::ThisNode thiz, MethodDeclaration bindingMethod |
exists(DataFlow::ThisNode thiz, MethodDeclaration bindingMethod, string name |
bindingMethod.getDeclaringClass() = method.getDeclaringClass() and
not bindingMethod.isStatic() and
thiz.getBinder().getAstNode() = bindingMethod.getBody()
thiz.getBinder().getAstNode() = bindingMethod.getBody() and
name = method.getName()
|
// binding assignments: `this[x] = <expr>.bind(...)`
exists(DataFlow::MethodCallNode bind, DataFlow::PropWrite w |
// this[x] = <expr>.bind(...)
not exists(w.getPropertyName()) or // unknown name, assume everything is bound
w.getPropertyName() = name
|
w = thiz.getAPropertyWrite() and
not exists(w.getPropertyName()) and
bind.getMethodName() = "bind" and
bind.flowsTo(w.getRhs())
)
or
// require("auto-bind")(this)
// library binders
exists(string mod |
mod = "auto-bind" or
mod = "react-autobind"
|
thiz.flowsTo(DataFlow::moduleImport(mod).getACall().getArgument(0))
)
or
exists(string name | name = method.getName() |
exists(DataFlow::MethodCallNode bind |
// this.<methodName> = <expr>.bind(...)
bind = thiz.getAPropertySource(name) and
bind.getMethodName() = "bind"
) or
// heuristic reflective binders
exists(DataFlow::CallNode binder, string calleeName |
(
binder.(DataFlow::MethodCallNode).getMethodName() = calleeName or
binder.getCalleeNode().asExpr().(VarAccess).getVariable().getName() = calleeName
) and
calleeName.regexpMatch("(?i).*bind.*") and
thiz.flowsTo(binder.getAnArgument()) and
// exclude the binding assignments
not thiz.getAPropertySource() = binder
|
// `myBindAll(this)`
binder.getNumArgument() = 1
or
// `myBindSome(this, [<name1>, <name2>])`
exists(DataFlow::ArrayCreationNode names |
names.flowsTo(binder.getAnArgument()) and
names.getAnElement().mayHaveStringValue(name)
)
or
exists(DataFlow::MethodCallNode bindAll |
bindAll.getMethodName() = "bindAll" and
thiz.flowsTo(bindAll.getArgument(0))
|
// _.bindAll(this, <name1>)
bindAll.getArgument(1).mayHaveStringValue(name)
or
// _.bindAll(this, [<name1>, <name2>])
exists(DataFlow::ArrayCreationNode names |
names.flowsTo(bindAll.getArgument(1)) and
names.getAnElement().mayHaveStringValue(name)
)
)
// `myBindSome(this, <name1>, <name2>)`
binder.getAnArgument().mayHaveStringValue(name)
)
)
or
@@ -66,10 +70,10 @@ private predicate isBoundInMethod(MethodDeclaration method) {
) and
name.regexpMatch("(?i).*(bind|bound).*")
|
// @autobind
// `@autobind`
decoration.(Identifier).getName() = name
or
// @action.bound
// `@action.bound`
decoration.(PropAccess).getPropertyName() = name
)
}

View File

@@ -15,38 +15,17 @@
import javascript
/**
* Holds if `rl` is a simple constant, which is bound to the result of the predicate.
*
* For example, `/a/g` has string value `"a"` and `/abc/` has string value `"abc"`,
* while `/ab?/` and `/a(?=b)/` do not have a string value.
*
* Flags are ignored, so `/a/i` is still considered to have string value `"a"`,
* even though it also matches `"A"`.
*
* Note the somewhat subtle use of monotonic aggregate semantics, which makes the
* `strictconcat` fail if one of the children of the root is not a constant (legacy
* semantics would simply skip such children).
*/
language[monotonicAggregates]
string getStringValue(RegExpLiteral rl) {
exists(RegExpTerm root | root = rl.getRoot() |
result = root.(RegExpConstant).getValue()
or
result = strictconcat(RegExpTerm ch, int i |
ch = root.(RegExpSequence).getChild(i)
|
ch.(RegExpConstant).getValue() order by i
)
)
}
/**
* Gets a predecessor of `nd` that is not an SSA phi node.
*/
DataFlow::Node getASimplePredecessor(DataFlow::Node nd) {
result = nd.getAPredecessor() and
not nd.(DataFlow::SsaDefinitionNode).getSsaVariable().getDefinition() instanceof SsaPhiNode
not exists(SsaDefinition ssa |
ssa = nd.(DataFlow::SsaDefinitionNode).getSsaVariable().getDefinition()
|
ssa instanceof SsaPhiNode or
ssa instanceof SsaVariableCapture
)
}
/**
@@ -54,37 +33,33 @@ DataFlow::Node getASimplePredecessor(DataFlow::Node nd) {
* into a form described by regular expression `regex`.
*/
predicate escapingScheme(string metachar, string regex) {
metachar = "&" and regex = "&.*;"
metachar = "&" and regex = "&.+;"
or
metachar = "%" and regex = "%.*"
metachar = "%" and regex = "%.+"
or
metachar = "\\" and regex = "\\\\.*"
metachar = "\\" and regex = "\\\\.+"
}
/**
* A call to `String.prototype.replace` that replaces all instances of a pattern.
*/
class Replacement extends DataFlow::Node {
RegExpLiteral pattern;
class Replacement extends StringReplaceCall {
Replacement() {
exists(DataFlow::MethodCallNode mcn | this = mcn |
mcn.getMethodName() = "replace" and
pattern.flow().(DataFlow::SourceNode).flowsTo(mcn.getArgument(0)) and
mcn.getNumArgument() = 2 and
pattern.isGlobal()
)
isGlobal()
}
/**
* Holds if this replacement replaces the string `input` with `output`.
* Gets the input of this replacement.
*/
predicate replaces(string input, string output) {
exists(DataFlow::MethodCallNode mcn |
mcn = this and
input = getStringValue(pattern) and
output = mcn.getArgument(1).getStringValue()
)
DataFlow::Node getInput() {
result = this.getReceiver()
}
/**
* Gets the output of this replacement.
*/
DataFlow::SourceNode getOutput() {
result = this
}
/**
@@ -119,7 +94,7 @@ class Replacement extends DataFlow::Node {
* Gets the previous replacement in this chain of replacements.
*/
Replacement getPreviousReplacement() {
result = getASimplePredecessor*(this.(DataFlow::MethodCallNode).getReceiver())
result.getOutput() = getASimplePredecessor*(getInput())
}
/**
@@ -130,7 +105,9 @@ class Replacement extends DataFlow::Node {
exists(Replacement pred | pred = this.getPreviousReplacement() |
if pred.escapes(_, metachar)
then result = pred
else result = pred.getAnEarlierEscaping(metachar)
else (
not pred.unescapes(metachar, _) and result = pred.getAnEarlierEscaping(metachar)
)
)
}
@@ -142,7 +119,9 @@ class Replacement extends DataFlow::Node {
exists(Replacement succ | this = succ.getPreviousReplacement() |
if succ.unescapes(metachar, _)
then result = succ
else result = succ.getALaterUnescaping(metachar)
else (
not succ.escapes(_, metachar) and result = succ.getALaterUnescaping(metachar)
)
)
}
}

View File

@@ -35,6 +35,18 @@ likely to handle corner cases correctly than a custom implementation.
Otherwise, make sure to use a regular expression with the <code>g</code> flag to ensure that
all occurrences are replaced, and remember to escape backslashes if applicable.
</p>
<p>
Note, however, that this is generally <i>not</i> sufficient for replacing multi-character strings:
the <code>String.prototype.replace</code> method only performs one pass over the input string,
and will not replace further instances of the string that result from earlier replacements.
</p>
<p>
For example, consider the code snippet <code>s.replace(/\/\.\.\//g, "")</code>, which attempts
to strip out all occurences of <code>/../</code> from <code>s</code>. This will not work as
expected: for the string <code>/./.././</code>, for example, it will remove the single
occurrence of <code>/../</code> in the middle, but the remainder of the string then becomes
<code>/../</code>, which is another instance of the substring we were trying to remove.
</p>
</recommendation>
<example>

View File

@@ -20,36 +20,31 @@ import javascript
string metachar() { result = "'\"\\&<>\n\r\t*|{}[]%$".charAt(_) }
/** Gets a string matched by `e` in a `replace` call. */
string getAMatchedString(Expr e) {
result = getAMatchedConstant(e.(RegExpLiteral).getRoot()).getValue()
string getAMatchedString(DataFlow::Node e) {
result = e.(DataFlow::RegExpLiteralNode).getRoot().getAMatchedString()
or
result = e.getStringValue()
}
/** Gets a constant matched by `t`. */
RegExpConstant getAMatchedConstant(RegExpTerm t) {
result = t
or
result = getAMatchedConstant(t.(RegExpAlt).getAlternative())
or
result = getAMatchedConstant(t.(RegExpGroup).getAChild())
or
exists(RegExpCharacterClass recc | recc = t and not recc.isInverted() |
result = getAMatchedConstant(recc.getAChild())
)
}
/** Holds if `t` is simple, that is, a union of constants. */
predicate isSimple(RegExpTerm t) {
t instanceof RegExpConstant
or
isSimple(t.(RegExpGroup).getAChild())
or
(
t instanceof RegExpAlt
or
t instanceof RegExpCharacterClass and not t.(RegExpCharacterClass).isInverted()
) and
isSimpleCharacterClass(t)
or
isSimpleAlt(t)
}
/** Holds if `t` is a non-inverted character class that contains no ranges. */
predicate isSimpleCharacterClass(RegExpCharacterClass t) {
not t.isInverted() and
forall(RegExpTerm ch | ch = t.getAChild() | isSimple(ch))
}
/** Holds if `t` is an alternation of simple terms. */
predicate isSimpleAlt(RegExpAlt t) {
forall(RegExpTerm ch | ch = t.getAChild() | isSimple(ch))
}
@@ -57,16 +52,15 @@ predicate isSimple(RegExpTerm t) {
* Holds if `mce` is of the form `x.replace(re, new)`, where `re` is a global
* regular expression and `new` prefixes the matched string with a backslash.
*/
predicate isBackslashEscape(MethodCallExpr mce, RegExpLiteral re) {
mce.getMethodName() = "replace" and
re.flow().(DataFlow::SourceNode).flowsToExpr(mce.getArgument(0)) and
re.isGlobal() and
exists(string new | new = mce.getArgument(1).getStringValue() |
// `new` is `\$&`, `\$1` or similar
new.regexpMatch("\\\\\\$(&|\\d)")
predicate isBackslashEscape(StringReplaceCall mce, DataFlow::RegExpLiteralNode re) {
mce.isGlobal() and
re = mce.getRegExp() and
(
// replacement with `\$&`, `\$1` or similar
mce.getRawReplacement().getStringValue().regexpMatch("\\\\\\$(&|\\d)")
or
// `new` is `\c`, where `c` is a constant matched by `re`
new.regexpMatch("\\\\\\Q" + getAMatchedString(re) + "\\E")
// replacement of `c` with `\c`
exists(string c | mce.replaces(c, "\\" + c))
)
}
@@ -78,7 +72,7 @@ predicate allBackslashesEscaped(DataFlow::Node nd) {
nd = DataFlow::globalVarRef("JSON").getAMemberCall("stringify")
or
// check whether `nd` itself escapes backslashes
exists(RegExpLiteral rel | isBackslashEscape(nd.asExpr(), rel) |
exists(DataFlow::RegExpLiteralNode rel | isBackslashEscape(nd, rel) |
// if it's a complex regexp, we conservatively assume that it probably escapes backslashes
not isSimple(rel.getRoot()) or
getAMatchedString(rel) = "\\"
@@ -104,10 +98,8 @@ predicate allBackslashesEscaped(DataFlow::Node nd) {
/**
* Holds if `repl` looks like a call to "String.prototype.replace" that deliberately removes the first occurrence of `str`.
*/
predicate removesFirstOccurence(DataFlow::MethodCallNode repl, string str) {
repl.getMethodName() = "replace" and
repl.getArgument(0).getStringValue() = str and
repl.getArgument(1).getStringValue() = ""
predicate removesFirstOccurence(StringReplaceCall repl, string str) {
not exists(repl.getRegExp()) and repl.replaces(str, "")
}
/**
@@ -134,25 +126,30 @@ predicate isDelimiterUnwrapper(
}
/*
* Holds if `repl` is a standalone use of `String.prototype.replace` to remove a single newline.
* Holds if `repl` is a standalone use of `String.prototype.replace` to remove a single newline,
* dollar or percent character.
*
* This is often done on inputs that are known to only contain a single instance of the character,
* such as output from a shell command that is known to end with a single newline, or strings
* like "$1.20" or "50%".
*/
predicate removesTrailingNewLine(DataFlow::MethodCallNode repl) {
repl.getMethodName() = "replace" and
repl.getArgument(0).mayHaveStringValue("\n") and
repl.getArgument(1).mayHaveStringValue("") and
not exists(DataFlow::MethodCallNode other | other.getMethodName() = "replace" |
repl.getAMethodCall() = other or
other.getAMethodCall() = repl
predicate whitelistedRemoval(StringReplaceCall repl) {
not repl.isGlobal() and
exists(string s | s = "\n" or s = "%" or s = "$" |
repl.replaces(s, "") and
not exists(StringReplaceCall other |
repl.getAMethodCall() = other or
other.getAMethodCall() = repl
)
)
}
from MethodCallExpr repl, Expr old, string msg
from StringReplaceCall repl, DataFlow::Node old, string msg
where
repl.getMethodName() = "replace" and
(old = repl.getArgument(0) or old.flow().(DataFlow::SourceNode).flowsToExpr(repl.getArgument(0))) and
(old = repl.getArgument(0) or old = repl.getRegExp()) and
(
not old.(RegExpLiteral).isGlobal() and
not repl.isGlobal() and
msg = "This replaces only the first occurrence of " + old + "." and
// only flag if this is likely to be a sanitizer or URL encoder or decoder
exists(string m | m = getAMatchedString(old) |
@@ -166,19 +163,22 @@ where
// URL encoder
repl.getArgument(1).getStringValue().regexpMatch(urlEscapePattern)
)
or
// path sanitizer
(m = ".." or m = "/.." or m = "../" or m = "/../")
) and
// don't flag replace operations in a loop
not DataFlow::valueNode(repl.getReceiver()) = DataFlow::valueNode(repl).getASuccessor+() and
not repl.getReceiver() = repl.getASuccessor+() and
// dont' flag unwrapper
not isDelimiterUnwrapper(repl.flow(), _) and
not isDelimiterUnwrapper(_, repl.flow()) and
// dont' flag the removal of trailing newlines
not removesTrailingNewLine(repl.flow())
not isDelimiterUnwrapper(repl, _) and
not isDelimiterUnwrapper(_, repl) and
// don't flag replacements of certain characters with whitespace
not whitelistedRemoval(repl)
or
exists(RegExpLiteral rel |
exists(DataFlow::RegExpLiteralNode rel |
isBackslashEscape(repl, rel) and
not allBackslashesEscaped(DataFlow::valueNode(repl)) and
not allBackslashesEscaped(repl) and
msg = "This does not escape backslash characters in the input."
)
)
select repl.getCallee(), msg
select repl.getCalleeNode(), msg

View File

@@ -33,7 +33,9 @@ predicate benignContext(Expr e) {
inVoidContext(e) or
// A return statement is often used to just end the function.
e = any(Function f).getAReturnedExpr()
e = any(Function f).getBody()
or
e = any(ReturnStmt r).getExpr()
or
exists(ConditionalExpr cond | cond.getABranch() = e and benignContext(cond))
or
@@ -42,7 +44,6 @@ predicate benignContext(Expr e) {
exists(Expr parent | parent.getUnderlyingValue() = e and benignContext(parent))
or
any(VoidExpr voidExpr).getOperand() = e
or
// weeds out calls inside HTML-attributes.
e.getParent().(ExprStmt).getParent() instanceof CodeInAttribute or
@@ -70,8 +71,8 @@ predicate benignContext(Expr e) {
e = any(ResolvedPromiseDefinition promise).getValue().asExpr()
}
predicate oneshotClosure(InvokeExpr call) {
call.getCallee().getUnderlyingValue() instanceof ImmediatelyInvokedFunctionExpr
predicate oneshotClosure(DataFlow::CallNode call) {
call.getCalleeNode().asExpr().getUnderlyingValue() instanceof ImmediatelyInvokedFunctionExpr
}
predicate alwaysThrows(Function f) {
@@ -149,6 +150,12 @@ predicate voidArrayCallback(DataFlow::CallNode call, Function func) {
)
}
predicate hasNonVoidReturnType(Function f) {
exists(TypeAnnotation type | type = f.getReturnTypeAnnotation() |
not type.isVoid()
)
}
/**
* Provides classes for working with various Deferred implementations.
@@ -214,6 +221,8 @@ where
not benignContext(call.getEnclosingExpr()) and
not lastStatementHasNoEffect(func) and
// anonymous one-shot closure. Those are used in weird ways and we ignore them.
not oneshotClosure(call.getEnclosingExpr())
not oneshotClosure(call) and
not hasNonVoidReturnType(func) and
not call.getEnclosingExpr() instanceof SuperCall
select
call, msg, func, name

View File

@@ -76,6 +76,7 @@ import semmle.javascript.frameworks.Electron
import semmle.javascript.frameworks.Files
import semmle.javascript.frameworks.Firebase
import semmle.javascript.frameworks.jQuery
import semmle.javascript.frameworks.Handlebars
import semmle.javascript.frameworks.LodashUnderscore
import semmle.javascript.frameworks.Logging
import semmle.javascript.frameworks.HttpFrameworks

View File

@@ -166,7 +166,7 @@ class Require extends CallExpr, Import {
exists(RequireVariable req |
this.getCallee() = req.getAnAccess() and
// `mjs` files explicitly disallow `require`
getFile().getExtension() != "mjs"
not getFile().getExtension() = "mjs"
)
}

View File

@@ -173,6 +173,23 @@ class RegExpTerm extends Locatable, @regexpterm {
parent.(StringLiteral).flow() instanceof RegExpPatternSource
)
}
/**
* Gets the single string this regular-expression term matches.
*
* This predicate is only defined for (sequences/groups of) constant regular expressions.
* In particular, terms involving zero-width assertions like `^` or `\b` are not considered
* to have a constant value.
*
* Note that this predicate does not take flags of the enclosing regular-expression literal
* into account.
*/
string getConstantValue() { none() }
/**
* Gets a string that is matched by this regular-expression term.
*/
string getAMatchedString() { result = getConstantValue() }
}
/**
@@ -223,6 +240,8 @@ class RegExpConstant extends RegExpTerm, @regexp_constant {
predicate isCharacter() { any() }
override predicate isNullable() { none() }
override string getConstantValue() { result = getValue() }
}
/**
@@ -266,6 +285,8 @@ class RegExpAlt extends RegExpTerm, @regexp_alt {
int getNumAlternative() { result = getNumChild() }
override predicate isNullable() { getAlternative().isNullable() }
override string getAMatchedString() { result = getAlternative().getAMatchedString() }
}
/**
@@ -289,6 +310,21 @@ class RegExpSequence extends RegExpTerm, @regexp_seq {
override predicate isNullable() {
forall(RegExpTerm child | child = getAChild() | child.isNullable())
}
override string getConstantValue() {
result = getConstantValue(0)
}
/**
* Gets the single string matched by the `i`th child and all following children of
* this sequence, if any.
*/
private string getConstantValue(int i) {
i = getNumChild() and
result = ""
or
result = getChild(i).getConstantValue() + getConstantValue(i+1)
}
}
/**
@@ -549,6 +585,10 @@ class RegExpGroup extends RegExpTerm, @regexp_group {
string getName() { isNamedCapture(this, result) }
override predicate isNullable() { getAChild().isNullable() }
override string getConstantValue() { result = getAChild().getConstantValue() }
override string getAMatchedString() { result = getAChild().getAMatchedString() }
}
/**
@@ -734,6 +774,10 @@ class RegExpCharacterClass extends RegExpTerm, @regexp_char_class {
predicate isInverted() { isInverted(this) }
override predicate isNullable() { none() }
override string getAMatchedString() {
not isInverted() and result = getAChild().getAMatchedString()
}
}
/**

View File

@@ -248,3 +248,62 @@ private class IteratorExceptionStep extends DataFlow::MethodCallNode, DataFlow::
succ = this.getExceptionalReturn()
}
}
/**
* A call to `String.prototype.replace`.
*
* We heuristically include any call to a method called `replace`, provided it either
* has exactly two arguments, or local data flow suggests that the receiver may be a string.
*/
class StringReplaceCall extends DataFlow::MethodCallNode {
StringReplaceCall() {
getMethodName() = "replace" and
(getNumArgument() = 2 or getReceiver().mayHaveStringValue(_))
}
/** Gets the regular expression passed as the first argument to `replace`, if any. */
DataFlow::RegExpLiteralNode getRegExp() {
result.flowsTo(getArgument(0))
}
/** Gets a string that is being replaced by this call. */
string getAReplacedString() {
result = getRegExp().getRoot().getAMatchedString() or
getArgument(0).mayHaveStringValue(result)
}
/**
* Gets the second argument of this call to `replace`, which is either a string
* or a callback.
*/
DataFlow::Node getRawReplacement() {
result = getArgument(1)
}
/**
* Holds if this is a global replacement, that is, the first argument is a regular expression
* with the `g` flag.
*/
predicate isGlobal() {
getRegExp().isGlobal()
}
/**
* Holds if this call to `replace` replaces `old` with `new`.
*/
predicate replaces(string old, string new) {
exists(string rawNew |
old = getAReplacedString() and
getRawReplacement().mayHaveStringValue(rawNew) and
new = rawNew.replaceAll("$&", old)
)
or
exists(DataFlow::FunctionNode replacer, DataFlow::PropRead pr, DataFlow::ObjectLiteralNode map |
replacer = getCallback(1) and
replacer.getParameter(0).flowsToExpr(pr.getPropertyNameExpr()) and
pr = map.getAPropertyRead() and
pr.flowsTo(replacer.getAReturn()) and
map.hasPropertyWrite(old, any(DataFlow::Node repl | repl.getStringValue() = new))
)
}
}

View File

@@ -1816,7 +1816,7 @@ class Type extends @type {
*
* For example, for a type `(S & T) | U` this gets the types `S`, `T`, and `U`.
*/
Type unfold() {
Type unfoldUnionAndIntersection() {
not result instanceof UnionOrIntersectionType and
(
result = this
@@ -1829,6 +1829,27 @@ class Type extends @type {
)
}
/**
* Repeatedly unfolds unions, intersections, and type aliases and gets any of the underlying types,
* or this type itself if it is not a union or intersection.
*
* For example, the type `(S & T) | U` unfolds to `S`, `T`, and `U`.
*
* If this is a type alias, the alias is itself included in the result, but this is not the case for intermediate type aliases.
* For example:
* ```js
* type One = number | string;
* type Two = One | Function & {x: string};
* One; // unfolds to number, string, and One
* Two; // unfolds to number, string, One, Function, {x: string}, and Two
* ```
*/
Type unfold() {
result = unfoldUnionAndIntersection()
or
result = this.(TypeAliasReference).getAliasedType().unfoldUnionAndIntersection()
}
/**
* Holds if this refers to the given named type, or is declared as a subtype thereof,
* or is a union or intersection containing such a type.
@@ -2287,6 +2308,24 @@ class EnumLiteralType extends TypeReference {
EnumMember getEnumMember() { result = declaration }
}
/**
* A type that refers to a type alias.
*/
class TypeAliasReference extends TypeReference {
TypeAliasReference() {
type_alias(this, _)
}
/**
* Gets the type behind the type alias.
*
* For example, for `type B<T> = T[][]`, this maps the type `B<number>` to `number[][]`.
*/
Type getAliasedType() {
type_alias(this, result)
}
}
/**
* An anonymous interface type, such as `{ x: number }`.
*/
@@ -2516,17 +2555,19 @@ class CallSignatureType extends @signature_type {
predicate hasTypeParameters() { getNumTypeParameter() > 0 }
/**
* Gets the type of the `n`th parameter of this signature.
* Gets the type of the `n`th parameter declared in this signature.
*
* If the `n`th parameter is a rest parameter `...T[]`, gets type `T`.
*/
Type getParameter(int n) { n >= 0 and result = getChild(n + getNumTypeParameter()) }
/**
* Gets the type of a parameter of this signature.
* Gets the type of a parameter of this signature, including the rest parameter, if any.
*/
Type getAParameter() { result = getParameter(_) }
/**
* Gets the number of parameters.
* Gets the number of parameters, including the rest parameter, if any.
*/
int getNumParameter() { result = count(int i | exists(getParameter(i))) }
@@ -2538,7 +2579,7 @@ class CallSignatureType extends @signature_type {
/**
* Gets the number of optional parameters, that is,
* parameters that are marked as optional with the `?` suffix.
* parameters that are marked as optional with the `?` suffix or is a rest parameter.
*/
int getNumOptionalParameter() { result = getNumParameter() - getNumRequiredParameter() }
@@ -2552,7 +2593,9 @@ class CallSignatureType extends @signature_type {
}
/**
* Holds if the `n`th parameter is declared optional with the `?` suffix.
* Holds if the `n`th parameter is declared optional with the `?` suffix or is the rest parameter.
*
* Note that rest parameters are not considered optional in this sense.
*/
predicate isOptionalParameter(int n) {
exists(getParameter(n)) and
@@ -2571,6 +2614,30 @@ class CallSignatureType extends @signature_type {
* Gets the name of a parameter of this signature.
*/
string getAParameterName() { result = getParameterName(_) }
/**
* Holds if this signature declares a rest parameter, such as `(x: number, ...y: string[])`.
*/
predicate hasRestParameter() { signature_rest_parameter(this, _) }
/**
* Gets the type of the rest parameter, if any.
*
* For example, for the signature `(...y: string[])`, this gets the type `string`.
*/
Type getRestParameterType() {
hasRestParameter() and
result = getParameter(getNumParameter() - 1)
}
/**
* Gets the type of the rest parameter as an array, if it exists.
*
* For example, for the signature `(...y: string[])`, this gets the type `string[]`.
*/
PlainArrayType getRestParameterArrayType() {
signature_rest_parameter(this, result)
}
}
/**

View File

@@ -749,6 +749,21 @@ class Parameter extends BindingPattern {
JSDocTag getJSDocTag() {
none() // overridden in SimpleParameter
}
/**
* Holds if this is a parameter declared optional with the `?` token.
*
* Note that this does not hold for rest parameters, and does not in general
* hold for parameters with defaults.
*
* For example, `x`, is declared optional below:
* ```
* function f(x?: number) {}
* ```
*/
predicate isDeclaredOptional() {
isOptionalParameterDeclaration(this)
}
}
/**

View File

@@ -530,6 +530,24 @@ class ArrayLiteralNode extends DataFlow::ValueNode, DataFlow::SourceNode {
int getSize() { result = astNode.getSize() }
}
/**
* A data-flow node corresponding to a regular-expression literal.
*
* Examples:
* ```js
* /https?/i
* ```
*/
class RegExpLiteralNode extends DataFlow::ValueNode, DataFlow::SourceNode {
override RegExpLiteral astNode;
/** Holds if this regular expression has a `g` flag. */
predicate isGlobal() { astNode.isGlobal() }
/** Gets the root term of this regular expression. */
RegExpTerm getRoot() { result = astNode.getRoot() }
}
/**
* A data flow node corresponding to a `new Array()` or `Array()` invocation.
*

View File

@@ -676,6 +676,20 @@ module TaintTracking {
}
}
/**
* A taint step through the Node.JS function `util.inspect(..)`.
*/
class UtilInspectTaintStep extends AdditionalTaintStep, DataFlow::InvokeNode {
UtilInspectTaintStep() {
this = DataFlow::moduleImport("util").getAMemberCall("inspect")
}
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
succ = this and
this.getAnArgument() = pred
}
}
/**
* A conditional checking a tainted string against a regular expression, which is
* considered to be a sanitizer for all configurations.

View File

@@ -0,0 +1,29 @@
/**
* Provides classes for working with Handlebars code.
*/
import javascript
module Handlebars {
/**
* A reference to the Handlebars library.
*/
class Handlebars extends DataFlow::SourceNode {
Handlebars() {
this.accessesGlobal("handlebars")
or
this.accessesGlobal("Handlebars")
or
this = DataFlow::moduleImport("handlebars")
or
this.hasUnderlyingType("Handlebars")
}
}
/**
* A new instantiation of a Handlebars.SafeString.
*/
class SafeString extends DataFlow::NewNode {
SafeString() { this = any(Handlebars h).getAConstructorInvocation("SafeString") }
}
}

View File

@@ -61,6 +61,15 @@ private module Console {
if name = "assert"
then result = getArgument([1 .. getNumArgument()])
else result = getAnArgument()
or
result = getASpreadArgument()
}
/**
* Gets the name of the console logging method, e.g. "log", "error", "assert", etc.
*/
string getName() {
result = name
}
}
}

View File

@@ -136,7 +136,12 @@ abstract class ReactComponent extends ASTNode {
result = arg0
)
or
result.flowsToExpr(getStaticMethod("getDerivedStateFromProps").getAReturnedExpr())
exists(string staticMember |
staticMember = "getDerivedStateFromProps" or
staticMember = "getDerivedStateFromError"
|
result.flowsToExpr(getStaticMethod(staticMember).getAReturnedExpr())
)
or
// shouldComponentUpdate: (nextProps, nextState)
result = DataFlow::parameterNode(getInstanceMethod("shouldComponentUpdate").getParameter(1))

View File

@@ -13,7 +13,7 @@ module CleartextLogging {
import CleartextLoggingCustomizations::CleartextLogging
/**
* A dataflow tracking configuration for clear-text logging of sensitive information.
* A taint tracking configuration for clear-text logging of sensitive information.
*
* This configuration identifies flows from `Source`s, which are sources of
* sensitive data, to `Sink`s, which is an abstract class representing all
@@ -21,27 +21,37 @@ module CleartextLogging {
* added either by extending the relevant class, or by subclassing this configuration itself,
* and amending the sources and sinks.
*/
class Configuration extends DataFlow::Configuration {
class Configuration extends TaintTracking::Configuration {
Configuration() { this = "CleartextLogging" }
override predicate isSource(DataFlow::Node source) { source instanceof Source }
override predicate isSource(DataFlow::Node source, DataFlow::FlowLabel lbl) {
source.(Source).getLabel() = lbl
}
override predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
override predicate isSink(DataFlow::Node sink, DataFlow::FlowLabel lbl) {
sink.(Sink).getLabel() = lbl
}
override predicate isBarrier(DataFlow::Node node) { node instanceof Barrier }
override predicate isSanitizer(DataFlow::Node node) { node instanceof Barrier }
override predicate isAdditionalFlowStep(DataFlow::Node src, DataFlow::Node trg) {
StringConcatenation::taintStep(src, trg)
or
exists(string name | name = "toString" or name = "valueOf" |
src.(DataFlow::SourceNode).getAMethodCall(name) = trg
)
or
override predicate isSanitizerEdge(DataFlow::Node pred, DataFlow::Node succ) {
succ.(DataFlow::PropRead).getBase() = pred
}
override predicate isAdditionalTaintStep(DataFlow::Node src, DataFlow::Node trg) {
// A taint propagating data flow edge through objects: a tainted write taints the entire object.
exists(DataFlow::PropWrite write |
write.getRhs() = src and
trg.(DataFlow::SourceNode).flowsTo(write.getBase())
)
or
// Taint through the arguments object.
exists(DataFlow::CallNode call, Function f |
src = call.getAnArgument() and
f = call.getACallee() and
not call.isImprecise() and
trg.asExpr() = f.getArgumentsVariable().getAnAccess()
)
}
}
}

View File

@@ -15,17 +15,39 @@ module CleartextLogging {
abstract class Source extends DataFlow::Node {
/** Gets a string that describes the type of this data flow source. */
abstract string describe();
abstract DataFlow::FlowLabel getLabel();
}
/**
* A data flow sink for clear-text logging of sensitive information.
*/
abstract class Sink extends DataFlow::Node { }
abstract class Sink extends DataFlow::Node {
DataFlow::FlowLabel getLabel() {
result.isDataOrTaint()
}
}
/**
* A barrier for clear-text logging of sensitive information.
*/
abstract class Barrier extends DataFlow::Node { }
/**
* A call to `.replace()` that seems to mask sensitive information.
*/
class MaskingReplacer extends Barrier, DataFlow::MethodCallNode {
MaskingReplacer() {
this.getCalleeName() = "replace" and
exists(RegExpLiteral reg |
reg = this.getArgument(0).getALocalSource().asExpr() and
reg.isGlobal() and
any(RegExpDot term).getLiteral() = reg
)
and
exists(this.getArgument(1).getStringValue())
}
}
/**
* An argument to a logging mechanism.
@@ -107,6 +129,10 @@ module CleartextLogging {
}
override string describe() { result = "an access to " + name }
override DataFlow::FlowLabel getLabel() {
result.isData()
}
}
/** An access to a variable or property that might contain a password. */
@@ -131,6 +157,10 @@ module CleartextLogging {
}
override string describe() { result = "an access to " + name }
override DataFlow::FlowLabel getLabel() {
result.isData()
}
}
/** A call that might return a password. */
@@ -143,5 +173,33 @@ module CleartextLogging {
}
override string describe() { result = "a call to " + name }
override DataFlow::FlowLabel getLabel() {
result.isData()
}
}
/** An access to the sensitive object `process.env`. */
class ProcessEnvSource extends Source {
ProcessEnvSource() {
this = NodeJSLib::process().getAPropertyRead("env")
}
override string describe() { result = "process environment" }
override DataFlow::FlowLabel getLabel() {
result.isData() or
result instanceof PartiallySensitiveMap
}
}
/**
* A flow label describing a map that might contain sensitive information in some properties.
* Property reads on such maps where the property name is fixed is unlikely to leak sensitive information.
*/
class PartiallySensitiveMap extends DataFlow::FlowLabel {
PartiallySensitiveMap() {
this = "PartiallySensitiveMap"
}
}
}

View File

@@ -95,6 +95,8 @@ module DomBasedXss {
mcn.getMethodName() = m and
this = mcn.getArgument(1)
)
or
this = any(Handlebars::SafeString s).getAnArgument()
}
}

View File

@@ -520,6 +520,7 @@ hasProtectedKeyword (int id: @property ref);
hasReadonlyKeyword (int id: @property ref);
isOptionalMember (int id: @property ref);
hasDefiniteAssignmentAssertion (int id: @field_or_vardeclarator ref);
isOptionalParameterDeclaration (unique int parameter: @pattern ref);
#keyset[constructor, param_index]
parameter_fields(
@@ -703,6 +704,10 @@ type_property(
varchar(900) name: string ref,
int propertyType: @type ref);
type_alias(
unique int aliasType: @type ref,
int underlyingType: @type ref);
@literaltype = @stringliteraltype | @numberliteraltype | @booleanliteraltype | @bigintliteraltype;
@type_with_literal_value = @stringliteraltype | @numberliteraltype | @bigintliteraltype;
type_literal_value(
@@ -717,6 +722,11 @@ signature_types (
int required_params: int ref
);
signature_rest_parameter(
unique int sig: @signature_type ref,
int rest_param_arra_type: @type ref
);
case @signature_type.kind of
0 = @function_signature_type
| 1 = @constructor_signature_type

View File

@@ -8006,6 +8006,17 @@
<dependencies/>
</relation>
<relation>
<name>isOptionalParameterDeclaration</name>
<cardinality>3966</cardinality>
<columnsizes>
<e>
<k>parameter</k>
<v>3966</v>
</e>
</columnsizes>
<dependencies/>
</relation>
<relation>
<name>parameter_fields</name>
<cardinality>2693</cardinality>
<columnsizes>
@@ -13516,6 +13527,54 @@
<dependencies/>
</relation>
<relation>
<name>type_alias</name>
<cardinality>1386</cardinality>
<columnsizes>
<e>
<k>aliasType</k>
<v>1386</v>
</e>
<e>
<k>underlyingType</k>
<v>1361</v>
</e>
</columnsizes>
<dependencies>
<dep>
<src>underlyingType</src>
<trg>aliasType</trg>
<val>
<hist>
<budget>12</budget>
<bs>
<b>
<a>1</a>
<b>2</b>
<v>1</v>
</b>
</bs>
</hist>
</val>
</dep>
<dep>
<src>aliasType</src>
<trg>underlyingType</trg>
<val>
<hist>
<budget>12</budget>
<bs>
<b>
<a>1</a>
<b>2</b>
<v>1</v>
</b>
</bs>
</hist>
</val>
</dep>
</dependencies>
</relation>
<relation>
<name>type_literal_value</name>
<cardinality>31882</cardinality>
<columnsizes>
@@ -14222,6 +14281,54 @@
</dependencies>
</relation>
<relation>
<name>signature_rest_parameter</name>
<cardinality>19521</cardinality>
<columnsizes>
<e>
<k>sig</k>
<v>19521</v>
</e>
<e>
<k>rest_param_arra_type</k>
<v>14259</v>
</e>
</columnsizes>
<dependencies>
<dep>
<src>rest_param_arra_type</src>
<trg>sig</trg>
<val>
<hist>
<budget>12</budget>
<bs>
<b>
<a>1</a>
<b>2</b>
<v>1</v>
</b>
</bs>
</hist>
</val>
</dep>
<dep>
<src>sig</src>
<trg>rest_param_arra_type</trg>
<val>
<hist>
<budget>12</budget>
<bs>
<b>
<a>1</a>
<b>2</b>
<v>1</v>
</b>
</bs>
</hist>
</val>
</dep>
</dependencies>
</relation>
<relation>
<name>type_contains_signature</name>
<cardinality>87640</cardinality>
<columnsizes>

View File

@@ -47,14 +47,39 @@ test_ExprSignature
| tst.ts:45:15:45:15 | x | string |
| tst.ts:46:3:46:25 | constru ... umber); | any |
| tst.ts:46:15:46:15 | x | number |
| tst.ts:50:3:50:36 | method( ... ing[]); | (x: number, ...y: string[]): any |
| tst.ts:50:10:50:10 | x | number |
| tst.ts:50:24:50:24 | y | string[] |
| tst.ts:51:4:51:4 | x | number |
| tst.ts:51:18:51:18 | y | string[] |
| tst.ts:52:7:52:7 | x | number |
| tst.ts:52:21:52:21 | y | string[] |
| tst.ts:54:3:54:34 | method2 ... ing[]); | (x: number, y: string[]): any |
| tst.ts:54:11:54:11 | x | number |
| tst.ts:54:22:54:22 | y | string[] |
| tst.ts:55:3:55:32 | method3 ... tring); | (x: number, y: string): any |
| tst.ts:55:11:55:11 | x | number |
| tst.ts:55:22:55:22 | y | string |
| tst.ts:59:3:59:25 | method( ... ing[]); | (...y: string[]): any |
| tst.ts:59:13:59:13 | y | string[] |
| tst.ts:60:7:60:7 | y | string[] |
| tst.ts:61:10:61:10 | y | string[] |
| tst.ts:63:3:63:23 | method2 ... ing[]); | (y: string[]): any |
| tst.ts:63:11:63:11 | y | string[] |
| tst.ts:64:3:64:21 | method3(y: string); | (y: string): any |
| tst.ts:64:11:64:11 | y | string |
test_TypeReferenceSig
| Callable | function | 0 | (x: number): string |
| Newable | constructor | 0 | new (x: number): any |
| OnlyRestParams | constructor | 0 | new (...y: string[]): any |
| OnlyRestParams | function | 0 | (...y: string[]): any |
| OverloadedCallable | function | 0 | (x: number): number |
| OverloadedCallable | function | 1 | (x: string): string |
| OverloadedCallable | function | 2 | (x: any): any |
| OverloadedNewable | constructor | 0 | new (x: number): OverloadedNewable |
| OverloadedNewable | constructor | 1 | new (x: any): any |
| WithRestParams | constructor | 0 | new (x: number, ...y: string[]): any |
| WithRestParams | function | 0 | (x: number, ...y: string[]): any |
test_FunctionCallSig
| tst.ts:2:3:2:22 | (x: number): string; | (x: number): string |
| tst.ts:6:3:6:22 | (x: number): number; | (x: number): number |
@@ -72,3 +97,62 @@ test_FunctionCallSig
| tst.ts:40:1:42:1 | functio ... oo");\\n} | (g: Generic<string>): string |
| tst.ts:45:3:45:25 | constru ... tring); | new (x: string): C |
| tst.ts:46:3:46:25 | constru ... umber); | new (x: number): C |
| tst.ts:50:3:50:36 | method( ... ing[]); | (x: number, ...y: string[]): any |
| tst.ts:51:3:51:30 | (x: num ... ing[]); | (x: number, ...y: string[]): any |
| tst.ts:52:3:52:33 | new(x: ... ing[]); | new (x: number, ...y: string[]): any |
| tst.ts:54:3:54:34 | method2 ... ing[]); | (x: number, y: string[]): any |
| tst.ts:55:3:55:32 | method3 ... tring); | (x: number, y: string): any |
| tst.ts:59:3:59:25 | method( ... ing[]); | (...y: string[]): any |
| tst.ts:60:3:60:19 | (...y: string[]); | (...y: string[]): any |
| tst.ts:61:3:61:22 | new(...y: string[]); | new (...y: string[]): any |
| tst.ts:63:3:63:23 | method2 ... ing[]); | (y: string[]): any |
| tst.ts:64:3:64:21 | method3(y: string); | (y: string): any |
test_getRestParameterType
| (...items: (string \| ConcatArray<string>)[]): string[] | string \| ConcatArray<string> |
| (...items: ConcatArray<string>[]): string[] | ConcatArray<string> |
| (...items: string[]): number | string |
| (...strings: string[]): string | string |
| (...y: string[]): any | string |
| (start: number, deleteCount: number, ...items: string[]): string[] | string |
| (substring: string, ...args: any[]): string | any |
| (x: number, ...y: string[]): any | string |
| new (...y: string[]): any | string |
| new (x: number, ...y: string[]): any | string |
test_getRestParameterArray
| (...items: (string \| ConcatArray<string>)[]): string[] | (string \| ConcatArray<string>)[] |
| (...items: ConcatArray<string>[]): string[] | ConcatArray<string>[] |
| (...items: string[]): number | string[] |
| (...strings: string[]): string | string[] |
| (...y: string[]): any | string[] |
| (start: number, deleteCount: number, ...items: string[]): string[] | string[] |
| (substring: string, ...args: any[]): string | any[] |
| (x: number, ...y: string[]): any | string[] |
| new (...y: string[]): any | string[] |
| new (x: number, ...y: string[]): any | string[] |
test_RestSig_getParameter
| (...items: (string \| ConcatArray<string>)[]): string[] | 0 | items | string \| ConcatArray<string> |
| (...items: ConcatArray<string>[]): string[] | 0 | items | ConcatArray<string> |
| (...items: string[]): number | 0 | items | string |
| (...strings: string[]): string | 0 | strings | string |
| (...y: string[]): any | 0 | y | string |
| (start: number, deleteCount: number, ...items: string[]): string[] | 0 | start | number |
| (start: number, deleteCount: number, ...items: string[]): string[] | 1 | deleteCount | number |
| (start: number, deleteCount: number, ...items: string[]): string[] | 2 | items | string |
| (substring: string, ...args: any[]): string | 0 | substring | string |
| (substring: string, ...args: any[]): string | 1 | args | any |
| (x: number, ...y: string[]): any | 0 | x | number |
| (x: number, ...y: string[]): any | 1 | y | string |
| new (...y: string[]): any | 0 | y | string |
| new (x: number, ...y: string[]): any | 0 | x | number |
| new (x: number, ...y: string[]): any | 1 | y | string |
test_RestSig_numRequiredParams
| (...items: (string \| ConcatArray<string>)[]): string[] | 0 |
| (...items: ConcatArray<string>[]): string[] | 0 |
| (...items: string[]): number | 0 |
| (...strings: string[]): string | 0 |
| (...y: string[]): any | 0 |
| (start: number, deleteCount: number, ...items: string[]): string[] | 2 |
| (substring: string, ...args: any[]): string | 1 |
| (x: number, ...y: string[]): any | 1 |
| new (...y: string[]): any | 0 |
| new (x: number, ...y: string[]): any | 1 |

View File

@@ -20,3 +20,22 @@ query predicate test_TypeReferenceSig(TypeReference type, SignatureKind kind, in
query predicate test_FunctionCallSig(Function f, CallSignatureType sig) {
sig = f.getCallSignature()
}
query Type test_getRestParameterType(CallSignatureType sig) {
result = sig.getRestParameterType()
}
query Type test_getRestParameterArray(CallSignatureType sig) {
result = sig.getRestParameterArrayType()
}
query predicate test_RestSig_getParameter(CallSignatureType sig, int n, string name, Type type) {
sig.hasRestParameter() and
name = sig.getParameterName(n) and
type = sig.getParameter(n)
}
query int test_RestSig_numRequiredParams(CallSignatureType sig) {
sig.hasRestParameter() and
result = sig.getNumRequiredParameter()
}

View File

@@ -45,3 +45,21 @@ declare class C {
constructor(x: string);
constructor(x: number);
}
interface WithRestParams {
method(x: number, ...y: string[]);
(x: number, ...y: string[]);
new(x: number, ...y: string[]);
method2(x: number, y: string[]);
method3(x: number, y: string);
}
interface OnlyRestParams {
method(...y: string[]);
(...y: string[]);
new(...y: string[]);
method2(y: string[]);
method3(y: string);
}

View File

@@ -0,0 +1,9 @@
exprType
| tst.ts:1:5:1:16 | stringOrNUll | string \| null |
| tst.ts:2:5:2:21 | stringOrUndefined | string \| undefined |
| tst.ts:3:5:3:27 | stringO ... defined | string \| null \| undefined |
| tst.ts:4:5:4:16 | stringOrVoid | string \| void |
| tst.ts:7:5:7:21 | stringOrNullAlias | string \| null |
| tst.ts:8:5:8:32 | stringO ... defined | string \| null \| undefined |
| tst.ts:10:5:10:23 | arrayOfStringOrNull | (string \| null)[] |
unaliasedType

View File

@@ -0,0 +1,5 @@
import javascript
query Type exprType(Expr e) { result = e.getType() }
query Type unaliasedType(TypeAliasReference ref) { result = ref.getAliasedType() }

View File

@@ -0,0 +1,6 @@
{
"include": ["."],
"compilerOptions": {
"strict": true
}
}

View File

@@ -0,0 +1,10 @@
let stringOrNUll: string | null;
let stringOrUndefined: string | undefined;
let stringOrNullOrUndefined: string | null | undefined;
let stringOrVoid: string | void;
type StringOrNullAlias = string | null;
let stringOrNullAlias: StringOrNullAlias;
let stringOrNullAliasOrUndefined: StringOrNullAlias | undefined;
let arrayOfStringOrNull: Array<string | null>;

View File

@@ -0,0 +1,15 @@
| tst.ts:1:23:1:23 | y |
| tst.ts:2:16:2:16 | y |
| tst.ts:6:28:6:28 | y |
| tst.ts:8:12:8:12 | x |
| tst.ts:8:24:8:24 | y |
| tst.ts:9:13:9:13 | x |
| tst.ts:13:7:13:7 | x |
| tst.ts:14:13:14:13 | x |
| tst.ts:15:17:15:17 | y |
| tst.ts:18:26:18:26 | y |
| tst.ts:18:30:18:30 | z |
| tst.ts:18:34:18:34 | w |
| tst.ts:20:40:20:45 | {x, y} |
| tst.ts:20:49:20:51 | [w] |
| withDefault.ts:1:22:1:22 | x |

View File

@@ -0,0 +1,3 @@
import javascript
query Parameter optionalParams() { result.isDeclaredOptional() }

View File

@@ -0,0 +1,20 @@
function f(x: number, y?: string) {
return (x, y?) => {};
}
class C {
constructor(x: number, y?: string) {}
method(x?: number, y?: string) {}
noTypes(x?) {}
}
interface I {
m(x?: number);
field: (x?: number) => void;
(x: number, y?: string): void;
}
function manyDefaults(x, y?, z?, w?) {}
declare function optionalDestructuring({x, y}?, [w]?);

View File

@@ -0,0 +1 @@
function withDefault(x? = 5) {} // not valid syntax

View File

@@ -0,0 +1,2 @@
import f = require("./tst");
f("world");

View File

@@ -0,0 +1,5 @@
| main.ts:1:8:1:8 | f | (x: string) => string |
| main.ts:1:20:1:26 | "./tst" | any |
| main.ts:2:1:2:1 | f | (x: string) => string |
| main.ts:2:1:2:10 | f("world") | string |
| main.ts:2:3:2:9 | "world" | "world" |

View File

@@ -0,0 +1,4 @@
import javascript
from Expr e
select e, e.getType()

View File

@@ -0,0 +1,6 @@
{
"compilerOptions": {
"allowJs": true
},
"include": ["."]
}

View File

@@ -0,0 +1,6 @@
/**
* @param {String} x
*/
module.exports = function(x) {
return 'Hello ' + x;
}

View File

@@ -0,0 +1 @@
| tst.ts:0:0:0:0 | tst.ts |

View File

@@ -0,0 +1,4 @@
import javascript
from File file
select file

View File

@@ -0,0 +1,6 @@
{
"include": ["."],
"compilerOptions": {
"typeRoots": ["does-not-exist"]
}
}

View File

@@ -1,2 +0,0 @@
| test.ts:3:1:7:6 | type Di ... \\n T; | Disjunction<T> |
| test.ts:9:8:9:65 | type Tr ... n<T>>>; | Disjunction<Disjunction<Disjunction<T>>> |

View File

@@ -0,0 +1,2 @@
| tst.ts:0:0:0:0 | tst.ts |
| typeroot.d.ts:0:0:0:0 | typeroot.d.ts |

View File

@@ -0,0 +1,4 @@
import javascript
from File file
select file

View File

@@ -0,0 +1,6 @@
{
"include": ["."],
"compilerOptions": {
"typeRoots": ["typeroot.d.ts"]
}
}

View File

@@ -1,3 +1,44 @@
rightHandSide
| tst.ts:1:1:1:16 | type A = number; | number |
| tst.ts:2:1:2:16 | type B<T> = T[]; | T[] |
| tst.ts:8:10:8:20 | type C = A; | number |
| tst.ts:15:1:15:23 | type Un ... \| Two; | One \| Two |
| tst.ts:17:1:17:36 | type Un ... mber }; | (One & { x: number; }) \| (Two & { x: number; }) |
| tst.ts:18:1:18:21 | type Un ... Union2; | (One & { x: number; }) \| (Two & { x: number; }) |
| tst.ts:19:1:19:21 | type Un ... Union3; | (One & { x: number; }) \| (Two & { x: number; }) |
| tst.ts:20:1:20:30 | type Un ... number; | number \| (One & { x: number; }) \| (Two & { x: n... |
getAliasedType
| B<T> | T[] |
| B<number> | number[] |
| Union | One \| Two |
| Union2 | (One & { x: number; }) \| (Two & { x: number; }) |
| Union5 | number \| (One & { x: number; }) \| (Two & { x: n... |
getTypeArgument
| B<T> | 0 | T |
| B<number> | 0 | number |
unfold
| B<T> | B<T> |
| B<T> | T[] |
| B<number> | B<number> |
| B<number> | number[] |
| Union | One |
| Union | Two |
| Union | Union |
| Union2 | One |
| Union2 | Two |
| Union2 | Union2 |
| Union2 | { x: number; } |
| Union5 | One |
| Union5 | Two |
| Union5 | Union5 |
| Union5 | number |
| Union5 | { x: number; } |
#select
| tst.ts:1:1:1:16 | type A = number; | tst.ts:1:6:1:6 | A | 0 | tst.ts:1:10:1:15 | number |
| tst.ts:2:1:2:16 | type B<T> = T[]; | tst.ts:2:6:2:6 | B | 1 | tst.ts:2:13:2:15 | T[] |
| tst.ts:8:10:8:20 | type C = A; | tst.ts:8:15:8:15 | C | 0 | tst.ts:8:19:8:19 | A |
| tst.ts:15:1:15:23 | type Un ... \| Two; | tst.ts:15:6:15:10 | Union | 0 | tst.ts:15:14:15:22 | One \| Two |
| tst.ts:17:1:17:36 | type Un ... mber }; | tst.ts:17:6:17:11 | Union2 | 0 | tst.ts:17:15:17:35 | Union & ... umber } |
| tst.ts:18:1:18:21 | type Un ... Union2; | tst.ts:18:6:18:11 | Union3 | 0 | tst.ts:18:15:18:20 | Union2 |
| tst.ts:19:1:19:21 | type Un ... Union3; | tst.ts:19:6:19:11 | Union4 | 0 | tst.ts:19:15:19:20 | Union3 |
| tst.ts:20:1:20:30 | type Un ... number; | tst.ts:20:6:20:11 | Union5 | 0 | tst.ts:20:15:20:29 | Union4 \| number |

View File

@@ -2,3 +2,19 @@ import javascript
from TypeAliasDeclaration decl
select decl, decl.getIdentifier(), decl.getNumTypeParameter(), decl.getDefinition()
query Type rightHandSide(TypeAliasDeclaration decl) {
result = decl.getDefinition().getType()
}
query Type getAliasedType(TypeAliasReference ref) {
result = ref.getAliasedType()
}
query Type getTypeArgument(TypeAliasReference ref, int n) {
result = ref.getTypeArgument(n)
}
query Type unfold(TypeAliasReference t) {
result = t.unfold()
}

View File

@@ -0,0 +1,3 @@
{
"include": ["."]
}

View File

@@ -8,3 +8,13 @@ namespace Q {
export type C = A;
}
var z: Q.C;
interface One { a: number }
interface Two { b: number }
type Union = One | Two;
type Union2 = Union & { x: number };
type Union3 = Union2;
type Union4 = Union3;
type Union5 = Union4 | number;

View File

@@ -74,7 +74,7 @@
| type_alias.ts:5:6:5:17 | ValueOrArray | ValueOrArray<T> |
| type_alias.ts:5:19:5:19 | T | T |
| type_alias.ts:5:24:5:24 | T | T |
| type_alias.ts:5:24:5:49 | T \| Arr ... ray<T>> | ValueOrArray<T> |
| type_alias.ts:5:24:5:49 | T \| Arr ... ray<T>> | T \| ValueOrArray<T>[] |
| type_alias.ts:5:28:5:32 | Array | T[] |
| type_alias.ts:5:28:5:49 | Array<V ... ray<T>> | ValueOrArray<T>[] |
| type_alias.ts:5:34:5:45 | ValueOrArray | ValueOrArray<T> |
@@ -84,7 +84,7 @@
| type_alias.ts:7:8:7:27 | ValueOrArray<number> | ValueOrArray<number> |
| type_alias.ts:7:21:7:26 | number | number |
| type_alias.ts:9:6:9:9 | Json | Json |
| type_alias.ts:10:5:15:12 | \| strin ... Json[] | Json |
| type_alias.ts:10:5:15:12 | \| strin ... Json[] | string \| number \| boolean \| { [property: string... |
| type_alias.ts:10:7:10:12 | string | string |
| type_alias.ts:11:7:11:12 | number | number |
| type_alias.ts:12:7:12:13 | boolean | boolean |
@@ -96,7 +96,7 @@
| type_alias.ts:15:7:15:12 | Json[] | Json[] |
| type_alias.ts:17:11:17:14 | Json | Json |
| type_alias.ts:19:6:19:16 | VirtualNode | VirtualNode |
| type_alias.ts:20:5:21:56 | \| strin ... Node[]] | VirtualNode |
| type_alias.ts:20:5:21:56 | \| strin ... Node[]] | string \| [string, { [key: string]: any; }, ...V... |
| type_alias.ts:20:7:20:12 | string | string |
| type_alias.ts:21:7:21:56 | [string ... Node[]] | [string, { [key: string]: any; }, ...VirtualNod... |
| type_alias.ts:21:8:21:13 | string | string |
@@ -123,7 +123,7 @@
| type_definitions.ts:21:6:21:10 | Alias | Alias<T> |
| type_definitions.ts:21:12:21:12 | T | T |
| type_definitions.ts:21:17:21:17 | T | T |
| type_definitions.ts:21:17:21:19 | T[] | Alias<T> |
| type_definitions.ts:21:17:21:19 | T[] | T[] |
| type_definitions.ts:22:26:22:30 | Alias | Alias<T> |
| type_definitions.ts:22:26:22:38 | Alias<number> | Alias<number> |
| type_definitions.ts:22:32:22:37 | number | number |

View File

@@ -1,5 +1,5 @@
{
"compilerOptions": {
"lib": ["es2015.symbol"]
"lib": ["es2015"]
}
}

View File

@@ -172,3 +172,21 @@ class Component4 extends React.Component {
this.setState({ });
}
}
class Component5 extends React.Component {
render() {
return <div>
<div onClick={this.bound_throughSomeBinder}/> // OK
</div>
}
constructor(props) {
super(props);
someBind(this, "bound_throughSomeBinder");
}
bound_throughSomeBinder() {
this.setState({ });
}
}

View File

@@ -0,0 +1,12 @@
import React from "react"
class C extends React.Component {
static getDerivedStateFromError(error) {
return { error }
}
render() {
this.state.error;
}
}

View File

@@ -330,6 +330,9 @@ nodes
| tst.js:307:10:307:10 | e |
| tst.js:308:20:308:20 | e |
| tst.js:308:20:308:20 | e |
| tst.js:313:35:313:42 | location |
| tst.js:313:35:313:42 | location |
| tst.js:313:35:313:42 | location |
| v-html.vue:2:8:2:23 | v-html=tainted |
| v-html.vue:2:8:2:23 | v-html=tainted |
| v-html.vue:6:42:6:58 | document.location |
@@ -631,6 +634,7 @@ edges
| tst.js:305:10:305:17 | location | tst.js:307:10:307:10 | e |
| tst.js:307:10:307:10 | e | tst.js:308:20:308:20 | e |
| tst.js:307:10:307:10 | e | tst.js:308:20:308:20 | e |
| tst.js:313:35:313:42 | location | tst.js:313:35:313:42 | location |
| v-html.vue:6:42:6:58 | document.location | v-html.vue:2:8:2:23 | v-html=tainted |
| v-html.vue:6:42:6:58 | document.location | v-html.vue:2:8:2:23 | v-html=tainted |
| v-html.vue:6:42:6:58 | document.location | v-html.vue:2:8:2:23 | v-html=tainted |
@@ -723,6 +727,7 @@ edges
| tst.js:285:59:285:65 | tainted | tst.js:285:59:285:65 | tainted | tst.js:285:59:285:65 | tainted | Cross-site scripting vulnerability due to $@. | tst.js:285:59:285:65 | tainted | user-provided value |
| tst.js:300:20:300:20 | e | tst.js:298:9:298:16 | location | tst.js:300:20:300:20 | e | Cross-site scripting vulnerability due to $@. | tst.js:298:9:298:16 | location | user-provided value |
| tst.js:308:20:308:20 | e | tst.js:305:10:305:17 | location | tst.js:308:20:308:20 | e | Cross-site scripting vulnerability due to $@. | tst.js:305:10:305:17 | location | user-provided value |
| tst.js:313:35:313:42 | location | tst.js:313:35:313:42 | location | tst.js:313:35:313:42 | location | Cross-site scripting vulnerability due to $@. | tst.js:313:35:313:42 | location | user-provided value |
| v-html.vue:2:8:2:23 | v-html=tainted | v-html.vue:6:42:6:58 | document.location | v-html.vue:2:8:2:23 | v-html=tainted | Cross-site scripting vulnerability due to $@. | v-html.vue:6:42:6:58 | document.location | user-provided value |
| winjs.js:3:43:3:49 | tainted | winjs.js:2:17:2:33 | document.location | winjs.js:3:43:3:49 | tainted | Cross-site scripting vulnerability due to $@. | winjs.js:2:17:2:33 | document.location | user-provided value |
| winjs.js:4:43:4:49 | tainted | winjs.js:2:17:2:33 | document.location | winjs.js:4:43:4:49 | tainted | Cross-site scripting vulnerability due to $@. | winjs.js:2:17:2:33 | document.location | user-provided value |

View File

@@ -307,5 +307,8 @@ function basicExceptions() {
} catch(e) {
$("body").append(e); // NOT OK
}
}
function handlebarsSafeString() {
return new Handlebars.SafeString(location); // NOT OK!
}

View File

@@ -5,3 +5,4 @@
| tst.js:53:10:53:33 | s.repla ... , '\\\\') | This replacement may produce '\\' characters that are double-unescaped $@. | tst.js:53:10:54:33 | s.repla ... , '\\'') | here |
| tst.js:60:7:60:28 | s.repla ... '%25') | This replacement may double-escape '%' characters from $@. | tst.js:59:7:59:28 | s.repla ... '%26') | here |
| tst.js:68:10:70:38 | s.repla ... &amp;") | This replacement may double-escape '&' characters from $@. | tst.js:68:10:69:39 | s.repla ... apos;") | here |
| tst.js:79:10:79:66 | s.repla ... &amp;") | This replacement may double-escape '&' characters from $@. | tst.js:79:10:79:43 | s.repla ... epl[c]) | here |

View File

@@ -69,3 +69,28 @@ function badEncode(s) {
.replace(indirect2, "&apos;")
.replace(indirect3, "&amp;");
}
function badEncodeWithReplacer(s) {
var repl = {
'"': "&quot;",
"'": "&apos;",
"&": "&amp;"
};
return s.replace(/["']/g, (c) => repl[c]).replace(/&/g, "&amp;");
}
// dubious, but out of scope for this query
function badRoundtrip(s) {
return s.replace(/\\\\/g, "\\").replace(/\\/g, "\\\\");
}
function testWithCapturedVar(x) {
var captured = x;
(function() {
captured = captured.replace(/\\/g, "\\\\");
})();
}
function encodeDecodeEncode(s) {
return goodEncode(goodDecode(goodEncode(s)));
}

View File

@@ -28,3 +28,4 @@
| tst.js:148:2:148:10 | x.replace | This replaces only the first occurrence of "\\n". |
| tst.js:149:2:149:24 | x.repla ... replace | This replaces only the first occurrence of "\\n". |
| tst.js:193:9:193:17 | s.replace | This replaces only the first occurrence of /'/. |
| tst.js:202:10:202:18 | p.replace | This replaces only the first occurrence of "/../". |

View File

@@ -197,3 +197,7 @@ app.get('/some/path', function(req, res) {
s.replace('"', '').replace('"', ''); // OK
s.replace("'", "").replace("'", ""); // OK
});
function bad18(p) {
return p.replace("/../", ""); // NOT OK
}

View File

@@ -89,6 +89,8 @@ nodes
| passwords.js:123:31:123:38 | password |
| passwords.js:123:31:123:48 | password.valueOf() |
| passwords.js:127:9:132:5 | config |
| passwords.js:127:9:132:5 | config |
| passwords.js:127:18:132:5 | {\\n ... )\\n } |
| passwords.js:127:18:132:5 | {\\n ... )\\n } |
| passwords.js:127:18:132:5 | {\\n ... )\\n } |
| passwords.js:130:12:130:19 | password |
@@ -97,10 +99,35 @@ nodes
| passwords.js:131:12:131:24 | getPassword() |
| passwords.js:135:17:135:22 | config |
| passwords.js:135:17:135:22 | config |
| passwords.js:135:17:135:22 | config |
| passwords.js:136:17:136:24 | config.x |
| passwords.js:136:17:136:24 | config.x |
| passwords.js:137:17:137:24 | config.y |
| passwords.js:137:17:137:24 | config.y |
| passwords.js:142:26:142:34 | arguments |
| passwords.js:142:26:142:34 | arguments |
| passwords.js:147:12:147:19 | password |
| passwords.js:147:12:147:19 | password |
| passwords.js:149:21:149:28 | config.x |
| passwords.js:150:21:150:31 | process.env |
| passwords.js:150:21:150:31 | process.env |
| passwords.js:152:9:152:63 | procdesc |
| passwords.js:152:20:152:44 | Util.in ... ss.env) |
| passwords.js:152:20:152:63 | Util.in ... /g, '') |
| passwords.js:152:33:152:43 | process.env |
| passwords.js:152:33:152:43 | process.env |
| passwords.js:154:21:154:28 | procdesc |
| passwords.js:156:17:156:27 | process.env |
| passwords.js:156:17:156:27 | process.env |
| passwords.js:156:17:156:27 | process.env |
| passwords.js:163:14:163:21 | password |
| passwords.js:163:14:163:21 | password |
| passwords.js:163:14:163:41 | passwor ... g, "*") |
| passwords.js:163:14:163:41 | passwor ... g, "*") |
| passwords.js:164:14:164:21 | password |
| passwords.js:164:14:164:21 | password |
| passwords.js:164:14:164:42 | passwor ... g, "*") |
| passwords.js:164:14:164:42 | passwor ... g, "*") |
| passwords_in_browser1.js:2:13:2:20 | password |
| passwords_in_browser1.js:2:13:2:20 | password |
| passwords_in_browser1.js:2:13:2:20 | password |
@@ -199,6 +226,9 @@ edges
| passwords.js:123:31:123:48 | password.valueOf() | passwords.js:123:17:123:48 | name + ... lueOf() |
| passwords.js:127:9:132:5 | config | passwords.js:135:17:135:22 | config |
| passwords.js:127:9:132:5 | config | passwords.js:135:17:135:22 | config |
| passwords.js:127:9:132:5 | config | passwords.js:135:17:135:22 | config |
| passwords.js:127:9:132:5 | config | passwords.js:135:17:135:22 | config |
| passwords.js:127:18:132:5 | {\\n ... )\\n } | passwords.js:127:9:132:5 | config |
| passwords.js:127:18:132:5 | {\\n ... )\\n } | passwords.js:127:9:132:5 | config |
| passwords.js:127:18:132:5 | {\\n ... )\\n } | passwords.js:127:9:132:5 | config |
| passwords.js:130:12:130:19 | password | passwords.js:127:18:132:5 | {\\n ... )\\n } |
@@ -213,6 +243,30 @@ edges
| passwords.js:131:12:131:24 | getPassword() | passwords.js:137:17:137:24 | config.y |
| passwords.js:131:12:131:24 | getPassword() | passwords.js:137:17:137:24 | config.y |
| passwords.js:131:12:131:24 | getPassword() | passwords.js:137:17:137:24 | config.y |
| passwords.js:147:12:147:19 | password | passwords.js:149:21:149:28 | config.x |
| passwords.js:147:12:147:19 | password | passwords.js:149:21:149:28 | config.x |
| passwords.js:149:21:149:28 | config.x | passwords.js:142:26:142:34 | arguments |
| passwords.js:149:21:149:28 | config.x | passwords.js:142:26:142:34 | arguments |
| passwords.js:150:21:150:31 | process.env | passwords.js:142:26:142:34 | arguments |
| passwords.js:150:21:150:31 | process.env | passwords.js:142:26:142:34 | arguments |
| passwords.js:150:21:150:31 | process.env | passwords.js:142:26:142:34 | arguments |
| passwords.js:150:21:150:31 | process.env | passwords.js:142:26:142:34 | arguments |
| passwords.js:152:9:152:63 | procdesc | passwords.js:154:21:154:28 | procdesc |
| passwords.js:152:20:152:44 | Util.in ... ss.env) | passwords.js:152:20:152:63 | Util.in ... /g, '') |
| passwords.js:152:20:152:63 | Util.in ... /g, '') | passwords.js:152:9:152:63 | procdesc |
| passwords.js:152:33:152:43 | process.env | passwords.js:152:20:152:44 | Util.in ... ss.env) |
| passwords.js:152:33:152:43 | process.env | passwords.js:152:20:152:44 | Util.in ... ss.env) |
| passwords.js:154:21:154:28 | procdesc | passwords.js:142:26:142:34 | arguments |
| passwords.js:154:21:154:28 | procdesc | passwords.js:142:26:142:34 | arguments |
| passwords.js:156:17:156:27 | process.env | passwords.js:156:17:156:27 | process.env |
| passwords.js:163:14:163:21 | password | passwords.js:163:14:163:41 | passwor ... g, "*") |
| passwords.js:163:14:163:21 | password | passwords.js:163:14:163:41 | passwor ... g, "*") |
| passwords.js:163:14:163:21 | password | passwords.js:163:14:163:41 | passwor ... g, "*") |
| passwords.js:163:14:163:21 | password | passwords.js:163:14:163:41 | passwor ... g, "*") |
| passwords.js:164:14:164:21 | password | passwords.js:164:14:164:42 | passwor ... g, "*") |
| passwords.js:164:14:164:21 | password | passwords.js:164:14:164:42 | passwor ... g, "*") |
| passwords.js:164:14:164:21 | password | passwords.js:164:14:164:42 | passwor ... g, "*") |
| passwords.js:164:14:164:21 | password | passwords.js:164:14:164:42 | passwor ... g, "*") |
| passwords_in_browser1.js:2:13:2:20 | password | passwords_in_browser1.js:2:13:2:20 | password |
| passwords_in_browser2.js:2:13:2:20 | password | passwords_in_browser2.js:2:13:2:20 | password |
| passwords_in_server_1.js:6:13:6:20 | password | passwords_in_server_1.js:6:13:6:20 | password |
@@ -250,6 +304,12 @@ edges
| passwords.js:135:17:135:22 | config | passwords.js:131:12:131:24 | getPassword() | passwords.js:135:17:135:22 | config | Sensitive data returned by $@ is logged here. | passwords.js:131:12:131:24 | getPassword() | a call to getPassword |
| passwords.js:136:17:136:24 | config.x | passwords.js:130:12:130:19 | password | passwords.js:136:17:136:24 | config.x | Sensitive data returned by $@ is logged here. | passwords.js:130:12:130:19 | password | an access to password |
| passwords.js:137:17:137:24 | config.y | passwords.js:131:12:131:24 | getPassword() | passwords.js:137:17:137:24 | config.y | Sensitive data returned by $@ is logged here. | passwords.js:131:12:131:24 | getPassword() | a call to getPassword |
| passwords.js:142:26:142:34 | arguments | passwords.js:147:12:147:19 | password | passwords.js:142:26:142:34 | arguments | Sensitive data returned by $@ is logged here. | passwords.js:147:12:147:19 | password | an access to password |
| passwords.js:142:26:142:34 | arguments | passwords.js:150:21:150:31 | process.env | passwords.js:142:26:142:34 | arguments | Sensitive data returned by $@ is logged here. | passwords.js:150:21:150:31 | process.env | process environment |
| passwords.js:142:26:142:34 | arguments | passwords.js:152:33:152:43 | process.env | passwords.js:142:26:142:34 | arguments | Sensitive data returned by $@ is logged here. | passwords.js:152:33:152:43 | process.env | process environment |
| passwords.js:156:17:156:27 | process.env | passwords.js:156:17:156:27 | process.env | passwords.js:156:17:156:27 | process.env | Sensitive data returned by $@ is logged here. | passwords.js:156:17:156:27 | process.env | process environment |
| passwords.js:163:14:163:41 | passwor ... g, "*") | passwords.js:163:14:163:21 | password | passwords.js:163:14:163:41 | passwor ... g, "*") | Sensitive data returned by $@ is logged here. | passwords.js:163:14:163:21 | password | an access to password |
| passwords.js:164:14:164:42 | passwor ... g, "*") | passwords.js:164:14:164:21 | password | passwords.js:164:14:164:42 | passwor ... g, "*") | Sensitive data returned by $@ is logged here. | passwords.js:164:14:164:21 | password | an access to password |
| passwords_in_server_1.js:6:13:6:20 | password | passwords_in_server_1.js:6:13:6:20 | password | passwords_in_server_1.js:6:13:6:20 | password | Sensitive data returned by $@ is logged here. | passwords_in_server_1.js:6:13:6:20 | password | an access to password |
| passwords_in_server_2.js:3:13:3:20 | password | passwords_in_server_2.js:3:13:3:20 | password | passwords_in_server_2.js:3:13:3:20 | password | Sensitive data returned by $@ is logged here. | passwords_in_server_2.js:3:13:3:20 | password | an access to password |
| passwords_in_server_3.js:2:13:2:20 | password | passwords_in_server_3.js:2:13:2:20 | password | passwords_in_server_3.js:2:13:2:20 | password | Sensitive data returned by $@ is logged here. | passwords_in_server_3.js:2:13:2:20 | password | an access to password |

View File

@@ -137,3 +137,29 @@
console.log(config.y); // NOT OK
console.log(config[x]); // OK (probably)
});
function indirectLogCall() {
console.log.apply(this, arguments);
}
var Util = require('util');
(function() {
var config = {
x: password
};
indirectLogCall(config.x); // NOT OK
indirectLogCall(process.env); // NOT OK
var procdesc = Util.inspect(process.env).replace(/\n/g, '')
indirectLogCall(procdesc); // NOT OK
console.log(process.env); // NOT OK
console.log(process.env.PATH); // OK.
console.log(process.env["foo" + "bar"]); // OK.
});
(function () {
console.log(password.replace(/./g, "*")); // OK!
console.log(password.replace(/\./g, "*")); // NOT OK!
console.log(password.replace(/foo/g, "*")); // NOT OK!
})();

View File

@@ -6,3 +6,4 @@
| tst.js:53:10:53:34 | bothOnl ... fects() | the $@ does not return anything, yet the return value is used. | tst.js:48:2:50:5 | functio ... )\\n } | function onlySideEffects2 |
| tst.js:76:12:76:46 | [1,2,3] ... n, 3)}) | the $@ does not return anything, yet the return value from the call to filter is used. | tst.js:76:27:76:45 | n => {equals(n, 3)} | callback function |
| tst.js:80:12:80:50 | filter( ... 3) } ) | the $@ does not return anything, yet the return value from the call to filter is used. | tst.js:80:28:80:48 | x => { ... x, 3) } | callback function |
| tst.ts:6:13:6:25 | returnsVoid() | the $@ does not return anything, yet the return value is used. | tst.ts:1:1:1:38 | declare ... : void; | function returnsVoid |

View File

@@ -12,7 +12,7 @@
}
</script>
</head>
<body>
<body onload="return addHandlers();">
<object id="container" data="editor/svg-editor.html" onload="addHandlers()"></object>
<a href="javascript:addHandlers()">Foo</a>
<div onclick="addHandlers()">Click me</div>

View File

@@ -88,4 +88,20 @@
}
new Deferred().resolve(onlySideEffects()); // OK
})();
})();
+function() {
console.log("FOO");
}.call(this);
class Foo {
constructor() {
console.log("FOO");
}
}
class Bar extends Foo {
constructor() {
console.log(super()); // OK.
}
}

View File

@@ -0,0 +1,6 @@
declare function returnsVoid() : void;
declare function returnsSomething(): number;
console.log(returnsSomething());
console.log(returnsVoid()); // NOT OK!

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,2 @@
description: improve TypeScript support for type aliases, rest parameters, and optional parameters
compatibility: backwards