mirror of
https://github.com/github/codeql.git
synced 2025-12-20 18:56:32 +01:00
Merge remote-tracking branch 'upstream/master' into exceptionXss
This commit is contained in:
@@ -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",
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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.");
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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))
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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.
|
||||
*
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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") }
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -95,6 +95,8 @@ module DomBasedXss {
|
||||
mcn.getMethodName() = m and
|
||||
this = mcn.getArgument(1)
|
||||
)
|
||||
or
|
||||
this = any(Handlebars::SafeString s).getAnArgument()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 |
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -0,0 +1,5 @@
|
||||
import javascript
|
||||
|
||||
query Type exprType(Expr e) { result = e.getType() }
|
||||
|
||||
query Type unaliasedType(TypeAliasReference ref) { result = ref.getAliasedType() }
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"include": ["."],
|
||||
"compilerOptions": {
|
||||
"strict": true
|
||||
}
|
||||
}
|
||||
@@ -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>;
|
||||
@@ -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 |
|
||||
@@ -0,0 +1,3 @@
|
||||
import javascript
|
||||
|
||||
query Parameter optionalParams() { result.isDeclaredOptional() }
|
||||
@@ -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]?);
|
||||
@@ -0,0 +1 @@
|
||||
function withDefault(x? = 5) {} // not valid syntax
|
||||
@@ -0,0 +1,2 @@
|
||||
import f = require("./tst");
|
||||
f("world");
|
||||
@@ -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" |
|
||||
@@ -0,0 +1,4 @@
|
||||
import javascript
|
||||
|
||||
from Expr e
|
||||
select e, e.getType()
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"allowJs": true
|
||||
},
|
||||
"include": ["."]
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
/**
|
||||
* @param {String} x
|
||||
*/
|
||||
module.exports = function(x) {
|
||||
return 'Hello ' + x;
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
| tst.ts:0:0:0:0 | tst.ts |
|
||||
@@ -0,0 +1,4 @@
|
||||
import javascript
|
||||
|
||||
from File file
|
||||
select file
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"include": ["."],
|
||||
"compilerOptions": {
|
||||
"typeRoots": ["does-not-exist"]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
let x = 5;
|
||||
@@ -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>>> |
|
||||
|
||||
@@ -1 +1,3 @@
|
||||
{}
|
||||
{
|
||||
"include": ["."]
|
||||
}
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
| tst.ts:0:0:0:0 | tst.ts |
|
||||
| typeroot.d.ts:0:0:0:0 | typeroot.d.ts |
|
||||
@@ -0,0 +1,4 @@
|
||||
import javascript
|
||||
|
||||
from File file
|
||||
select file
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"include": ["."],
|
||||
"compilerOptions": {
|
||||
"typeRoots": ["typeroot.d.ts"]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
let x = 5;
|
||||
0
javascript/ql/test/library-tests/TypeScript/RegressionTests/TypeRootFile/typeroot.d.ts
vendored
Normal file
0
javascript/ql/test/library-tests/TypeScript/RegressionTests/TypeRootFile/typeroot.d.ts
vendored
Normal 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 |
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"include": ["."]
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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 |
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"lib": ["es2015.symbol"]
|
||||
"lib": ["es2015"]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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({ });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
import React from "react"
|
||||
|
||||
class C extends React.Component {
|
||||
static getDerivedStateFromError(error) {
|
||||
return { error }
|
||||
}
|
||||
|
||||
render() {
|
||||
this.state.error;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 |
|
||||
|
||||
@@ -307,5 +307,8 @@ function basicExceptions() {
|
||||
} catch(e) {
|
||||
$("body").append(e); // NOT OK
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function handlebarsSafeString() {
|
||||
return new Handlebars.SafeString(location); // NOT OK!
|
||||
}
|
||||
|
||||
@@ -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 ... &") | 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 ... &") | This replacement may double-escape '&' characters from $@. | tst.js:79:10:79:43 | s.repla ... epl[c]) | here |
|
||||
|
||||
@@ -69,3 +69,28 @@ function badEncode(s) {
|
||||
.replace(indirect2, "'")
|
||||
.replace(indirect3, "&");
|
||||
}
|
||||
|
||||
function badEncodeWithReplacer(s) {
|
||||
var repl = {
|
||||
'"': """,
|
||||
"'": "'",
|
||||
"&": "&"
|
||||
};
|
||||
return s.replace(/["']/g, (c) => repl[c]).replace(/&/g, "&");
|
||||
}
|
||||
|
||||
// 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)));
|
||||
}
|
||||
|
||||
@@ -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 "/../". |
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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 |
|
||||
|
||||
@@ -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!
|
||||
})();
|
||||
@@ -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 |
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
{}
|
||||
@@ -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>
|
||||
|
||||
@@ -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.
|
||||
}
|
||||
}
|
||||
@@ -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
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,2 @@
|
||||
description: improve TypeScript support for type aliases, rest parameters, and optional parameters
|
||||
compatibility: backwards
|
||||
Reference in New Issue
Block a user