diff --git a/change-notes/1.24/analysis-cpp.md b/change-notes/1.24/analysis-cpp.md index aa4bbdf5887..54954e0f70a 100644 --- a/change-notes/1.24/analysis-cpp.md +++ b/change-notes/1.24/analysis-cpp.md @@ -26,6 +26,10 @@ The following changes in version 1.24 affect C/C++ analysis in all applications. ## Changes to libraries +* The data-flow library has been improved when flow through functions needs to be + combined with both taint tracking and flow through fields allowing more flow + to be tracked. This affects and improves some security queries, which may + report additional results. * Created the `semmle.code.cpp.models.interfaces.Allocation` library to model allocation such as `new` expressions and calls to `malloc`. This in intended to replace the functionality in `semmle.code.cpp.commons.Alloc` with a more consistent and useful interface. * Created the `semmle.code.cpp.models.interfaces.Deallocation` library to model deallocation such as `delete` expressions and calls to `free`. This in intended to replace the functionality in `semmle.code.cpp.commons.Alloc` with a more consistent and useful interface. * The new class `StackVariable` should be used in place of `LocalScopeVariable` diff --git a/change-notes/1.24/analysis-csharp.md b/change-notes/1.24/analysis-csharp.md index 88f4abe91cc..bf2ae180eb1 100644 --- a/change-notes/1.24/analysis-csharp.md +++ b/change-notes/1.24/analysis-csharp.md @@ -18,6 +18,7 @@ The following changes in version 1.24 affect C# analysis in all applications. | **Query** | **Expected impact** | **Change** | |------------------------------|------------------------|-----------------------------------| | Useless assignment to local variable (`cs/useless-assignment-to-local`) | Fewer false positive results | Results have been removed when the variable is named `_` in a `foreach` statement. | +| Potentially dangerous use of non-short-circuit logic (`cs/non-short-circuit`) | Fewer false positive results | Results have been removed when the expression contains an `out` parameter. | | Dereferenced variable may be null (`cs/dereferenced-value-may-be-null`) | More results | Results are reported from parameters with a default value of `null`. | ## Removal of old queries @@ -29,6 +30,10 @@ The following changes in version 1.24 affect C# analysis in all applications. ## Changes to libraries +* The data-flow library has been improved when flow through methods needs to be + combined with both taint tracking and flow through fields allowing more flow + to be tracked. This affects and improves most security queries, which may + report additional results. * The taint tracking library now tracks flow through (implicit or explicit) conversion operator calls. * [Code contracts](https://docs.microsoft.com/en-us/dotnet/framework/debug-trace-profile/code-contracts) are now recognized, and are treated like any other assertion methods. * Expression nullability flow state is given by the predicates `Expr.hasNotNullFlowState()` and `Expr.hasMaybeNullFlowState()`. diff --git a/change-notes/1.24/analysis-java.md b/change-notes/1.24/analysis-java.md index cfdd157d13f..1598bf16039 100644 --- a/change-notes/1.24/analysis-java.md +++ b/change-notes/1.24/analysis-java.md @@ -10,10 +10,11 @@ The following changes in version 1.24 affect Java analysis in all applications. | **Query** | **Tags** | **Purpose** | |-----------------------------|-----------|--------------------------------------------------------------------| -| Disabled Spring CSRF protection (`java/spring-disabled-csrf-protection`) | security, external/cwe/cwe-352 | Finds disabled Cross-Site Request Forgery (CSRF) protection in Spring. | +| Disabled Spring CSRF protection (`java/spring-disabled-csrf-protection`) | security, external/cwe/cwe-352 | Finds disabled Cross-Site Request Forgery (CSRF) protection in Spring. Results are shown on LGTM by default. | | Failure to use HTTPS or SFTP URL in Maven artifact upload/download (`java/maven/non-https-url`) | security, external/cwe/cwe-300, external/cwe/cwe-319, external/cwe/cwe-494, external/cwe/cwe-829 | Finds use of insecure protocols during Maven dependency resolution. Results are shown on LGTM by default. | +| LDAP query built from user-controlled sources (`java/ldap-injection`) | security, external/cwe/cwe-090 | Finds LDAP queries vulnerable to injection of unsanitized user-controlled input. Results are shown on LGTM by default. | | Left shift by more than the type width (`java/lshift-larger-than-type-width`) | correctness | Finds left shifts of ints by 32 bits or more and left shifts of longs by 64 bits or more. Results are shown on LGTM by default. | -| Suspicious date format (`java/suspicious-date-format`) | correctness | Finds date format patterns that use placeholders that are likely to be incorrect. | +| Suspicious date format (`java/suspicious-date-format`) | correctness | Finds date format patterns that use placeholders that are likely to be incorrect. Results are shown on LGTM by default. | ## Changes to existing queries @@ -25,6 +26,10 @@ The following changes in version 1.24 affect Java analysis in all applications. ## Changes to libraries +* The data-flow library has been improved when flow through methods needs to be + combined with both taint tracking and flow through fields allowing more flow + to be tracked. This affects and improves most security queries, which may + report additional results. * Identification of test classes has been improved. Previously, one of the match conditions would classify any class with a name containing the string "Test" as a test class, but now this matching has been replaced with one that @@ -32,3 +37,6 @@ The following changes in version 1.24 affect Java analysis in all applications. general file classification mechanism and thus suppression of alerts, and also any security queries using taint tracking, as test classes act as default barriers stopping taint flow. +* Parentheses are now no longer modelled directly in the AST, that is, the + `ParExpr` class is empty. Instead, a parenthesized expression can be + identified with the `Expr.isParenthesized()` member predicate. diff --git a/change-notes/1.24/analysis-javascript.md b/change-notes/1.24/analysis-javascript.md index 1405a26d186..97298e0738f 100644 --- a/change-notes/1.24/analysis-javascript.md +++ b/change-notes/1.24/analysis-javascript.md @@ -7,7 +7,9 @@ * Imports with the `.js` extension can now be resolved to a TypeScript file, when the import refers to a file generated by TypeScript. -- The analysis of sanitizer guards has improved, leading to fewer false-positive results from the security queries. +* Imports that rely on path-mappings from a `tsconfig.json` file can now be resolved. + +* The analysis of sanitizer guards has improved, leading to fewer false-positive results from the security queries. * Support for the following frameworks and libraries has been improved: - [react](https://www.npmjs.com/package/react) @@ -18,6 +20,7 @@ - [Socket.IO](https://socket.io/) - [ws](https://github.com/websockets/ws) - [WebSocket](https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API) + - [Koa](https://www.npmjs.com/package/koa) ## New queries @@ -39,6 +42,7 @@ | Expression has no effect (`js/useless-expression`) | Fewer false positive results | The query now recognizes block-level flow type annotations and ignores the first statement of a try block. | | Use of call stack introspection in strict mode (`js/strict-mode-call-stack-introspection`) | Fewer false positive results | The query no longer flags expression statements. | | Missing CSRF middleware (`js/missing-token-validation`) | Fewer false positive results | The query reports fewer duplicates and only flags handlers that explicitly access cookie data. | +| Uncontrolled data used in path expression (`js/path-injection`) | More results | This query now recognizes additional ways dangerous paths can be constructed. | ## Changes to libraries diff --git a/config/identical-files.json b/config/identical-files.json index e57bf00ca23..c93a2b28ae6 100644 --- a/config/identical-files.json +++ b/config/identical-files.json @@ -190,9 +190,14 @@ "cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/SSAConstructionImports.qll", "cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/SSAConstructionImports.qll" ], - "C++ SSA AliasAnalysis": [ + "SSA AliasAnalysis": [ "cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/AliasAnalysis.qll", - "cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/AliasAnalysis.qll" + "cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/AliasAnalysis.qll", + "csharp/ql/src/semmle/code/csharp/ir/implementation/unaliased_ssa/internal/AliasAnalysis.qll" + ], + "C++ SSA AliasAnalysisImports": [ + "cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/AliasAnalysisImports.qll", + "cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/AliasAnalysisImports.qll" ], "C++ IR ValueNumberingImports": [ "cpp/ql/src/semmle/code/cpp/ir/implementation/raw/gvn/internal/ValueNumberingImports.qll", @@ -203,6 +208,10 @@ "cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/SimpleSSA.qll", "csharp/ql/src/semmle/code/csharp/ir/implementation/unaliased_ssa/internal/SimpleSSA.qll" ], + "IR AliasConfiguration (unaliased_ssa)": [ + "cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/AliasConfiguration.qll", + "csharp/ql/src/semmle/code/csharp/ir/implementation/unaliased_ssa/internal/AliasConfiguration.qll" + ], "IR SSA SSAConstruction": [ "cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/SSAConstruction.qll", "cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/SSAConstruction.qll", diff --git a/cpp/ql/src/semmle/code/cpp/commons/Printf.qll b/cpp/ql/src/semmle/code/cpp/commons/Printf.qll index 8c041351fd0..32cea249214 100644 --- a/cpp/ql/src/semmle/code/cpp/commons/Printf.qll +++ b/cpp/ql/src/semmle/code/cpp/commons/Printf.qll @@ -258,14 +258,7 @@ class FormatLiteral extends Literal { * Gets the position in the string at which the nth conversion specifier * starts. */ - int getConvSpecOffset(int n) { - n = 0 and result = this.getFormat().indexOf("%", 0, 0) - or - n > 0 and - exists(int p | - n = p + 1 and result = this.getFormat().indexOf("%", 0, this.getConvSpecOffset(p) + 2) - ) - } + int getConvSpecOffset(int n) { result = this.getFormat().indexOf("%", n, 0) } /* * Each of these predicates gets a regular expressions to match each individual diff --git a/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowUtil.qll b/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowUtil.qll index 2c3438a1e46..700087871cc 100644 --- a/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowUtil.qll +++ b/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowUtil.qll @@ -6,7 +6,6 @@ private import cpp private import semmle.code.cpp.dataflow.internal.FlowVar private import semmle.code.cpp.models.interfaces.DataFlow private import semmle.code.cpp.controlflow.Guards -private import semmle.code.cpp.valuenumbering.GlobalValueNumbering cached private newtype TNode = @@ -689,9 +688,9 @@ class BarrierGuard extends GuardCondition { /** Gets a node guarded by this guard. */ final ExprNode getAGuardedNode() { - exists(GVN value, boolean branch | - result.getExpr() = value.getAnExpr() and - this.checks(value.getAnExpr(), branch) and + exists(SsaDefinition def, Variable v, boolean branch | + result.getExpr() = def.getAUse(v) and + this.checks(def.getAUse(v), branch) and this.controls(result.getExpr().getBasicBlock(), branch) ) } diff --git a/cpp/ql/src/semmle/code/cpp/ir/dataflow/DefaultTaintTracking.qll b/cpp/ql/src/semmle/code/cpp/ir/dataflow/DefaultTaintTracking.qll index ddd93fa7a86..fea47e1b5bd 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/dataflow/DefaultTaintTracking.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/dataflow/DefaultTaintTracking.qll @@ -21,31 +21,19 @@ private predicate predictableInstruction(Instruction instr) { predictableInstruction(instr.(UnaryInstruction).getUnary()) } +private DataFlow::Node getNodeForSource(Expr source) { + isUserInput(source, _) and + ( + result = DataFlow::exprNode(source) + or + result = DataFlow::definitionByReferenceNode(source) + ) +} + private class DefaultTaintTrackingCfg extends DataFlow::Configuration { DefaultTaintTrackingCfg() { this = "DefaultTaintTrackingCfg" } - override predicate isSource(DataFlow::Node source) { - exists(CallInstruction ci, WriteSideEffectInstruction wsei | - userInputArgument(ci.getConvertedResultExpression(), wsei.getIndex()) and - source.asInstruction() = wsei and - wsei.getPrimaryInstruction() = ci - ) - or - userInputReturned(source.asExpr()) - or - isUserInput(source.asExpr(), _) - or - source.asExpr() instanceof EnvironmentRead - or - source - .asInstruction() - .(LoadInstruction) - .getSourceAddress() - .(VariableAddressInstruction) - .getASTVariable() - .hasName("argv") and - source.asInstruction().getEnclosingFunction().hasGlobalName("main") - } + override predicate isSource(DataFlow::Node source) { source = getNodeForSource(_) } override predicate isSink(DataFlow::Node sink) { any() } @@ -59,7 +47,7 @@ private class DefaultTaintTrackingCfg extends DataFlow::Configuration { private class ToGlobalVarTaintTrackingCfg extends DataFlow::Configuration { ToGlobalVarTaintTrackingCfg() { this = "GlobalVarTaintTrackingCfg" } - override predicate isSource(DataFlow::Node source) { isUserInput(source.asExpr(), _) } + override predicate isSource(DataFlow::Node source) { source = getNodeForSource(_) } override predicate isSink(DataFlow::Node sink) { exists(GlobalOrNamespaceVariable gv | writesVariable(sink.asInstruction(), gv)) @@ -163,6 +151,22 @@ private predicate instructionTaintStep(Instruction i1, Instruction i2) { // from `a`. i2.(PointerAddInstruction).getLeft() = i1 or + // Until we have from through indirections across calls, we'll take flow out + // of the parameter and into its indirection. + exists(IRFunction f, Parameter parameter | + i1 = getInitializeParameter(f, parameter) and + i2 = getInitializeIndirection(f, parameter) + ) + or + // Until we have flow through indirections across calls, we'll take flow out + // of the indirection and into the argument. + // When we get proper flow through indirections across calls, this code can be + // moved to `adjusedSink` or possibly into the `DataFlow::ExprNode` class. + exists(ReadSideEffectInstruction read | + read.getAnOperand().(SideEffectOperand).getAnyDef() = i1 and + read.getArgumentDef() = i2 + ) + or // Flow from argument to return value i2 = any(CallInstruction call | @@ -188,6 +192,18 @@ private predicate instructionTaintStep(Instruction i1, Instruction i2) { ) } +pragma[noinline] +private InitializeIndirectionInstruction getInitializeIndirection(IRFunction f, Parameter p) { + result.getParameter() = p and + result.getEnclosingIRFunction() = f +} + +pragma[noinline] +private InitializeParameterInstruction getInitializeParameter(IRFunction f, Parameter p) { + result.getParameter() = p and + result.getEnclosingIRFunction() = f +} + /** * Get an instruction that goes into argument `argumentIndex` of `call`. This * can be either directly or through one pointer indirection. @@ -285,31 +301,11 @@ private Element adjustedSink(DataFlow::Node sink) { // For compatibility, send flow into a `NotExpr` even if it's part of a // short-circuiting condition and thus might get skipped. result.(NotExpr).getOperand() = sink.asExpr() - or - // For compatibility, send flow from argument read side effects to their - // corresponding argument expression - exists(IndirectReadSideEffectInstruction read | - read.getAnOperand().(SideEffectOperand).getAnyDef() = sink.asInstruction() and - read.getArgumentDef().getUnconvertedResultExpression() = result - ) - or - exists(BufferReadSideEffectInstruction read | - read.getAnOperand().(SideEffectOperand).getAnyDef() = sink.asInstruction() and - read.getArgumentDef().getUnconvertedResultExpression() = result - ) - or - exists(SizedBufferReadSideEffectInstruction read | - read.getAnOperand().(SideEffectOperand).getAnyDef() = sink.asInstruction() and - read.getArgumentDef().getUnconvertedResultExpression() = result - ) } predicate tainted(Expr source, Element tainted) { exists(DefaultTaintTrackingCfg cfg, DataFlow::Node sink | - cfg.hasFlow(DataFlow::exprNode(source), sink) - or - cfg.hasFlow(DataFlow::definitionByReferenceNode(source), sink) - | + cfg.hasFlow(getNodeForSource(source), sink) and tainted = adjustedSink(sink) ) } @@ -322,7 +318,7 @@ predicate taintedIncludingGlobalVars(Expr source, Element tainted, string global ToGlobalVarTaintTrackingCfg toCfg, FromGlobalVarTaintTrackingCfg fromCfg, DataFlow::Node store, GlobalOrNamespaceVariable global, DataFlow::Node load, DataFlow::Node sink | - toCfg.hasFlow(DataFlow::exprNode(source), store) and + toCfg.hasFlow(getNodeForSource(source), store) and store .asInstruction() .(StoreInstruction) diff --git a/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowUtil.qll b/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowUtil.qll index beef2899792..9428935ad7d 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowUtil.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowUtil.qll @@ -6,6 +6,7 @@ private import cpp private import semmle.code.cpp.ir.IR private import semmle.code.cpp.controlflow.IRGuards private import semmle.code.cpp.ir.ValueNumbering +private import semmle.code.cpp.models.interfaces.DataFlow /** * A newtype wrapper to prevent accidental casts between `Node` and @@ -289,6 +290,51 @@ private predicate simpleInstructionLocalFlowStep(Instruction iFrom, Instruction // Flow through the partial operand belongs in the taint-tracking libraries // for now. iTo.getAnOperand().(ChiTotalOperand).getDef() = iFrom + or + // Flow through modeled functions + modelFlow(iFrom, iTo) +} + +private predicate modelFlow(Instruction iFrom, Instruction iTo) { + exists( + CallInstruction call, DataFlowFunction func, FunctionInput modelIn, FunctionOutput modelOut + | + call.getStaticCallTarget() = func and + func.hasDataFlow(modelIn, modelOut) + | + ( + modelOut.isReturnValue() and + iTo = call + or + // TODO: Add write side effects for return values + modelOut.isReturnValueDeref() and + iTo = call + or + exists(WriteSideEffectInstruction outNode | + modelOut.isParameterDeref(outNode.getIndex()) and + iTo = outNode and + outNode.getPrimaryInstruction() = call + ) + // TODO: add write side effects for qualifiers + ) and + ( + exists(int index | + modelIn.isParameter(index) and + iFrom = call.getPositionalArgument(index) + ) + or + exists(int index, ReadSideEffectInstruction read | + modelIn.isParameterDeref(index) and + read.getIndex() = index and + read.getPrimaryInstruction() = call and + iFrom = read.getSideEffectOperand().getAnyDef() + ) + or + modelIn.isQualifierAddress() and + iFrom = call.getThisArgument() + // TODO: add read side effects for qualifiers + ) + ) } /** diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/MemoryAccessKind.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/MemoryAccessKind.qll index f5c6de314ab..eac4d333afc 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/implementation/MemoryAccessKind.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/MemoryAccessKind.qll @@ -1,6 +1,7 @@ private newtype TMemoryAccessKind = TIndirectMemoryAccess() or TBufferMemoryAccess() or + TEntireAllocationMemoryAccess() or TEscapedMemoryAccess() or TNonLocalMemoryAccess() or TPhiMemoryAccess() or @@ -43,6 +44,16 @@ class BufferMemoryAccess extends MemoryAccessKind, TBufferMemoryAccess { final override predicate usesAddressOperand() { any() } } +/** + * The operand or results accesses all memory in the contiguous allocation that contains the address + * specified by the `AddressOperand` on the same instruction. + */ +class EntireAllocationMemoryAccess extends MemoryAccessKind, TEntireAllocationMemoryAccess { + override string toString() { result = "alloc" } + + final override predicate usesAddressOperand() { any() } +} + /** * The operand or result accesses all memory whose address has escaped. */ diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/Opcode.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/Opcode.qll index d39d8c30a1a..4b1124cf27e 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/implementation/Opcode.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/Opcode.qll @@ -232,6 +232,31 @@ abstract class BufferReadOpcode extends BufferAccessOpcode { final override MemoryAccessKind getReadMemoryAccess() { result instanceof BufferMemoryAccess } } +/** + * An opcode that access an entire memory allocation. + */ +abstract class EntireAllocationAccessOpcode extends Opcode { + final override predicate hasAddressOperand() { any() } +} + +/** + * An opcode that write to an entire memory allocation. + */ +abstract class EntireAllocationWriteOpcode extends EntireAllocationAccessOpcode { + final override MemoryAccessKind getWriteMemoryAccess() { + result instanceof EntireAllocationMemoryAccess + } +} + +/** + * An opcode that reads from an entire memory allocation. + */ +abstract class EntireAllocationReadOpcode extends EntireAllocationAccessOpcode { + final override MemoryAccessKind getReadMemoryAccess() { + result instanceof EntireAllocationMemoryAccess + } +} + /** * An opcode that accesses a memory buffer whose size is determined by a `BufferSizeOperand`. */ @@ -325,7 +350,7 @@ module Opcode { final override string toString() { result = "InitializeParameter" } } - class InitializeIndirection extends IndirectWriteOpcode, TInitializeIndirection { + class InitializeIndirection extends EntireAllocationWriteOpcode, TInitializeIndirection { final override string toString() { result = "InitializeIndirection" } } @@ -349,7 +374,7 @@ module Opcode { final override string toString() { result = "ReturnVoid" } } - class ReturnIndirection extends IndirectReadOpcode, TReturnIndirection { + class ReturnIndirection extends EntireAllocationReadOpcode, TReturnIndirection { final override string toString() { result = "ReturnIndirection" } final override predicate hasOperandInternal(OperandTag tag) { diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/AliasAnalysis.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/AliasAnalysis.qll index ff4602347ae..e2d3828fc52 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/AliasAnalysis.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/AliasAnalysis.qll @@ -1,21 +1,9 @@ private import AliasAnalysisInternal -private import cpp private import InputIR -private import semmle.code.cpp.ir.internal.IntegerConstant as Ints -private import semmle.code.cpp.ir.implementation.IRConfiguration -private import semmle.code.cpp.models.interfaces.Alias +private import AliasAnalysisImports private class IntValue = Ints::IntValue; -/** - * Gets the offset of field `field` in bits. - */ -private IntValue getFieldBitOffset(Field field) { - if field instanceof BitField - then result = Ints::add(Ints::mul(field.getByteOffset(), 8), field.(BitField).getBitOffset()) - else result = Ints::mul(field.getByteOffset(), 8) -} - /** * Holds if the operand `tag` of instruction `instr` is used in a way that does * not result in any address held in that operand from escaping beyond the @@ -36,7 +24,7 @@ private predicate operandIsConsumedWithoutEscaping(Operand operand) { instr instanceof PointerDiffInstruction or // Converting an address to a `bool` does not escape the address. - instr.(ConvertInstruction).getResultType() instanceof BoolType + instr.(ConvertInstruction).getResultIRType() instanceof IRBooleanType ) ) or @@ -111,13 +99,10 @@ private predicate operandIsPropagated(Operand operand, IntValue bitOffset) { bitOffset = Ints::unknown() or // Conversion to another pointer type propagates the source address. - exists(ConvertInstruction convert, Type resultType | + exists(ConvertInstruction convert, IRType resultType | convert = instr and - resultType = convert.getResultType() and - ( - resultType instanceof PointerType or - resultType instanceof Class //REVIEW: Remove when all glvalues are pointers - ) and + resultType = convert.getResultIRType() and + resultType instanceof IRAddressType and bitOffset = 0 ) or @@ -131,7 +116,7 @@ private predicate operandIsPropagated(Operand operand, IntValue bitOffset) { or // Computing a field address from a pointer propagates the address plus the // offset of the field. - bitOffset = getFieldBitOffset(instr.(FieldAddressInstruction).getField()) + bitOffset = Language::getFieldBitOffset(instr.(FieldAddressInstruction).getField()) or // A copy propagates the source value. operand = instr.(CopyInstruction).getSourceValueOperand() and bitOffset = 0 @@ -212,7 +197,7 @@ private predicate operandReturned(Operand operand, IntValue bitOffset) { } private predicate isArgumentForParameter(CallInstruction ci, Operand operand, Instruction init) { - exists(Function f | + exists(Language::Function f | ci = operand.getUse() and f = ci.getStaticCallTarget() and ( @@ -223,27 +208,27 @@ private predicate isArgumentForParameter(CallInstruction ci, Operand operand, In init.getEnclosingFunction() = f and operand instanceof ThisArgumentOperand ) and - not f.isVirtual() and - not f instanceof AliasFunction + not Language::isFunctionVirtual(f) and + not f instanceof AliasModels::AliasFunction ) } private predicate isAlwaysReturnedArgument(Operand operand) { - exists(AliasFunction f | + exists(AliasModels::AliasFunction f | f = operand.getUse().(CallInstruction).getStaticCallTarget() and f.parameterIsAlwaysReturned(operand.(PositionalArgumentOperand).getIndex()) ) } private predicate isOnlyEscapesViaReturnArgument(Operand operand) { - exists(AliasFunction f | + exists(AliasModels::AliasFunction f | f = operand.getUse().(CallInstruction).getStaticCallTarget() and f.parameterEscapesOnlyViaReturn(operand.(PositionalArgumentOperand).getIndex()) ) } private predicate isNeverEscapesArgument(Operand operand) { - exists(AliasFunction f | + exists(AliasModels::AliasFunction f | f = operand.getUse().(CallInstruction).getStaticCallTarget() and f.parameterNeverEscapes(operand.(PositionalArgumentOperand).getIndex()) ) @@ -265,61 +250,86 @@ private predicate resultEscapesNonReturn(Instruction instr) { } /** - * Holds if the address of the specified local variable or parameter escapes the - * domain of the analysis. + * Holds if the address of `allocation` escapes outside the domain of the analysis. This can occur + * either because the allocation's address is taken within the function and escapes, or because the + * allocation is marked as always escaping via `alwaysEscapes()`. */ -private predicate automaticVariableAddressEscapes(IRAutomaticVariable var) { - // The variable's address escapes if the result of any - // VariableAddressInstruction that computes the variable's address escapes. - exists(VariableAddressInstruction instr | - instr.getIRVariable() = var and - resultEscapesNonReturn(instr) - ) -} - -/** - * Holds if the address of the specified variable escapes the domain of the - * analysis. - */ -predicate variableAddressEscapes(IRVariable var) { +predicate allocationEscapes(Configuration::Allocation allocation) { + allocation.alwaysEscapes() + or exists(IREscapeAnalysisConfiguration config | - config.useSoundEscapeAnalysis() and - automaticVariableAddressEscapes(var.(IRAutomaticVariable)) + config.useSoundEscapeAnalysis() and resultEscapesNonReturn(allocation.getABaseInstruction()) ) - or - // All variables with static storage duration have their address escape, even when escape analysis - // is allowed to be unsound. Otherwise, we won't have a definition for any non-escaped global - // variable. Normally, we rely on `AliasedDefinition` to handle that. - not var instanceof IRAutomaticVariable } /** - * Holds if the result of instruction `instr` points within variable `var`, at - * bit offset `bitOffset` within the variable. If the result points within - * `var`, but at an unknown or non-constant offset, then `bitOffset` is unknown. + * Equivalent to `operandIsPropagated()`, but includes interprocedural propagation. */ -predicate resultPointsTo(Instruction instr, IRVariable var, IntValue bitOffset) { - // The address of a variable points to that variable, at offset 0. - instr.(VariableAddressInstruction).getIRVariable() = var and - bitOffset = 0 +private predicate operandIsPropagatedIncludingByCall(Operand operand, IntValue bitOffset) { + operandIsPropagated(operand, bitOffset) or - // A string literal is just a special read-only global variable. - instr.(StringConstantInstruction).getIRVariable() = var and - bitOffset = 0 - or - exists(Operand operand, IntValue originalBitOffset, IntValue propagatedBitOffset | - operand = instr.getAnOperand() and - // If an operand is propagated, then the result points to the same variable, - // offset by the bit offset from the propagation. - resultPointsTo(operand.getAnyDef(), var, originalBitOffset) and - ( - operandIsPropagated(operand, propagatedBitOffset) - or - exists(CallInstruction ci, Instruction init | - isArgumentForParameter(ci, operand, init) and - resultReturned(init, propagatedBitOffset) - ) - ) and - bitOffset = Ints::add(originalBitOffset, propagatedBitOffset) + exists(CallInstruction call, Instruction init | + isArgumentForParameter(call, operand, init) and + resultReturned(init, bitOffset) + ) +} + +/** + * Holds if `addrOperand` is at offset `bitOffset` from the value of instruction `base`. The offset + * may be `unknown()`. + */ +private predicate hasBaseAndOffset(AddressOperand addrOperand, Instruction base, IntValue bitOffset) { + base = addrOperand.getDef() and bitOffset = 0 // Base case + or + exists( + Instruction middle, int previousBitOffset, Operand middleOperand, IntValue additionalBitOffset + | + // We already have an offset from `middle`. + hasBaseAndOffset(addrOperand, middle, previousBitOffset) and + // `middle` is propagated from `base`. + middleOperand = middle.getAnOperand() and + operandIsPropagatedIncludingByCall(middleOperand, additionalBitOffset) and + base = middleOperand.getDef() and + bitOffset = Ints::add(previousBitOffset, additionalBitOffset) + ) +} + +/** + * Holds if `addrOperand` is at constant offset `bitOffset` from the value of instruction `base`. + * Only holds for the `base` with the longest chain of propagation to `addrOperand`. + */ +predicate addressOperandBaseAndConstantOffset( + AddressOperand addrOperand, Instruction base, int bitOffset +) { + hasBaseAndOffset(addrOperand, base, bitOffset) and + Ints::hasValue(bitOffset) and + not exists(Instruction previousBase, int previousBitOffset | + hasBaseAndOffset(addrOperand, previousBase, previousBitOffset) and + previousBase = base.getAnOperand().getDef() and + Ints::hasValue(previousBitOffset) + ) +} + +/** + * Gets the allocation into which `addrOperand` points, if known. + */ +Configuration::Allocation getAddressOperandAllocation(AddressOperand addrOperand) { + addressOperandAllocationAndOffset(addrOperand, result, _) +} + +/** + * Holds if `addrOperand` is at offset `bitOffset` from a base instruction of `allocation`. The + * offset may be `unknown()`. + */ +predicate addressOperandAllocationAndOffset( + AddressOperand addrOperand, Configuration::Allocation allocation, IntValue bitOffset +) { + exists(Instruction base | + allocation.getABaseInstruction() = base and + hasBaseAndOffset(addrOperand, base, bitOffset) and + not exists(Instruction previousBase | + hasBaseAndOffset(addrOperand, previousBase, _) and + previousBase = base.getAnOperand().getDef() + ) ) } diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/AliasAnalysisImports.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/AliasAnalysisImports.qll new file mode 100644 index 00000000000..c4aeaf93cce --- /dev/null +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/AliasAnalysisImports.qll @@ -0,0 +1,3 @@ +import semmle.code.cpp.ir.implementation.IRConfiguration +import semmle.code.cpp.ir.internal.IntegerConstant as Ints +import semmle.code.cpp.models.interfaces.Alias as AliasModels diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/AliasAnalysisInternal.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/AliasAnalysisInternal.qll index 003b7008619..8a407105080 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/AliasAnalysisInternal.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/AliasAnalysisInternal.qll @@ -1 +1,3 @@ +import semmle.code.cpp.ir.internal.IRCppLanguage as Language import semmle.code.cpp.ir.implementation.unaliased_ssa.IR as InputIR +import AliasConfiguration as Configuration diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/AliasConfiguration.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/AliasConfiguration.qll new file mode 100644 index 00000000000..d9937294d70 --- /dev/null +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/AliasConfiguration.qll @@ -0,0 +1,97 @@ +private import AliasConfigurationInternal +private import semmle.code.cpp.ir.implementation.unaliased_ssa.IR +private import cpp +private import AliasAnalysis + +private newtype TAllocation = + TVariableAllocation(IRVariable var) or + TIndirectParameterAllocation(IRAutomaticUserVariable var) { + exists(InitializeIndirectionInstruction instr | instr.getIRVariable() = var) + } + +/** + * A memory allocation that can be tracked by the AliasedSSA alias analysis. + */ +abstract class Allocation extends TAllocation { + abstract string toString(); + + final string getAllocationString() { result = toString() } + + abstract Instruction getABaseInstruction(); + + abstract IRFunction getEnclosingIRFunction(); + + abstract Language::Location getLocation(); + + abstract string getUniqueId(); + + abstract IRType getIRType(); + + abstract predicate isReadOnly(); + + abstract predicate alwaysEscapes(); + + abstract predicate isAlwaysAllocatedOnStack(); + + final predicate isUnaliased() { not allocationEscapes(this) } +} + +class VariableAllocation extends Allocation, TVariableAllocation { + IRVariable var; + + VariableAllocation() { this = TVariableAllocation(var) } + + final override string toString() { result = var.toString() } + + final override VariableInstruction getABaseInstruction() { + result.getIRVariable() = var and + (result instanceof VariableAddressInstruction or result instanceof StringConstantInstruction) + } + + final override IRFunction getEnclosingIRFunction() { result = var.getEnclosingIRFunction() } + + final override Language::Location getLocation() { result = var.getLocation() } + + final override string getUniqueId() { result = var.getUniqueId() } + + final override IRType getIRType() { result = var.getIRType() } + + final override predicate isReadOnly() { var.isReadOnly() } + + final override predicate isAlwaysAllocatedOnStack() { var instanceof IRAutomaticVariable } + + final override predicate alwaysEscapes() { + // All variables with static storage duration have their address escape, even when escape analysis + // is allowed to be unsound. Otherwise, we won't have a definition for any non-escaped global + // variable. Normally, we rely on `AliasedDefinition` to handle that. + not var instanceof IRAutomaticVariable + } + + final IRVariable getIRVariable() { result = var } +} + +class IndirectParameterAllocation extends Allocation, TIndirectParameterAllocation { + IRAutomaticUserVariable var; + + IndirectParameterAllocation() { this = TIndirectParameterAllocation(var) } + + final override string toString() { result = "*" + var.toString() } + + final override InitializeParameterInstruction getABaseInstruction() { + result.getIRVariable() = var + } + + final override IRFunction getEnclosingIRFunction() { result = var.getEnclosingIRFunction() } + + final override Language::Location getLocation() { result = var.getLocation() } + + final override string getUniqueId() { result = var.getUniqueId() } + + final override IRType getIRType() { result = var.getIRType() } + + final override predicate isReadOnly() { none() } + + final override predicate isAlwaysAllocatedOnStack() { none() } + + final override predicate alwaysEscapes() { none() } +} diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/AliasConfigurationInternal.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/AliasConfigurationInternal.qll new file mode 100644 index 00000000000..bd6c2f4c151 --- /dev/null +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/AliasConfigurationInternal.qll @@ -0,0 +1 @@ +import semmle.code.cpp.ir.internal.IRCppLanguage as Language diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/AliasedSSA.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/AliasedSSA.qll index 73bf062c88d..f372988ea3d 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/AliasedSSA.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/AliasedSSA.qll @@ -6,38 +6,52 @@ private import semmle.code.cpp.ir.implementation.unaliased_ssa.IR private import semmle.code.cpp.ir.internal.IntegerConstant as Ints private import semmle.code.cpp.ir.internal.IntegerInterval as Interval private import semmle.code.cpp.ir.implementation.internal.OperandTag +private import AliasConfiguration private class IntValue = Ints::IntValue; +private predicate isIndirectOrBufferMemoryAccess(MemoryAccessKind kind) { + kind instanceof IndirectMemoryAccess or + kind instanceof BufferMemoryAccess +} + private predicate hasResultMemoryAccess( - Instruction instr, IRVariable var, IRType type, Language::LanguageType languageType, + Instruction instr, Allocation var, IRType type, Language::LanguageType languageType, IntValue startBitOffset, IntValue endBitOffset, boolean isMayAccess ) { - resultPointsTo(instr.getResultAddress(), var, startBitOffset) and - languageType = instr.getResultLanguageType() and - type = languageType.getIRType() and - (if instr.hasResultMayMemoryAccess() then isMayAccess = true else isMayAccess = false) and - if exists(type.getByteSize()) - then endBitOffset = Ints::add(startBitOffset, Ints::mul(type.getByteSize(), 8)) - else endBitOffset = Ints::unknown() + exists(AddressOperand addrOperand | + addrOperand = instr.getResultAddressOperand() and + addressOperandAllocationAndOffset(addrOperand, var, startBitOffset) and + languageType = instr.getResultLanguageType() and + type = languageType.getIRType() and + isIndirectOrBufferMemoryAccess(instr.getResultMemoryAccess()) and + (if instr.hasResultMayMemoryAccess() then isMayAccess = true else isMayAccess = false) and + if type.getByteSize() > 0 + then endBitOffset = Ints::add(startBitOffset, Ints::mul(type.getByteSize(), 8)) + else endBitOffset = Ints::unknown() + ) } private predicate hasOperandMemoryAccess( - MemoryOperand operand, IRVariable var, IRType type, Language::LanguageType languageType, + MemoryOperand operand, Allocation var, IRType type, Language::LanguageType languageType, IntValue startBitOffset, IntValue endBitOffset, boolean isMayAccess ) { - resultPointsTo(operand.getAddressOperand().getAnyDef(), var, startBitOffset) and - languageType = operand.getLanguageType() and - type = languageType.getIRType() and - (if operand.hasMayReadMemoryAccess() then isMayAccess = true else isMayAccess = false) and - if exists(type.getByteSize()) - then endBitOffset = Ints::add(startBitOffset, Ints::mul(type.getByteSize(), 8)) - else endBitOffset = Ints::unknown() + exists(AddressOperand addrOperand | + addrOperand = operand.getAddressOperand() and + addressOperandAllocationAndOffset(addrOperand, var, startBitOffset) and + languageType = operand.getLanguageType() and + type = languageType.getIRType() and + isIndirectOrBufferMemoryAccess(operand.getMemoryAccess()) and + (if operand.hasMayReadMemoryAccess() then isMayAccess = true else isMayAccess = false) and + if type.getByteSize() > 0 + then endBitOffset = Ints::add(startBitOffset, Ints::mul(type.getByteSize(), 8)) + else endBitOffset = Ints::unknown() + ) } private newtype TMemoryLocation = TVariableMemoryLocation( - IRVariable var, IRType type, Language::LanguageType languageType, IntValue startBitOffset, + Allocation var, IRType type, Language::LanguageType languageType, IntValue startBitOffset, IntValue endBitOffset, boolean isMayAccess ) { ( @@ -45,17 +59,18 @@ private newtype TMemoryLocation = or hasOperandMemoryAccess(_, var, type, _, startBitOffset, endBitOffset, isMayAccess) or - exists(IRAutomaticVariable autoVar | - // Always create a memory location for the entire variable. - autoVar = var and - type = autoVar.getIRType() and - startBitOffset = 0 and - endBitOffset = type.getByteSize() * 8 and - isMayAccess = false - ) + // For a stack variable, always create a memory location for the entire variable. + var.isAlwaysAllocatedOnStack() and + type = var.getIRType() and + startBitOffset = 0 and + endBitOffset = type.getByteSize() * 8 and + isMayAccess = false ) and languageType = type.getCanonicalLanguageType() } or + TEntireAllocationMemoryLocation(IndirectParameterAllocation var, boolean isMayAccess) { + isMayAccess = false or isMayAccess = true + } or TUnknownMemoryLocation(IRFunction irFunc, boolean isMayAccess) { isMayAccess = false or isMayAccess = true } or @@ -94,6 +109,8 @@ abstract class MemoryLocation extends TMemoryLocation { abstract predicate isMayAccess(); + Allocation getAllocation() { none() } + /** * Holds if the location cannot be overwritten except by definition of a `MemoryLocation` for * which `def.canDefineReadOnly()` holds. @@ -114,27 +131,63 @@ abstract class MemoryLocation extends TMemoryLocation { abstract class VirtualVariable extends MemoryLocation { } +abstract class AllocationMemoryLocation extends MemoryLocation { + Allocation var; + boolean isMayAccess; + + AllocationMemoryLocation() { + this instanceof TMemoryLocation and + isMayAccess = false + or + isMayAccess = true // Just ensures that `isMayAccess` is bound. + } + + final override VirtualVariable getVirtualVariable() { + if allocationEscapes(var) + then result = TAllAliasedMemory(var.getEnclosingIRFunction(), false) + else result.(AllocationMemoryLocation).getAllocation() = var + } + + final override IRFunction getIRFunction() { result = var.getEnclosingIRFunction() } + + final override Location getLocation() { result = var.getLocation() } + + final override Allocation getAllocation() { result = var } + + final override predicate isMayAccess() { isMayAccess = true } + + final override predicate isReadOnly() { var.isReadOnly() } +} + /** * An access to memory within a single known `IRVariable`. The variable may be either an unescaped variable * (with its own `VirtualIRVariable`) or an escaped variable (assigned to `UnknownVirtualVariable`). */ -class VariableMemoryLocation extends TVariableMemoryLocation, MemoryLocation { - IRVariable var; +class VariableMemoryLocation extends TVariableMemoryLocation, AllocationMemoryLocation { IRType type; Language::LanguageType languageType; IntValue startBitOffset; IntValue endBitOffset; - boolean isMayAccess; VariableMemoryLocation() { this = TVariableMemoryLocation(var, type, languageType, startBitOffset, endBitOffset, isMayAccess) } + private string getIntervalString() { + if coversEntireVariable() + then result = "" + else result = Interval::getIntervalString(startBitOffset, endBitOffset) + } + + private string getTypeString() { + if coversEntireVariable() and type = var.getIRType() + then result = "" + else result = "<" + languageType.toString() + ">" + } + final override string toStringInternal() { - result = - var.toString() + Interval::getIntervalString(startBitOffset, endBitOffset) + "<" + - type.toString() + ", " + languageType.toString() + ">" + result = var.toString() + getIntervalString() + getTypeString() } final override Language::LanguageType getType() { @@ -152,35 +205,17 @@ class VariableMemoryLocation extends TVariableMemoryLocation, MemoryLocation { result = type.getCanonicalLanguageType() } - final override IRFunction getIRFunction() { result = var.getEnclosingIRFunction() } - - final override Location getLocation() { result = var.getLocation() } - final IntValue getStartBitOffset() { result = startBitOffset } final IntValue getEndBitOffset() { result = endBitOffset } - final IRVariable getVariable() { result = var } - final override string getUniqueId() { result = var.getUniqueId() + Interval::getIntervalString(startBitOffset, endBitOffset) + "<" + type.getIdentityString() + ">" } - final override VirtualVariable getVirtualVariable() { - if variableAddressEscapes(var) - then result = TAllAliasedMemory(var.getEnclosingIRFunction(), false) - else - result = - TVariableMemoryLocation(var, var.getIRType(), _, 0, var.getIRType().getByteSize() * 8, false) - } - - final override predicate isMayAccess() { isMayAccess = true } - - final override predicate isReadOnly() { var.isReadOnly() } - - final override predicate isAlwaysAllocatedOnStack() { var instanceof IRAutomaticVariable } + final override predicate isAlwaysAllocatedOnStack() { var.isAlwaysAllocatedOnStack() } /** * Holds if this memory location covers the entire variable. @@ -191,6 +226,26 @@ class VariableMemoryLocation extends TVariableMemoryLocation, MemoryLocation { } } +class EntireAllocationMemoryLocation extends TEntireAllocationMemoryLocation, + AllocationMemoryLocation { + EntireAllocationMemoryLocation() { this = TEntireAllocationMemoryLocation(var, isMayAccess) } + + final override string toStringInternal() { result = var.toString() } + + final override Language::LanguageType getType() { + result = any(IRUnknownType unknownType).getCanonicalLanguageType() + } + + final override string getUniqueId() { result = var.getUniqueId() } +} + +class EntireAllocationVirtualVariable extends EntireAllocationMemoryLocation, VirtualVariable { + EntireAllocationVirtualVariable() { + not allocationEscapes(var) and + not isMayAccess() + } +} + /** * Represents the `MemoryLocation` for an `IRVariable` that acts as its own `VirtualVariable`. Includes any * `VariableMemoryLocation` that exactly overlaps its entire `IRVariable`, and only if that `IRVariable` does not @@ -198,7 +253,7 @@ class VariableMemoryLocation extends TVariableMemoryLocation, MemoryLocation { */ class VariableVirtualVariable extends VariableMemoryLocation, VirtualVariable { VariableVirtualVariable() { - not variableAddressEscapes(var) and + not allocationEscapes(var) and type = var.getIRType() and coversEntireVariable() and not isMayAccess() @@ -356,13 +411,30 @@ private Overlap getExtentOverlap(MemoryLocation def, MemoryLocation use) { not use.isAlwaysAllocatedOnStack() ) or + def.getVirtualVariable() = use.getVirtualVariable() and + def instanceof EntireAllocationMemoryLocation and + ( + // EntireAllocationMemoryLocation exactly overlaps itself. + use instanceof EntireAllocationMemoryLocation and + result instanceof MustExactlyOverlap + or + // EntireAllocationMemoryLocation totally overlaps any location within the same virtual + // variable. + not use instanceof EntireAllocationMemoryLocation and + result instanceof MustTotallyOverlap + ) + or exists(VariableMemoryLocation defVariableLocation | defVariableLocation = def and ( // A VariableMemoryLocation may partially overlap an unknown location within the same // virtual variable. def.getVirtualVariable() = use.getVirtualVariable() and - (use instanceof UnknownMemoryLocation or use instanceof AllAliasedMemory) and + ( + use instanceof UnknownMemoryLocation or + use instanceof AllAliasedMemory or + use instanceof EntireAllocationMemoryLocation + ) and result instanceof MayPartiallyOverlap or // A VariableMemoryLocation that is not a local variable may partially overlap an @@ -423,19 +495,19 @@ private predicate isRelatableMemoryLocation(VariableMemoryLocation vml) { vml.getStartBitOffset() != Ints::unknown() } -private predicate isCoveredOffset(IRVariable var, int offsetRank, VariableMemoryLocation vml) { +private predicate isCoveredOffset(Allocation var, int offsetRank, VariableMemoryLocation vml) { exists(int startRank, int endRank, VirtualVariable vvar | vml.getStartBitOffset() = rank[startRank](IntValue offset_ | isRelevantOffset(vvar, offset_)) and vml.getEndBitOffset() = rank[endRank](IntValue offset_ | isRelevantOffset(vvar, offset_)) and - var = vml.getVariable() and + var = vml.getAllocation() and vvar = vml.getVirtualVariable() and isRelatableMemoryLocation(vml) and offsetRank in [startRank .. endRank] ) } -private predicate hasUnknownOffset(IRVariable var, VariableMemoryLocation vml) { - vml.getVariable() = var and +private predicate hasUnknownOffset(Allocation var, VariableMemoryLocation vml) { + vml.getAllocation() = var and ( vml.getStartBitOffset() = Ints::unknown() or vml.getEndBitOffset() = Ints::unknown() @@ -445,14 +517,14 @@ private predicate hasUnknownOffset(IRVariable var, VariableMemoryLocation vml) { private predicate overlappingIRVariableMemoryLocations( VariableMemoryLocation def, VariableMemoryLocation use ) { - exists(IRVariable var, int offsetRank | + exists(Allocation var, int offsetRank | isCoveredOffset(var, offsetRank, def) and isCoveredOffset(var, offsetRank, use) ) or - hasUnknownOffset(use.getVariable(), def) + hasUnknownOffset(use.getAllocation(), def) or - hasUnknownOffset(def.getVariable(), use) + hasUnknownOffset(def.getAllocation(), use) } private Overlap getVariableMemoryLocationOverlap( @@ -470,10 +542,10 @@ MemoryLocation getResultMemoryLocation(Instruction instr) { (if instr.hasResultMayMemoryAccess() then isMayAccess = true else isMayAccess = false) and ( ( - kind.usesAddressOperand() and + isIndirectOrBufferMemoryAccess(kind) and if hasResultMemoryAccess(instr, _, _, _, _, _, _) then - exists(IRVariable var, IRType type, IntValue startBitOffset, IntValue endBitOffset | + exists(Allocation var, IRType type, IntValue startBitOffset, IntValue endBitOffset | hasResultMemoryAccess(instr, var, type, _, startBitOffset, endBitOffset, isMayAccess) and result = TVariableMemoryLocation(var, type, _, startBitOffset, endBitOffset, isMayAccess) @@ -481,6 +553,11 @@ MemoryLocation getResultMemoryLocation(Instruction instr) { else result = TUnknownMemoryLocation(instr.getEnclosingIRFunction(), isMayAccess) ) or + kind instanceof EntireAllocationMemoryAccess and + result = + TEntireAllocationMemoryLocation(getAddressOperandAllocation(instr.getResultAddressOperand()), + isMayAccess) + or kind instanceof EscapedMemoryAccess and result = TAllAliasedMemory(instr.getEnclosingIRFunction(), isMayAccess) or @@ -496,10 +573,10 @@ MemoryLocation getOperandMemoryLocation(MemoryOperand operand) { (if operand.hasMayReadMemoryAccess() then isMayAccess = true else isMayAccess = false) and ( ( - kind.usesAddressOperand() and + isIndirectOrBufferMemoryAccess(kind) and if hasOperandMemoryAccess(operand, _, _, _, _, _, _) then - exists(IRVariable var, IRType type, IntValue startBitOffset, IntValue endBitOffset | + exists(Allocation var, IRType type, IntValue startBitOffset, IntValue endBitOffset | hasOperandMemoryAccess(operand, var, type, _, startBitOffset, endBitOffset, isMayAccess) and result = TVariableMemoryLocation(var, type, _, startBitOffset, endBitOffset, isMayAccess) @@ -507,6 +584,11 @@ MemoryLocation getOperandMemoryLocation(MemoryOperand operand) { else result = TUnknownMemoryLocation(operand.getEnclosingIRFunction(), isMayAccess) ) or + kind instanceof EntireAllocationMemoryAccess and + result = + TEntireAllocationMemoryLocation(getAddressOperandAllocation(operand.getAddressOperand()), + isMayAccess) + or kind instanceof EscapedMemoryAccess and result = TAllAliasedMemory(operand.getEnclosingIRFunction(), isMayAccess) or diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/AliasAnalysis.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/AliasAnalysis.qll index ff4602347ae..e2d3828fc52 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/AliasAnalysis.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/AliasAnalysis.qll @@ -1,21 +1,9 @@ private import AliasAnalysisInternal -private import cpp private import InputIR -private import semmle.code.cpp.ir.internal.IntegerConstant as Ints -private import semmle.code.cpp.ir.implementation.IRConfiguration -private import semmle.code.cpp.models.interfaces.Alias +private import AliasAnalysisImports private class IntValue = Ints::IntValue; -/** - * Gets the offset of field `field` in bits. - */ -private IntValue getFieldBitOffset(Field field) { - if field instanceof BitField - then result = Ints::add(Ints::mul(field.getByteOffset(), 8), field.(BitField).getBitOffset()) - else result = Ints::mul(field.getByteOffset(), 8) -} - /** * Holds if the operand `tag` of instruction `instr` is used in a way that does * not result in any address held in that operand from escaping beyond the @@ -36,7 +24,7 @@ private predicate operandIsConsumedWithoutEscaping(Operand operand) { instr instanceof PointerDiffInstruction or // Converting an address to a `bool` does not escape the address. - instr.(ConvertInstruction).getResultType() instanceof BoolType + instr.(ConvertInstruction).getResultIRType() instanceof IRBooleanType ) ) or @@ -111,13 +99,10 @@ private predicate operandIsPropagated(Operand operand, IntValue bitOffset) { bitOffset = Ints::unknown() or // Conversion to another pointer type propagates the source address. - exists(ConvertInstruction convert, Type resultType | + exists(ConvertInstruction convert, IRType resultType | convert = instr and - resultType = convert.getResultType() and - ( - resultType instanceof PointerType or - resultType instanceof Class //REVIEW: Remove when all glvalues are pointers - ) and + resultType = convert.getResultIRType() and + resultType instanceof IRAddressType and bitOffset = 0 ) or @@ -131,7 +116,7 @@ private predicate operandIsPropagated(Operand operand, IntValue bitOffset) { or // Computing a field address from a pointer propagates the address plus the // offset of the field. - bitOffset = getFieldBitOffset(instr.(FieldAddressInstruction).getField()) + bitOffset = Language::getFieldBitOffset(instr.(FieldAddressInstruction).getField()) or // A copy propagates the source value. operand = instr.(CopyInstruction).getSourceValueOperand() and bitOffset = 0 @@ -212,7 +197,7 @@ private predicate operandReturned(Operand operand, IntValue bitOffset) { } private predicate isArgumentForParameter(CallInstruction ci, Operand operand, Instruction init) { - exists(Function f | + exists(Language::Function f | ci = operand.getUse() and f = ci.getStaticCallTarget() and ( @@ -223,27 +208,27 @@ private predicate isArgumentForParameter(CallInstruction ci, Operand operand, In init.getEnclosingFunction() = f and operand instanceof ThisArgumentOperand ) and - not f.isVirtual() and - not f instanceof AliasFunction + not Language::isFunctionVirtual(f) and + not f instanceof AliasModels::AliasFunction ) } private predicate isAlwaysReturnedArgument(Operand operand) { - exists(AliasFunction f | + exists(AliasModels::AliasFunction f | f = operand.getUse().(CallInstruction).getStaticCallTarget() and f.parameterIsAlwaysReturned(operand.(PositionalArgumentOperand).getIndex()) ) } private predicate isOnlyEscapesViaReturnArgument(Operand operand) { - exists(AliasFunction f | + exists(AliasModels::AliasFunction f | f = operand.getUse().(CallInstruction).getStaticCallTarget() and f.parameterEscapesOnlyViaReturn(operand.(PositionalArgumentOperand).getIndex()) ) } private predicate isNeverEscapesArgument(Operand operand) { - exists(AliasFunction f | + exists(AliasModels::AliasFunction f | f = operand.getUse().(CallInstruction).getStaticCallTarget() and f.parameterNeverEscapes(operand.(PositionalArgumentOperand).getIndex()) ) @@ -265,61 +250,86 @@ private predicate resultEscapesNonReturn(Instruction instr) { } /** - * Holds if the address of the specified local variable or parameter escapes the - * domain of the analysis. + * Holds if the address of `allocation` escapes outside the domain of the analysis. This can occur + * either because the allocation's address is taken within the function and escapes, or because the + * allocation is marked as always escaping via `alwaysEscapes()`. */ -private predicate automaticVariableAddressEscapes(IRAutomaticVariable var) { - // The variable's address escapes if the result of any - // VariableAddressInstruction that computes the variable's address escapes. - exists(VariableAddressInstruction instr | - instr.getIRVariable() = var and - resultEscapesNonReturn(instr) - ) -} - -/** - * Holds if the address of the specified variable escapes the domain of the - * analysis. - */ -predicate variableAddressEscapes(IRVariable var) { +predicate allocationEscapes(Configuration::Allocation allocation) { + allocation.alwaysEscapes() + or exists(IREscapeAnalysisConfiguration config | - config.useSoundEscapeAnalysis() and - automaticVariableAddressEscapes(var.(IRAutomaticVariable)) + config.useSoundEscapeAnalysis() and resultEscapesNonReturn(allocation.getABaseInstruction()) ) - or - // All variables with static storage duration have their address escape, even when escape analysis - // is allowed to be unsound. Otherwise, we won't have a definition for any non-escaped global - // variable. Normally, we rely on `AliasedDefinition` to handle that. - not var instanceof IRAutomaticVariable } /** - * Holds if the result of instruction `instr` points within variable `var`, at - * bit offset `bitOffset` within the variable. If the result points within - * `var`, but at an unknown or non-constant offset, then `bitOffset` is unknown. + * Equivalent to `operandIsPropagated()`, but includes interprocedural propagation. */ -predicate resultPointsTo(Instruction instr, IRVariable var, IntValue bitOffset) { - // The address of a variable points to that variable, at offset 0. - instr.(VariableAddressInstruction).getIRVariable() = var and - bitOffset = 0 +private predicate operandIsPropagatedIncludingByCall(Operand operand, IntValue bitOffset) { + operandIsPropagated(operand, bitOffset) or - // A string literal is just a special read-only global variable. - instr.(StringConstantInstruction).getIRVariable() = var and - bitOffset = 0 - or - exists(Operand operand, IntValue originalBitOffset, IntValue propagatedBitOffset | - operand = instr.getAnOperand() and - // If an operand is propagated, then the result points to the same variable, - // offset by the bit offset from the propagation. - resultPointsTo(operand.getAnyDef(), var, originalBitOffset) and - ( - operandIsPropagated(operand, propagatedBitOffset) - or - exists(CallInstruction ci, Instruction init | - isArgumentForParameter(ci, operand, init) and - resultReturned(init, propagatedBitOffset) - ) - ) and - bitOffset = Ints::add(originalBitOffset, propagatedBitOffset) + exists(CallInstruction call, Instruction init | + isArgumentForParameter(call, operand, init) and + resultReturned(init, bitOffset) + ) +} + +/** + * Holds if `addrOperand` is at offset `bitOffset` from the value of instruction `base`. The offset + * may be `unknown()`. + */ +private predicate hasBaseAndOffset(AddressOperand addrOperand, Instruction base, IntValue bitOffset) { + base = addrOperand.getDef() and bitOffset = 0 // Base case + or + exists( + Instruction middle, int previousBitOffset, Operand middleOperand, IntValue additionalBitOffset + | + // We already have an offset from `middle`. + hasBaseAndOffset(addrOperand, middle, previousBitOffset) and + // `middle` is propagated from `base`. + middleOperand = middle.getAnOperand() and + operandIsPropagatedIncludingByCall(middleOperand, additionalBitOffset) and + base = middleOperand.getDef() and + bitOffset = Ints::add(previousBitOffset, additionalBitOffset) + ) +} + +/** + * Holds if `addrOperand` is at constant offset `bitOffset` from the value of instruction `base`. + * Only holds for the `base` with the longest chain of propagation to `addrOperand`. + */ +predicate addressOperandBaseAndConstantOffset( + AddressOperand addrOperand, Instruction base, int bitOffset +) { + hasBaseAndOffset(addrOperand, base, bitOffset) and + Ints::hasValue(bitOffset) and + not exists(Instruction previousBase, int previousBitOffset | + hasBaseAndOffset(addrOperand, previousBase, previousBitOffset) and + previousBase = base.getAnOperand().getDef() and + Ints::hasValue(previousBitOffset) + ) +} + +/** + * Gets the allocation into which `addrOperand` points, if known. + */ +Configuration::Allocation getAddressOperandAllocation(AddressOperand addrOperand) { + addressOperandAllocationAndOffset(addrOperand, result, _) +} + +/** + * Holds if `addrOperand` is at offset `bitOffset` from a base instruction of `allocation`. The + * offset may be `unknown()`. + */ +predicate addressOperandAllocationAndOffset( + AddressOperand addrOperand, Configuration::Allocation allocation, IntValue bitOffset +) { + exists(Instruction base | + allocation.getABaseInstruction() = base and + hasBaseAndOffset(addrOperand, base, bitOffset) and + not exists(Instruction previousBase | + hasBaseAndOffset(addrOperand, previousBase, _) and + previousBase = base.getAnOperand().getDef() + ) ) } diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/AliasAnalysisImports.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/AliasAnalysisImports.qll new file mode 100644 index 00000000000..c4aeaf93cce --- /dev/null +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/AliasAnalysisImports.qll @@ -0,0 +1,3 @@ +import semmle.code.cpp.ir.implementation.IRConfiguration +import semmle.code.cpp.ir.internal.IntegerConstant as Ints +import semmle.code.cpp.models.interfaces.Alias as AliasModels diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/AliasAnalysisInternal.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/AliasAnalysisInternal.qll index 8a9e43e14a3..08a563abc73 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/AliasAnalysisInternal.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/AliasAnalysisInternal.qll @@ -1 +1,3 @@ +import semmle.code.cpp.ir.internal.IRCppLanguage as Language import semmle.code.cpp.ir.implementation.raw.IR as InputIR +import AliasConfiguration as Configuration diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/AliasConfiguration.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/AliasConfiguration.qll new file mode 100644 index 00000000000..5be476e12ee --- /dev/null +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/AliasConfiguration.qll @@ -0,0 +1,16 @@ +private import AliasConfigurationImports + +/** + * A memory allocation that can be tracked by the SimpleSSA alias analysis. + * All automatic variables are tracked. + */ +class Allocation extends IRAutomaticVariable { + VariableAddressInstruction getABaseInstruction() { result.getIRVariable() = this } + + final string getAllocationString() { result = toString() } + + predicate alwaysEscapes() { + // An automatic variable only escapes if its address is taken and escapes. + none() + } +} diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/AliasConfigurationImports.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/AliasConfigurationImports.qll new file mode 100644 index 00000000000..07cbc6308b7 --- /dev/null +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/AliasConfigurationImports.qll @@ -0,0 +1 @@ +import semmle.code.cpp.ir.implementation.raw.IR diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/SimpleSSA.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/SimpleSSA.qll index ddff6444b90..7de1ab8d72e 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/SimpleSSA.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/SimpleSSA.qll @@ -1,66 +1,57 @@ import AliasAnalysis private import SimpleSSAImports import SimpleSSAPublicImports +private import AliasConfiguration -private class IntValue = Ints::IntValue; - -private predicate hasResultMemoryAccess( - Instruction instr, IRVariable var, Language::LanguageType type, IntValue bitOffset -) { - resultPointsTo(instr.getResultAddressOperand().getAnyDef(), var, bitOffset) and - type = instr.getResultLanguageType() -} - -private predicate hasOperandMemoryAccess( - MemoryOperand operand, IRVariable var, Language::LanguageType type, IntValue bitOffset -) { - resultPointsTo(operand.getAddressOperand().getAnyDef(), var, bitOffset) and - type = operand.getLanguageType() -} - -/** - * Holds if the specified variable should be modeled in SSA form. For unaliased SSA, we only model a variable if its - * address never escapes and all reads and writes of that variable access the entire variable using the original type - * of the variable. - */ -private predicate isVariableModeled(IRVariable var) { - not variableAddressEscapes(var) and - // There's no need to check for the right size. An `IRVariable` never has an `UnknownType`, so the test for - // `type = var.getType()` is sufficient. - forall(Instruction instr, Language::LanguageType type, IntValue bitOffset | - hasResultMemoryAccess(instr, var, type, bitOffset) and - not instr.hasResultMayMemoryAccess() - | +private predicate isTotalAccess(Allocation var, AddressOperand addrOperand, IRType type) { + exists(Instruction constantBase, int bitOffset | + addressOperandBaseAndConstantOffset(addrOperand, constantBase, bitOffset) and bitOffset = 0 and - type.getIRType() = var.getIRType() and - not instr.hasResultMayMemoryAccess() - ) and - forall(MemoryOperand operand, Language::LanguageType type, IntValue bitOffset | - hasOperandMemoryAccess(operand, var, type, bitOffset) - | - bitOffset = 0 and - type.getIRType() = var.getIRType() and - not operand.hasMayReadMemoryAccess() + constantBase = var.getABaseInstruction() and + type = var.getIRType() ) } -private newtype TMemoryLocation = MkMemoryLocation(IRVariable var) { isVariableModeled(var) } +/** + * Holds if the specified variable should be modeled in SSA form. For unaliased SSA, we only model a + * variable if its address never escapes and all reads and writes of that variable access the entire + * variable using the original type of the variable. + */ +private predicate isVariableModeled(Allocation var) { + not allocationEscapes(var) and + forall(Instruction instr, AddressOperand addrOperand, IRType type | + addrOperand = instr.getResultAddressOperand() and + type = instr.getResultIRType() and + var = getAddressOperandAllocation(addrOperand) + | + isTotalAccess(var, addrOperand, type) and not instr.hasResultMayMemoryAccess() + ) and + forall(MemoryOperand memOperand, AddressOperand addrOperand, IRType type | + addrOperand = memOperand.getAddressOperand() and + type = memOperand.getIRType() and + var = getAddressOperandAllocation(addrOperand) + | + isTotalAccess(var, addrOperand, type) and not memOperand.hasMayReadMemoryAccess() + ) +} -private MemoryLocation getMemoryLocation(IRVariable var) { result.getIRVariable() = var } +private newtype TMemoryLocation = MkMemoryLocation(Allocation var) { isVariableModeled(var) } + +private MemoryLocation getMemoryLocation(Allocation var) { result.getAllocation() = var } class MemoryLocation extends TMemoryLocation { - IRVariable var; + Allocation var; MemoryLocation() { this = MkMemoryLocation(var) } - final string toString() { result = var.toString() } + final string toString() { result = var.getAllocationString() } + + final Allocation getAllocation() { result = var } final Language::Location getLocation() { result = var.getLocation() } final IRFunction getIRFunction() { result = var.getEnclosingIRFunction() } - final IRVariable getIRVariable() { result = var } - final VirtualVariable getVirtualVariable() { result = this } final Language::LanguageType getType() { result = var.getLanguageType() } @@ -77,15 +68,9 @@ Overlap getOverlap(MemoryLocation def, MemoryLocation use) { } MemoryLocation getResultMemoryLocation(Instruction instr) { - exists(IRVariable var | - hasResultMemoryAccess(instr, var, _, _) and - result = getMemoryLocation(var) - ) + result = getMemoryLocation(getAddressOperandAllocation(instr.getResultAddressOperand())) } MemoryLocation getOperandMemoryLocation(MemoryOperand operand) { - exists(IRVariable var | - hasOperandMemoryAccess(operand, var, _, _) and - result = getMemoryLocation(var) - ) + result = getMemoryLocation(getAddressOperandAllocation(operand.getAddressOperand())) } diff --git a/cpp/ql/src/semmle/code/cpp/ir/internal/IRCppLanguage.qll b/cpp/ql/src/semmle/code/cpp/ir/internal/IRCppLanguage.qll index 2adea27afb4..3bde6c7d501 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/internal/IRCppLanguage.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/internal/IRCppLanguage.qll @@ -80,3 +80,17 @@ predicate hasPotentialLoop(Function f) { } predicate hasGoto(Function f) { exists(Cpp::GotoStmt s | s.getEnclosingFunction() = f) } + +/** + * Gets the offset of field `field` in bits. + */ +int getFieldBitOffset(Field field) { + if field instanceof Cpp::BitField + then result = (field.getByteOffset() * 8) + field.(Cpp::BitField).getBitOffset() + else result = field.getByteOffset() * 8 +} + +/** + * Holds if the specified `Function` can be overridden in a derived class. + */ +predicate isFunctionVirtual(Function f) { f.isVirtual() } diff --git a/cpp/ql/src/semmle/code/cpp/valuenumbering/GlobalValueNumbering.qll b/cpp/ql/src/semmle/code/cpp/valuenumbering/GlobalValueNumbering.qll index f9231e24725..7a2d43a26e0 100644 --- a/cpp/ql/src/semmle/code/cpp/valuenumbering/GlobalValueNumbering.qll +++ b/cpp/ql/src/semmle/code/cpp/valuenumbering/GlobalValueNumbering.qll @@ -1,608 +1 @@ -/** - * Provides an implementation of Global Value Numbering. - * See https://en.wikipedia.org/wiki/Global_value_numbering - * - * The predicate `globalValueNumber` converts an expression into a `GVN`, - * which is an abstract type representing the value of the expression. If - * two expressions have the same `GVN` then they compute the same value. - * For example: - * - * ``` - * void f(int x, int y) { - * g(x+y, x+y); - * } - * ``` - * - * In this example, both arguments in the call to `g` compute the same value, - * so both arguments have the same `GVN`. In other words, we can find - * this call with the following query: - * - * ``` - * from FunctionCall call, GVN v - * where v = globalValueNumber(call.getArgument(0)) - * and v = globalValueNumber(call.getArgument(1)) - * select call - * ``` - * - * The analysis is conservative, so two expressions might have different - * `GVN`s even though the actually always compute the same value. The most - * common reason for this is that the analysis cannot prove that there - * are no side-effects that might cause the computed value to change. - */ - -/* - * Note to developers: the correctness of this module depends on the - * definitions of GVN, globalValueNumber, and analyzableExpr being kept in - * sync with each other. If you change this module then make sure that the - * change is symmetric across all three. - */ - -import cpp -private import semmle.code.cpp.controlflow.SSA - -/** - * Holds if the result is a control flow node that might change the - * value of any global variable. This is used in the implementation - * of `GVN_OtherVariable`, because we need to be quite conservative when - * we assign a value number to a global variable. For example: - * - * ``` - * x = g+1; - * dosomething(); - * y = g+1; - * ``` - * - * It is not safe to assign the same value number to both instances - * of `g+1` in this example, because the call to `dosomething` might - * change the value of `g`. - */ -private ControlFlowNode nodeWithPossibleSideEffect() { - result instanceof Call - or - // If the lhs of an assignment is not analyzable by SSA, then - // we need to treat the assignment as having a possible side-effect. - result instanceof Assignment and not result instanceof SsaDefinition - or - result instanceof CrementOperation and not result instanceof SsaDefinition - or - exists(LocalVariable v | - result = v.getInitializer().getExpr() and not result instanceof SsaDefinition - ) - or - result instanceof AsmStmt -} - -/** - * Gets the entry node of the control flow graph of which `node` is a - * member. - */ -cached -private ControlFlowNode getControlFlowEntry(ControlFlowNode node) { - result = node.getControlFlowScope().getEntryPoint() and - result.getASuccessor*() = node -} - -/** - * Holds if there is a control flow edge from `src` to `dst` or - * if `dst` is an expression with a possible side-effect. The idea - * is to treat side effects as entry points in the control flow - * graph so that we can use the dominator tree to find the most recent - * side-effect. - */ -private predicate sideEffectCFG(ControlFlowNode src, ControlFlowNode dst) { - src.getASuccessor() = dst - or - // Add an edge from the entry point to any node that might have a side - // effect. - dst = nodeWithPossibleSideEffect() and - src = getControlFlowEntry(dst) -} - -/** - * Holds if `dominator` is the immediate dominator of `node` in - * the side-effect CFG. - */ -private predicate iDomEffect(ControlFlowNode dominator, ControlFlowNode node) = - idominance(functionEntry/1, sideEffectCFG/2)(_, dominator, node) - -/** - * Gets the most recent side effect. To be more precise, `result` is a - * dominator of `node` and no side-effects can occur between `result` and - * `node`. - * - * `sideEffectCFG` has an edge from the function entry to every node with a - * side-effect. This means that every node with a side-effect has the - * function entry as its immediate dominator. So if node `x` dominates node - * `y` then there can be no side effects between `x` and `y` unless `x` is - * the function entry. So the optimal choice for `result` has the function - * entry as its immediate dominator. - * - * Example: - * - * ``` - * 000: int f(int a, int b, int *p) { - * 001: int r = 0; - * 002: if (a) { - * 003: if (b) { - * 004: sideEffect1(); - * 005: } - * 006: } else { - * 007: sideEffect2(); - * 008: } - * 009: if (a) { - * 010: r++; // Not a side-effect, because r is an SSA variable. - * 011: } - * 012: if (b) { - * 013: r++; // Not a side-effect, because r is an SSA variable. - * 014: } - * 015: return *p; - * 016: } - * ``` - * - * Suppose we want to find the most recent side-effect for the dereference - * of `p` on line 015. The `sideEffectCFG` has an edge from the function - * entry (line 000) to the side effects at lines 004 and 007. Therefore, - * the immediate dominator tree looks like this: - * - * 000 - 001 - 002 - 003 - * - 004 - * - 007 - * - 009 - 010 - * - 012 - 013 - * - 015 - * - * The immediate dominator path to line 015 is 000 - 009 - 012 - 015. - * Therefore, the most recent side effect for line 015 is line 009. - */ -cached -private ControlFlowNode mostRecentSideEffect(ControlFlowNode node) { - exists(ControlFlowNode entry | - functionEntry(entry) and - iDomEffect(entry, result) and - iDomEffect*(result, node) - ) -} - -/** Used to represent the "global value number" of an expression. */ -cached -private newtype GVNBase = - GVN_IntConst(int val, Type t) { mk_IntConst(val, t, _) } or - GVN_FloatConst(float val, Type t) { mk_FloatConst(val, t, _) } or - // If the local variable does not have a defining value, then - // we use the SsaDefinition as its global value number. - GVN_UndefinedStackVariable(StackVariable x, SsaDefinition def) { - mk_UndefinedStackVariable(x, def, _) - } or - // Variables with no SSA information. As a crude (but safe) - // approximation, we use `mostRecentSideEffect` to compute a definition - // location for the variable. This ensures that two instances of the same - // global variable will only get the same value number if they are - // guaranteed to have the same value. - GVN_OtherVariable(Variable x, ControlFlowNode dominator) { mk_OtherVariable(x, dominator, _) } or - GVN_FieldAccess(GVN s, Field f) { - mk_DotFieldAccess(s, f, _) or - mk_PointerFieldAccess_with_deref(s, f, _) or - mk_ImplicitThisFieldAccess_with_deref(s, f, _) - } or - // Dereference a pointer. The value might have changed since the last - // time the pointer was dereferenced, so we need to include a definition - // location. As a crude (but safe) approximation, we use - // `mostRecentSideEffect` to compute a definition location. - GVN_Deref(GVN p, ControlFlowNode dominator) { - mk_Deref(p, dominator, _) or - mk_PointerFieldAccess(p, _, dominator, _) or - mk_ImplicitThisFieldAccess_with_qualifier(p, _, dominator, _) - } or - GVN_ThisExpr(Function fcn) { - mk_ThisExpr(fcn, _) or - mk_ImplicitThisFieldAccess(fcn, _, _, _) - } or - GVN_Conversion(Type t, GVN child) { mk_Conversion(t, child, _) } or - GVN_BinaryOp(GVN lhs, GVN rhs, string opname) { mk_BinaryOp(lhs, rhs, opname, _) } or - GVN_UnaryOp(GVN child, string opname) { mk_UnaryOp(child, opname, _) } or - GVN_ArrayAccess(GVN x, GVN i, ControlFlowNode dominator) { mk_ArrayAccess(x, i, dominator, _) } or - // Any expression that is not handled by the cases above is - // given a unique number based on the expression itself. - GVN_Unanalyzable(Expr e) { not analyzableExpr(e) } - -/** - * A Global Value Number. A GVN is an abstract representation of the value - * computed by an expression. The relationship between `Expr` and `GVN` is - * many-to-one: every `Expr` has exactly one `GVN`, but multiple - * expressions can have the same `GVN`. If two expressions have the same - * `GVN`, it means that they compute the same value at run time. The `GVN` - * is an opaque value, so you cannot deduce what the run-time value of an - * expression will be from its `GVN`. The only use for the `GVN` of an - * expression is to find other expressions that compute the same value. - * Use the predicate `globalValueNumber` to get the `GVN` for an `Expr`. - * - * Note: `GVN` has `toString` and `getLocation` methods, so that it can be - * displayed in a results list. These work by picking an arbitrary - * expression with this `GVN` and using its `toString` and `getLocation` - * methods. - */ -class GVN extends GVNBase { - GVN() { this instanceof GVNBase } - - /** Gets an expression that has this GVN. */ - Expr getAnExpr() { this = globalValueNumber(result) } - - /** Gets the kind of the GVN. This can be useful for debugging. */ - string getKind() { - if this instanceof GVN_IntConst - then result = "IntConst" - else - if this instanceof GVN_FloatConst - then result = "FloatConst" - else - if this instanceof GVN_UndefinedStackVariable - then result = "UndefinedStackVariable" - else - if this instanceof GVN_OtherVariable - then result = "OtherVariable" - else - if this instanceof GVN_FieldAccess - then result = "FieldAccess" - else - if this instanceof GVN_Deref - then result = "Deref" - else - if this instanceof GVN_ThisExpr - then result = "ThisExpr" - else - if this instanceof GVN_Conversion - then result = "Conversion" - else - if this instanceof GVN_BinaryOp - then result = "BinaryOp" - else - if this instanceof GVN_UnaryOp - then result = "UnaryOp" - else - if this instanceof GVN_ArrayAccess - then result = "ArrayAccess" - else - if this instanceof GVN_Unanalyzable - then result = "Unanalyzable" - else result = "error" - } - - /** - * Gets an example of an expression with this GVN. - * This is useful for things like implementing toString(). - */ - private Expr exampleExpr() { - // Pick the expression with the minimum source location string. This is - // just an arbitrary way to pick an expression with this `GVN`. - result = min(Expr e | this = globalValueNumber(e) | e order by e.getLocation().toString()) - } - - /** Gets a textual representation of this element. */ - string toString() { result = exampleExpr().toString() } - - /** Gets the primary location of this element. */ - Location getLocation() { result = exampleExpr().getLocation() } -} - -private predicate analyzableIntConst(Expr e) { - strictcount(e.getValue().toInt()) = 1 and - strictcount(e.getUnspecifiedType()) = 1 -} - -private predicate mk_IntConst(int val, Type t, Expr e) { - analyzableIntConst(e) and - val = e.getValue().toInt() and - t = e.getUnspecifiedType() -} - -private predicate analyzableFloatConst(Expr e) { - strictcount(e.getValue().toFloat()) = 1 and - strictcount(e.getUnspecifiedType()) = 1 and - not analyzableIntConst(e) -} - -private predicate mk_FloatConst(float val, Type t, Expr e) { - analyzableFloatConst(e) and - val = e.getValue().toFloat() and - t = e.getUnspecifiedType() -} - -private predicate analyzableStackVariable(VariableAccess access) { - strictcount(SsaDefinition def | def.getAUse(_) = access | def) = 1 and - strictcount(SsaDefinition def, Variable v | def.getAUse(v) = access | v) = 1 and - count(SsaDefinition def, Variable v | - def.getAUse(v) = access - | - def.getDefiningValue(v).getFullyConverted() - ) <= 1 and - not analyzableConst(access) -} - -// Note: this predicate only has a result if the access has no -// defining value. If there is a defining value, then there is no -// need to generate a fresh `GVN` for the access because `globalValueNumber` -// will follow the chain and use the GVN of the defining value. -private predicate mk_UndefinedStackVariable( - StackVariable x, SsaDefinition def, VariableAccess access -) { - analyzableStackVariable(access) and - access = def.getAUse(x) and - not exists(def.getDefiningValue(x)) -} - -private predicate analyzableDotFieldAccess(DotFieldAccess access) { - strictcount(access.getTarget()) = 1 and - strictcount(access.getQualifier().getFullyConverted()) = 1 and - not analyzableConst(access) -} - -private predicate mk_DotFieldAccess(GVN qualifier, Field target, DotFieldAccess access) { - analyzableDotFieldAccess(access) and - target = access.getTarget() and - qualifier = globalValueNumber(access.getQualifier().getFullyConverted()) -} - -private predicate analyzablePointerFieldAccess(PointerFieldAccess access) { - strictcount(mostRecentSideEffect(access)) = 1 and - strictcount(access.getTarget()) = 1 and - strictcount(access.getQualifier().getFullyConverted()) = 1 and - not analyzableConst(access) -} - -private predicate mk_PointerFieldAccess( - GVN qualifier, Field target, ControlFlowNode dominator, PointerFieldAccess access -) { - analyzablePointerFieldAccess(access) and - dominator = mostRecentSideEffect(access) and - target = access.getTarget() and - qualifier = globalValueNumber(access.getQualifier().getFullyConverted()) -} - -/** - * `obj->field` is equivalent to `(*obj).field`, so we need to wrap an - * extra `GVN_Deref` around the qualifier. - */ -private predicate mk_PointerFieldAccess_with_deref( - GVN new_qualifier, Field target, PointerFieldAccess access -) { - exists(GVN qualifier, ControlFlowNode dominator | - mk_PointerFieldAccess(qualifier, target, dominator, access) and - new_qualifier = GVN_Deref(qualifier, dominator) - ) -} - -private predicate analyzableImplicitThisFieldAccess(ImplicitThisFieldAccess access) { - strictcount(mostRecentSideEffect(access)) = 1 and - strictcount(access.getTarget()) = 1 and - strictcount(access.getEnclosingFunction()) = 1 and - not analyzableConst(access) -} - -private predicate mk_ImplicitThisFieldAccess( - Function fcn, Field target, ControlFlowNode dominator, ImplicitThisFieldAccess access -) { - analyzableImplicitThisFieldAccess(access) and - dominator = mostRecentSideEffect(access) and - target = access.getTarget() and - fcn = access.getEnclosingFunction() -} - -private predicate mk_ImplicitThisFieldAccess_with_qualifier( - GVN qualifier, Field target, ControlFlowNode dominator, ImplicitThisFieldAccess access -) { - exists(Function fcn | - mk_ImplicitThisFieldAccess(fcn, target, dominator, access) and - qualifier = GVN_ThisExpr(fcn) - ) -} - -private predicate mk_ImplicitThisFieldAccess_with_deref( - GVN new_qualifier, Field target, ImplicitThisFieldAccess access -) { - exists(GVN qualifier, ControlFlowNode dominator | - mk_ImplicitThisFieldAccess_with_qualifier(qualifier, target, dominator, access) and - new_qualifier = GVN_Deref(qualifier, dominator) - ) -} - -/** - * Holds if `access` is an access of a variable that does - * not have SSA information. (For example, because the variable - * is global.) - */ -private predicate analyzableOtherVariable(VariableAccess access) { - not access instanceof FieldAccess and - not exists(SsaDefinition def | access = def.getAUse(_)) and - strictcount(access.getTarget()) = 1 and - strictcount(mostRecentSideEffect(access)) = 1 and - not analyzableConst(access) -} - -private predicate mk_OtherVariable(Variable x, ControlFlowNode dominator, VariableAccess access) { - analyzableOtherVariable(access) and - x = access.getTarget() and - dominator = mostRecentSideEffect(access) -} - -private predicate analyzableConversion(Conversion conv) { - strictcount(conv.getUnspecifiedType()) = 1 and - strictcount(conv.getExpr()) = 1 and - not analyzableConst(conv) -} - -private predicate mk_Conversion(Type t, GVN child, Conversion conv) { - analyzableConversion(conv) and - t = conv.getUnspecifiedType() and - child = globalValueNumber(conv.getExpr()) -} - -private predicate analyzableBinaryOp(BinaryOperation op) { - op.isPure() and - strictcount(op.getLeftOperand().getFullyConverted()) = 1 and - strictcount(op.getRightOperand().getFullyConverted()) = 1 and - strictcount(op.getOperator()) = 1 and - not analyzableConst(op) -} - -private predicate mk_BinaryOp(GVN lhs, GVN rhs, string opname, BinaryOperation op) { - analyzableBinaryOp(op) and - lhs = globalValueNumber(op.getLeftOperand().getFullyConverted()) and - rhs = globalValueNumber(op.getRightOperand().getFullyConverted()) and - opname = op.getOperator() -} - -private predicate analyzableUnaryOp(UnaryOperation op) { - not op instanceof PointerDereferenceExpr and - op.isPure() and - strictcount(op.getOperand().getFullyConverted()) = 1 and - strictcount(op.getOperator()) = 1 and - not analyzableConst(op) -} - -private predicate mk_UnaryOp(GVN child, string opname, UnaryOperation op) { - analyzableUnaryOp(op) and - child = globalValueNumber(op.getOperand().getFullyConverted()) and - opname = op.getOperator() -} - -private predicate analyzableThisExpr(ThisExpr thisExpr) { - strictcount(thisExpr.getEnclosingFunction()) = 1 and - not analyzableConst(thisExpr) -} - -private predicate mk_ThisExpr(Function fcn, ThisExpr thisExpr) { - analyzableThisExpr(thisExpr) and - fcn = thisExpr.getEnclosingFunction() -} - -private predicate analyzableArrayAccess(ArrayExpr ae) { - strictcount(ae.getArrayBase().getFullyConverted()) = 1 and - strictcount(ae.getArrayOffset().getFullyConverted()) = 1 and - strictcount(mostRecentSideEffect(ae)) = 1 and - not analyzableConst(ae) -} - -private predicate mk_ArrayAccess(GVN base, GVN offset, ControlFlowNode dominator, ArrayExpr ae) { - analyzableArrayAccess(ae) and - base = globalValueNumber(ae.getArrayBase().getFullyConverted()) and - offset = globalValueNumber(ae.getArrayOffset().getFullyConverted()) and - dominator = mostRecentSideEffect(ae) -} - -private predicate analyzablePointerDereferenceExpr(PointerDereferenceExpr deref) { - strictcount(deref.getOperand().getFullyConverted()) = 1 and - strictcount(mostRecentSideEffect(deref)) = 1 and - not analyzableConst(deref) -} - -private predicate mk_Deref(GVN p, ControlFlowNode dominator, PointerDereferenceExpr deref) { - analyzablePointerDereferenceExpr(deref) and - p = globalValueNumber(deref.getOperand().getFullyConverted()) and - dominator = mostRecentSideEffect(deref) -} - -/** Gets the global value number of expression `e`. */ -cached -GVN globalValueNumber(Expr e) { - exists(int val, Type t | - mk_IntConst(val, t, e) and - result = GVN_IntConst(val, t) - ) - or - exists(float val, Type t | - mk_FloatConst(val, t, e) and - result = GVN_FloatConst(val, t) - ) - or - // Local variable with a defining value. - exists(StackVariable x, SsaDefinition def | - analyzableStackVariable(e) and - e = def.getAUse(x) and - result = globalValueNumber(def.getDefiningValue(x).getFullyConverted()) - ) - or - // Local variable without a defining value. - exists(StackVariable x, SsaDefinition def | - mk_UndefinedStackVariable(x, def, e) and - result = GVN_UndefinedStackVariable(x, def) - ) - or - // Variable with no SSA information. - exists(Variable x, ControlFlowNode dominator | - mk_OtherVariable(x, dominator, e) and - result = GVN_OtherVariable(x, dominator) - ) - or - exists(GVN qualifier, Field target | - mk_DotFieldAccess(qualifier, target, e) and - result = GVN_FieldAccess(qualifier, target) - ) - or - exists(GVN qualifier, Field target | - mk_PointerFieldAccess_with_deref(qualifier, target, e) and - result = GVN_FieldAccess(qualifier, target) - ) - or - exists(GVN qualifier, Field target | - mk_ImplicitThisFieldAccess_with_deref(qualifier, target, e) and - result = GVN_FieldAccess(qualifier, target) - ) - or - exists(Function fcn | - mk_ThisExpr(fcn, e) and - result = GVN_ThisExpr(fcn) - ) - or - exists(Type t, GVN child | - mk_Conversion(t, child, e) and - result = GVN_Conversion(t, child) - ) - or - exists(GVN lhs, GVN rhs, string opname | - mk_BinaryOp(lhs, rhs, opname, e) and - result = GVN_BinaryOp(lhs, rhs, opname) - ) - or - exists(GVN child, string opname | - mk_UnaryOp(child, opname, e) and - result = GVN_UnaryOp(child, opname) - ) - or - exists(GVN x, GVN i, ControlFlowNode dominator | - mk_ArrayAccess(x, i, dominator, e) and - result = GVN_ArrayAccess(x, i, dominator) - ) - or - exists(GVN p, ControlFlowNode dominator | - mk_Deref(p, dominator, e) and - result = GVN_Deref(p, dominator) - ) - or - not analyzableExpr(e) and result = GVN_Unanalyzable(e) -} - -private predicate analyzableConst(Expr e) { - analyzableIntConst(e) or - analyzableFloatConst(e) -} - -/** - * Holds if the expression is explicitly handled by `globalValueNumber`. - * Unanalyzable expressions still need to be given a global value number, - * but it will be a unique number that is not shared with any other - * expression. - */ -private predicate analyzableExpr(Expr e) { - analyzableConst(e) or - analyzableStackVariable(e) or - analyzableDotFieldAccess(e) or - analyzablePointerFieldAccess(e) or - analyzableImplicitThisFieldAccess(e) or - analyzableOtherVariable(e) or - analyzableConversion(e) or - analyzableBinaryOp(e) or - analyzableUnaryOp(e) or - analyzableThisExpr(e) or - analyzableArrayAccess(e) or - analyzablePointerDereferenceExpr(e) -} +import GlobalValueNumberingImpl diff --git a/cpp/ql/src/semmle/code/cpp/valuenumbering/GlobalValueNumberingImpl.qll b/cpp/ql/src/semmle/code/cpp/valuenumbering/GlobalValueNumberingImpl.qll new file mode 100644 index 00000000000..f9231e24725 --- /dev/null +++ b/cpp/ql/src/semmle/code/cpp/valuenumbering/GlobalValueNumberingImpl.qll @@ -0,0 +1,608 @@ +/** + * Provides an implementation of Global Value Numbering. + * See https://en.wikipedia.org/wiki/Global_value_numbering + * + * The predicate `globalValueNumber` converts an expression into a `GVN`, + * which is an abstract type representing the value of the expression. If + * two expressions have the same `GVN` then they compute the same value. + * For example: + * + * ``` + * void f(int x, int y) { + * g(x+y, x+y); + * } + * ``` + * + * In this example, both arguments in the call to `g` compute the same value, + * so both arguments have the same `GVN`. In other words, we can find + * this call with the following query: + * + * ``` + * from FunctionCall call, GVN v + * where v = globalValueNumber(call.getArgument(0)) + * and v = globalValueNumber(call.getArgument(1)) + * select call + * ``` + * + * The analysis is conservative, so two expressions might have different + * `GVN`s even though the actually always compute the same value. The most + * common reason for this is that the analysis cannot prove that there + * are no side-effects that might cause the computed value to change. + */ + +/* + * Note to developers: the correctness of this module depends on the + * definitions of GVN, globalValueNumber, and analyzableExpr being kept in + * sync with each other. If you change this module then make sure that the + * change is symmetric across all three. + */ + +import cpp +private import semmle.code.cpp.controlflow.SSA + +/** + * Holds if the result is a control flow node that might change the + * value of any global variable. This is used in the implementation + * of `GVN_OtherVariable`, because we need to be quite conservative when + * we assign a value number to a global variable. For example: + * + * ``` + * x = g+1; + * dosomething(); + * y = g+1; + * ``` + * + * It is not safe to assign the same value number to both instances + * of `g+1` in this example, because the call to `dosomething` might + * change the value of `g`. + */ +private ControlFlowNode nodeWithPossibleSideEffect() { + result instanceof Call + or + // If the lhs of an assignment is not analyzable by SSA, then + // we need to treat the assignment as having a possible side-effect. + result instanceof Assignment and not result instanceof SsaDefinition + or + result instanceof CrementOperation and not result instanceof SsaDefinition + or + exists(LocalVariable v | + result = v.getInitializer().getExpr() and not result instanceof SsaDefinition + ) + or + result instanceof AsmStmt +} + +/** + * Gets the entry node of the control flow graph of which `node` is a + * member. + */ +cached +private ControlFlowNode getControlFlowEntry(ControlFlowNode node) { + result = node.getControlFlowScope().getEntryPoint() and + result.getASuccessor*() = node +} + +/** + * Holds if there is a control flow edge from `src` to `dst` or + * if `dst` is an expression with a possible side-effect. The idea + * is to treat side effects as entry points in the control flow + * graph so that we can use the dominator tree to find the most recent + * side-effect. + */ +private predicate sideEffectCFG(ControlFlowNode src, ControlFlowNode dst) { + src.getASuccessor() = dst + or + // Add an edge from the entry point to any node that might have a side + // effect. + dst = nodeWithPossibleSideEffect() and + src = getControlFlowEntry(dst) +} + +/** + * Holds if `dominator` is the immediate dominator of `node` in + * the side-effect CFG. + */ +private predicate iDomEffect(ControlFlowNode dominator, ControlFlowNode node) = + idominance(functionEntry/1, sideEffectCFG/2)(_, dominator, node) + +/** + * Gets the most recent side effect. To be more precise, `result` is a + * dominator of `node` and no side-effects can occur between `result` and + * `node`. + * + * `sideEffectCFG` has an edge from the function entry to every node with a + * side-effect. This means that every node with a side-effect has the + * function entry as its immediate dominator. So if node `x` dominates node + * `y` then there can be no side effects between `x` and `y` unless `x` is + * the function entry. So the optimal choice for `result` has the function + * entry as its immediate dominator. + * + * Example: + * + * ``` + * 000: int f(int a, int b, int *p) { + * 001: int r = 0; + * 002: if (a) { + * 003: if (b) { + * 004: sideEffect1(); + * 005: } + * 006: } else { + * 007: sideEffect2(); + * 008: } + * 009: if (a) { + * 010: r++; // Not a side-effect, because r is an SSA variable. + * 011: } + * 012: if (b) { + * 013: r++; // Not a side-effect, because r is an SSA variable. + * 014: } + * 015: return *p; + * 016: } + * ``` + * + * Suppose we want to find the most recent side-effect for the dereference + * of `p` on line 015. The `sideEffectCFG` has an edge from the function + * entry (line 000) to the side effects at lines 004 and 007. Therefore, + * the immediate dominator tree looks like this: + * + * 000 - 001 - 002 - 003 + * - 004 + * - 007 + * - 009 - 010 + * - 012 - 013 + * - 015 + * + * The immediate dominator path to line 015 is 000 - 009 - 012 - 015. + * Therefore, the most recent side effect for line 015 is line 009. + */ +cached +private ControlFlowNode mostRecentSideEffect(ControlFlowNode node) { + exists(ControlFlowNode entry | + functionEntry(entry) and + iDomEffect(entry, result) and + iDomEffect*(result, node) + ) +} + +/** Used to represent the "global value number" of an expression. */ +cached +private newtype GVNBase = + GVN_IntConst(int val, Type t) { mk_IntConst(val, t, _) } or + GVN_FloatConst(float val, Type t) { mk_FloatConst(val, t, _) } or + // If the local variable does not have a defining value, then + // we use the SsaDefinition as its global value number. + GVN_UndefinedStackVariable(StackVariable x, SsaDefinition def) { + mk_UndefinedStackVariable(x, def, _) + } or + // Variables with no SSA information. As a crude (but safe) + // approximation, we use `mostRecentSideEffect` to compute a definition + // location for the variable. This ensures that two instances of the same + // global variable will only get the same value number if they are + // guaranteed to have the same value. + GVN_OtherVariable(Variable x, ControlFlowNode dominator) { mk_OtherVariable(x, dominator, _) } or + GVN_FieldAccess(GVN s, Field f) { + mk_DotFieldAccess(s, f, _) or + mk_PointerFieldAccess_with_deref(s, f, _) or + mk_ImplicitThisFieldAccess_with_deref(s, f, _) + } or + // Dereference a pointer. The value might have changed since the last + // time the pointer was dereferenced, so we need to include a definition + // location. As a crude (but safe) approximation, we use + // `mostRecentSideEffect` to compute a definition location. + GVN_Deref(GVN p, ControlFlowNode dominator) { + mk_Deref(p, dominator, _) or + mk_PointerFieldAccess(p, _, dominator, _) or + mk_ImplicitThisFieldAccess_with_qualifier(p, _, dominator, _) + } or + GVN_ThisExpr(Function fcn) { + mk_ThisExpr(fcn, _) or + mk_ImplicitThisFieldAccess(fcn, _, _, _) + } or + GVN_Conversion(Type t, GVN child) { mk_Conversion(t, child, _) } or + GVN_BinaryOp(GVN lhs, GVN rhs, string opname) { mk_BinaryOp(lhs, rhs, opname, _) } or + GVN_UnaryOp(GVN child, string opname) { mk_UnaryOp(child, opname, _) } or + GVN_ArrayAccess(GVN x, GVN i, ControlFlowNode dominator) { mk_ArrayAccess(x, i, dominator, _) } or + // Any expression that is not handled by the cases above is + // given a unique number based on the expression itself. + GVN_Unanalyzable(Expr e) { not analyzableExpr(e) } + +/** + * A Global Value Number. A GVN is an abstract representation of the value + * computed by an expression. The relationship between `Expr` and `GVN` is + * many-to-one: every `Expr` has exactly one `GVN`, but multiple + * expressions can have the same `GVN`. If two expressions have the same + * `GVN`, it means that they compute the same value at run time. The `GVN` + * is an opaque value, so you cannot deduce what the run-time value of an + * expression will be from its `GVN`. The only use for the `GVN` of an + * expression is to find other expressions that compute the same value. + * Use the predicate `globalValueNumber` to get the `GVN` for an `Expr`. + * + * Note: `GVN` has `toString` and `getLocation` methods, so that it can be + * displayed in a results list. These work by picking an arbitrary + * expression with this `GVN` and using its `toString` and `getLocation` + * methods. + */ +class GVN extends GVNBase { + GVN() { this instanceof GVNBase } + + /** Gets an expression that has this GVN. */ + Expr getAnExpr() { this = globalValueNumber(result) } + + /** Gets the kind of the GVN. This can be useful for debugging. */ + string getKind() { + if this instanceof GVN_IntConst + then result = "IntConst" + else + if this instanceof GVN_FloatConst + then result = "FloatConst" + else + if this instanceof GVN_UndefinedStackVariable + then result = "UndefinedStackVariable" + else + if this instanceof GVN_OtherVariable + then result = "OtherVariable" + else + if this instanceof GVN_FieldAccess + then result = "FieldAccess" + else + if this instanceof GVN_Deref + then result = "Deref" + else + if this instanceof GVN_ThisExpr + then result = "ThisExpr" + else + if this instanceof GVN_Conversion + then result = "Conversion" + else + if this instanceof GVN_BinaryOp + then result = "BinaryOp" + else + if this instanceof GVN_UnaryOp + then result = "UnaryOp" + else + if this instanceof GVN_ArrayAccess + then result = "ArrayAccess" + else + if this instanceof GVN_Unanalyzable + then result = "Unanalyzable" + else result = "error" + } + + /** + * Gets an example of an expression with this GVN. + * This is useful for things like implementing toString(). + */ + private Expr exampleExpr() { + // Pick the expression with the minimum source location string. This is + // just an arbitrary way to pick an expression with this `GVN`. + result = min(Expr e | this = globalValueNumber(e) | e order by e.getLocation().toString()) + } + + /** Gets a textual representation of this element. */ + string toString() { result = exampleExpr().toString() } + + /** Gets the primary location of this element. */ + Location getLocation() { result = exampleExpr().getLocation() } +} + +private predicate analyzableIntConst(Expr e) { + strictcount(e.getValue().toInt()) = 1 and + strictcount(e.getUnspecifiedType()) = 1 +} + +private predicate mk_IntConst(int val, Type t, Expr e) { + analyzableIntConst(e) and + val = e.getValue().toInt() and + t = e.getUnspecifiedType() +} + +private predicate analyzableFloatConst(Expr e) { + strictcount(e.getValue().toFloat()) = 1 and + strictcount(e.getUnspecifiedType()) = 1 and + not analyzableIntConst(e) +} + +private predicate mk_FloatConst(float val, Type t, Expr e) { + analyzableFloatConst(e) and + val = e.getValue().toFloat() and + t = e.getUnspecifiedType() +} + +private predicate analyzableStackVariable(VariableAccess access) { + strictcount(SsaDefinition def | def.getAUse(_) = access | def) = 1 and + strictcount(SsaDefinition def, Variable v | def.getAUse(v) = access | v) = 1 and + count(SsaDefinition def, Variable v | + def.getAUse(v) = access + | + def.getDefiningValue(v).getFullyConverted() + ) <= 1 and + not analyzableConst(access) +} + +// Note: this predicate only has a result if the access has no +// defining value. If there is a defining value, then there is no +// need to generate a fresh `GVN` for the access because `globalValueNumber` +// will follow the chain and use the GVN of the defining value. +private predicate mk_UndefinedStackVariable( + StackVariable x, SsaDefinition def, VariableAccess access +) { + analyzableStackVariable(access) and + access = def.getAUse(x) and + not exists(def.getDefiningValue(x)) +} + +private predicate analyzableDotFieldAccess(DotFieldAccess access) { + strictcount(access.getTarget()) = 1 and + strictcount(access.getQualifier().getFullyConverted()) = 1 and + not analyzableConst(access) +} + +private predicate mk_DotFieldAccess(GVN qualifier, Field target, DotFieldAccess access) { + analyzableDotFieldAccess(access) and + target = access.getTarget() and + qualifier = globalValueNumber(access.getQualifier().getFullyConverted()) +} + +private predicate analyzablePointerFieldAccess(PointerFieldAccess access) { + strictcount(mostRecentSideEffect(access)) = 1 and + strictcount(access.getTarget()) = 1 and + strictcount(access.getQualifier().getFullyConverted()) = 1 and + not analyzableConst(access) +} + +private predicate mk_PointerFieldAccess( + GVN qualifier, Field target, ControlFlowNode dominator, PointerFieldAccess access +) { + analyzablePointerFieldAccess(access) and + dominator = mostRecentSideEffect(access) and + target = access.getTarget() and + qualifier = globalValueNumber(access.getQualifier().getFullyConverted()) +} + +/** + * `obj->field` is equivalent to `(*obj).field`, so we need to wrap an + * extra `GVN_Deref` around the qualifier. + */ +private predicate mk_PointerFieldAccess_with_deref( + GVN new_qualifier, Field target, PointerFieldAccess access +) { + exists(GVN qualifier, ControlFlowNode dominator | + mk_PointerFieldAccess(qualifier, target, dominator, access) and + new_qualifier = GVN_Deref(qualifier, dominator) + ) +} + +private predicate analyzableImplicitThisFieldAccess(ImplicitThisFieldAccess access) { + strictcount(mostRecentSideEffect(access)) = 1 and + strictcount(access.getTarget()) = 1 and + strictcount(access.getEnclosingFunction()) = 1 and + not analyzableConst(access) +} + +private predicate mk_ImplicitThisFieldAccess( + Function fcn, Field target, ControlFlowNode dominator, ImplicitThisFieldAccess access +) { + analyzableImplicitThisFieldAccess(access) and + dominator = mostRecentSideEffect(access) and + target = access.getTarget() and + fcn = access.getEnclosingFunction() +} + +private predicate mk_ImplicitThisFieldAccess_with_qualifier( + GVN qualifier, Field target, ControlFlowNode dominator, ImplicitThisFieldAccess access +) { + exists(Function fcn | + mk_ImplicitThisFieldAccess(fcn, target, dominator, access) and + qualifier = GVN_ThisExpr(fcn) + ) +} + +private predicate mk_ImplicitThisFieldAccess_with_deref( + GVN new_qualifier, Field target, ImplicitThisFieldAccess access +) { + exists(GVN qualifier, ControlFlowNode dominator | + mk_ImplicitThisFieldAccess_with_qualifier(qualifier, target, dominator, access) and + new_qualifier = GVN_Deref(qualifier, dominator) + ) +} + +/** + * Holds if `access` is an access of a variable that does + * not have SSA information. (For example, because the variable + * is global.) + */ +private predicate analyzableOtherVariable(VariableAccess access) { + not access instanceof FieldAccess and + not exists(SsaDefinition def | access = def.getAUse(_)) and + strictcount(access.getTarget()) = 1 and + strictcount(mostRecentSideEffect(access)) = 1 and + not analyzableConst(access) +} + +private predicate mk_OtherVariable(Variable x, ControlFlowNode dominator, VariableAccess access) { + analyzableOtherVariable(access) and + x = access.getTarget() and + dominator = mostRecentSideEffect(access) +} + +private predicate analyzableConversion(Conversion conv) { + strictcount(conv.getUnspecifiedType()) = 1 and + strictcount(conv.getExpr()) = 1 and + not analyzableConst(conv) +} + +private predicate mk_Conversion(Type t, GVN child, Conversion conv) { + analyzableConversion(conv) and + t = conv.getUnspecifiedType() and + child = globalValueNumber(conv.getExpr()) +} + +private predicate analyzableBinaryOp(BinaryOperation op) { + op.isPure() and + strictcount(op.getLeftOperand().getFullyConverted()) = 1 and + strictcount(op.getRightOperand().getFullyConverted()) = 1 and + strictcount(op.getOperator()) = 1 and + not analyzableConst(op) +} + +private predicate mk_BinaryOp(GVN lhs, GVN rhs, string opname, BinaryOperation op) { + analyzableBinaryOp(op) and + lhs = globalValueNumber(op.getLeftOperand().getFullyConverted()) and + rhs = globalValueNumber(op.getRightOperand().getFullyConverted()) and + opname = op.getOperator() +} + +private predicate analyzableUnaryOp(UnaryOperation op) { + not op instanceof PointerDereferenceExpr and + op.isPure() and + strictcount(op.getOperand().getFullyConverted()) = 1 and + strictcount(op.getOperator()) = 1 and + not analyzableConst(op) +} + +private predicate mk_UnaryOp(GVN child, string opname, UnaryOperation op) { + analyzableUnaryOp(op) and + child = globalValueNumber(op.getOperand().getFullyConverted()) and + opname = op.getOperator() +} + +private predicate analyzableThisExpr(ThisExpr thisExpr) { + strictcount(thisExpr.getEnclosingFunction()) = 1 and + not analyzableConst(thisExpr) +} + +private predicate mk_ThisExpr(Function fcn, ThisExpr thisExpr) { + analyzableThisExpr(thisExpr) and + fcn = thisExpr.getEnclosingFunction() +} + +private predicate analyzableArrayAccess(ArrayExpr ae) { + strictcount(ae.getArrayBase().getFullyConverted()) = 1 and + strictcount(ae.getArrayOffset().getFullyConverted()) = 1 and + strictcount(mostRecentSideEffect(ae)) = 1 and + not analyzableConst(ae) +} + +private predicate mk_ArrayAccess(GVN base, GVN offset, ControlFlowNode dominator, ArrayExpr ae) { + analyzableArrayAccess(ae) and + base = globalValueNumber(ae.getArrayBase().getFullyConverted()) and + offset = globalValueNumber(ae.getArrayOffset().getFullyConverted()) and + dominator = mostRecentSideEffect(ae) +} + +private predicate analyzablePointerDereferenceExpr(PointerDereferenceExpr deref) { + strictcount(deref.getOperand().getFullyConverted()) = 1 and + strictcount(mostRecentSideEffect(deref)) = 1 and + not analyzableConst(deref) +} + +private predicate mk_Deref(GVN p, ControlFlowNode dominator, PointerDereferenceExpr deref) { + analyzablePointerDereferenceExpr(deref) and + p = globalValueNumber(deref.getOperand().getFullyConverted()) and + dominator = mostRecentSideEffect(deref) +} + +/** Gets the global value number of expression `e`. */ +cached +GVN globalValueNumber(Expr e) { + exists(int val, Type t | + mk_IntConst(val, t, e) and + result = GVN_IntConst(val, t) + ) + or + exists(float val, Type t | + mk_FloatConst(val, t, e) and + result = GVN_FloatConst(val, t) + ) + or + // Local variable with a defining value. + exists(StackVariable x, SsaDefinition def | + analyzableStackVariable(e) and + e = def.getAUse(x) and + result = globalValueNumber(def.getDefiningValue(x).getFullyConverted()) + ) + or + // Local variable without a defining value. + exists(StackVariable x, SsaDefinition def | + mk_UndefinedStackVariable(x, def, e) and + result = GVN_UndefinedStackVariable(x, def) + ) + or + // Variable with no SSA information. + exists(Variable x, ControlFlowNode dominator | + mk_OtherVariable(x, dominator, e) and + result = GVN_OtherVariable(x, dominator) + ) + or + exists(GVN qualifier, Field target | + mk_DotFieldAccess(qualifier, target, e) and + result = GVN_FieldAccess(qualifier, target) + ) + or + exists(GVN qualifier, Field target | + mk_PointerFieldAccess_with_deref(qualifier, target, e) and + result = GVN_FieldAccess(qualifier, target) + ) + or + exists(GVN qualifier, Field target | + mk_ImplicitThisFieldAccess_with_deref(qualifier, target, e) and + result = GVN_FieldAccess(qualifier, target) + ) + or + exists(Function fcn | + mk_ThisExpr(fcn, e) and + result = GVN_ThisExpr(fcn) + ) + or + exists(Type t, GVN child | + mk_Conversion(t, child, e) and + result = GVN_Conversion(t, child) + ) + or + exists(GVN lhs, GVN rhs, string opname | + mk_BinaryOp(lhs, rhs, opname, e) and + result = GVN_BinaryOp(lhs, rhs, opname) + ) + or + exists(GVN child, string opname | + mk_UnaryOp(child, opname, e) and + result = GVN_UnaryOp(child, opname) + ) + or + exists(GVN x, GVN i, ControlFlowNode dominator | + mk_ArrayAccess(x, i, dominator, e) and + result = GVN_ArrayAccess(x, i, dominator) + ) + or + exists(GVN p, ControlFlowNode dominator | + mk_Deref(p, dominator, e) and + result = GVN_Deref(p, dominator) + ) + or + not analyzableExpr(e) and result = GVN_Unanalyzable(e) +} + +private predicate analyzableConst(Expr e) { + analyzableIntConst(e) or + analyzableFloatConst(e) +} + +/** + * Holds if the expression is explicitly handled by `globalValueNumber`. + * Unanalyzable expressions still need to be given a global value number, + * but it will be a unique number that is not shared with any other + * expression. + */ +private predicate analyzableExpr(Expr e) { + analyzableConst(e) or + analyzableStackVariable(e) or + analyzableDotFieldAccess(e) or + analyzablePointerFieldAccess(e) or + analyzableImplicitThisFieldAccess(e) or + analyzableOtherVariable(e) or + analyzableConversion(e) or + analyzableBinaryOp(e) or + analyzableUnaryOp(e) or + analyzableThisExpr(e) or + analyzableArrayAccess(e) or + analyzablePointerDereferenceExpr(e) +} diff --git a/cpp/ql/test/TestUtilities/InlineExpectationsTest.qll b/cpp/ql/test/TestUtilities/InlineExpectationsTest.qll new file mode 100644 index 00000000000..bbd9090c8e6 --- /dev/null +++ b/cpp/ql/test/TestUtilities/InlineExpectationsTest.qll @@ -0,0 +1,285 @@ +/** + * Provides a library for writing QL tests whose success or failure is based on expected results + * embedded in the test source code as comments, rather than a `.expected` file. + * + * To create a new inline expectations test: + * - Declare a class that extends `InlineExpectationsTest`. In the characteristic predicate of the + * new class, bind `this` to a unique string (usually the name of the test). + * - Override the `hasActualResult()` predicate to produce the actual results of the query. For each + * result, specify a `Location`, a text description of the element for which the result was + * reported, a short string to serve as the tag to identify expected results for this test, and the + * expected value of the result. + * - Override `getARelevantTag()` to return the set of tags that can be produced by + * `hasActualResult()`. Often this is just a single tag. + * + * Example: + * ``` + * class ConstantValueTest extends InlineExpectationsTest { + * ConstantValueTest() { this = "ConstantValueTest" } + * + * override string getARelevantTag() { + * // We only use one tag for this test. + * result = "const" + * } + * + * override predicate hasActualResult( + * Location location, string element, string tag, string valuesasas + * ) { + * exists(Expr e | + * tag = "const" and // The tag for this test. + * valuesasas = e.getValue() and // The expected value. Will only hold for constant expressions. + * location = e.getLocation() and // The location of the result to be reported. + * element = e.toString() // The display text for the result. + * ) + * } + * } + * ``` + * + * There is no need to write a `select` clause or query predicate. All of the differences between + * expected results and actual results will be reported in the `failures()` query predicate. + * + * To annotate the test source code with an expected result, place a C++-style (`//`) comment on the + * same line as the expected result, with text of the following format as the body of the comment: + * + * `// $tag=expected-value` + * + * Where `tag` is the value of the `tag` parameter from `hasActualResult()`, and `expected-value` is + * the value of the `value` parameter from `hasActualResult()`. The `=expected-value` portion may be + * omitted, in which case `expected-value` is treated as the empty string. Multiple expectations may + * be placed in the same comment, as long as each is prefixed by a `$`. Any actual result that + * appears on a line that does not contain a matching expected result comment will be reported with + * a message of the form "Unexpected result: tag=value". Any expected result comment for which there + * is no matching actual result will be reported with a message of the form + * "Missing result: tag=expected-value". + * + * Example: + * ``` + * int i = x + 5; // $const=5 + * int j = y + (7 - 3) // $const=7 $const=3 $const=4 // The result of the subtraction is a constant. + * ``` + * + * For tests that contain known false positives and false negatives, it is possible to further + * annotate that a particular expected result is known to be a false positive, or that a particular + * missing result is known to be a false negative: + * + * `// $f+:tag=expected-value` // False positive + * `// $f-:tag=expected-value` // False negative + * + * A false positive expectation is treated as any other expected result, except that if there is no + * matching actual result, the message will be of the form "Fixed false positive: tag=value". A + * false negative expectation is treated as if there were no expected result, except that if a + * matching expected result is found, the message will be of the form + * "Fixed false negative: tag=value". + * + * If the same result value is expected for two or more tags on the same line, there is a shorthand + * notation available: + * + * `// $tag1,tag2=expected-value` + * + * is equivalent to: + * + * `// $tag1=expected-value $tag2=expected-value` + */ + +import cpp + +/** + * Base class for tests with inline expectations. The test extends this class to provide the actual + * results of the query, which are then compared with the expected results in comments to produce a + * list of failure messages that point out where the actual results differ from the expected + * results. + */ +abstract class InlineExpectationsTest extends string { + bindingset[this] + InlineExpectationsTest() { any() } + + /** + * Returns all tags that can be generated by this test. Most tests will only ever produce a single + * tag. Any expected result comments for a tag that is not returned by the `getARelevantTag()` + * predicate for an active test will be ignored. This makes it possible to write multiple tests in + * different `.ql` files that all query the same source code. + */ + abstract string getARelevantTag(); + + /** + * Returns the actual results of the query that is being tested. Each result consist of the + * following values: + * - `location` - The source code location of the result. Any expected result comment must appear + * on the start line of this location. + * - `element` - Display text for the element on which the result is reported. + * - `tag` - The tag that marks this result as coming from this test. This must be one of the tags + * returned by `getARelevantTag()`. + * - `value` - The value of the result, which will be matched against the value associated with + * `tag` in any expected result comment on that line. + */ + abstract predicate hasActualResult(Location location, string element, string tag, string value); + + final predicate hasFailureMessage(FailureLocatable element, string message) { + exists(ActualResult actualResult | + actualResult.getTest() = this and + element = actualResult and + ( + exists(FalseNegativeExpectation falseNegative | + falseNegative.matchesActualResult(actualResult) and + message = "Fixed false negative:" + falseNegative.getExpectationText() + ) + or + not exists(ValidExpectation expectation | expectation.matchesActualResult(actualResult)) and + message = "Unexpected result: " + actualResult.getExpectationText() + ) + ) + or + exists(ValidExpectation expectation | + not exists(ActualResult actualResult | expectation.matchesActualResult(actualResult)) and + expectation.getTag() = getARelevantTag() and + element = expectation and + ( + expectation instanceof GoodExpectation and + message = "Missing result:" + expectation.getExpectationText() + or + expectation instanceof FalsePositiveExpectation and + message = "Fixed false positive:" + expectation.getExpectationText() + ) + ) + or + exists(InvalidExpectation expectation | + element = expectation and + message = "Invalid expectation syntax: " + expectation.getExpectation() + ) + } +} + +/** + * RegEx pattern to match a comment containing one or more expected results. The comment must be a + * C++-style (`//`) comment with `$` as its first non-whitespace character. Any subsequent character + * is treated as part of the expected results, except that the comment may contain a `//` sequence + * to treat the remainder of the line as a regular (non-interpreted) comment. + */ +private string expectationCommentPattern() { result = "//\\s*(\\$(?:[^/]|/[^/])*)(?://.*)?" } + +/** + * RegEx pattern to match a single expected result, not including the leading `$`. It starts with an + * optional `f+:` or `f-:`, followed by one or more comma-separated tags containing only letters, + * `-`, and `_`, optionally followed by `=` and the expected value. + */ +private string expectationPattern() { + result = "(?:(f(?:\\+|-)):)?((?:[A-Za-z-_]+)(?:\\s*,\\s*[A-Za-z-_]+)*)(?:=(.*))?" +} + +private string getAnExpectation(CppStyleComment comment) { + result = comment.getContents().regexpCapture(expectationCommentPattern(), 1).splitAt("$").trim() and + result != "" +} + +private newtype TFailureLocatable = + TActualResult( + InlineExpectationsTest test, Location location, string element, string tag, string value + ) { + test.hasActualResult(location, element, tag, value) + } or + TValidExpectation(CppStyleComment comment, string tag, string value, string knownFailure) { + exists(string expectation | + expectation = getAnExpectation(comment) and + expectation.regexpMatch(expectationPattern()) and + tag = expectation.regexpCapture(expectationPattern(), 2).splitAt(",").trim() and + ( + if exists(expectation.regexpCapture(expectationPattern(), 3)) + then value = expectation.regexpCapture(expectationPattern(), 3) + else value = "" + ) and + ( + if exists(expectation.regexpCapture(expectationPattern(), 1)) + then knownFailure = expectation.regexpCapture(expectationPattern(), 1) + else knownFailure = "" + ) + ) + } or + TInvalidExpectation(CppStyleComment comment, string expectation) { + expectation = getAnExpectation(comment) and + not expectation.regexpMatch(expectationPattern()) + } + +class FailureLocatable extends TFailureLocatable { + string toString() { none() } + + Location getLocation() { none() } + + final string getExpectationText() { result = getTag() + "=" + getValue() } + + string getTag() { none() } + + string getValue() { none() } +} + +class ActualResult extends FailureLocatable, TActualResult { + InlineExpectationsTest test; + Location location; + string element; + string tag; + string value; + + ActualResult() { this = TActualResult(test, location, element, tag, value) } + + override string toString() { result = element } + + override Location getLocation() { result = location } + + InlineExpectationsTest getTest() { result = test } + + override string getTag() { result = tag } + + override string getValue() { result = value } +} + +abstract private class Expectation extends FailureLocatable { + CppStyleComment comment; + + override string toString() { result = comment.toString() } + + override Location getLocation() { result = comment.getLocation() } +} + +private class ValidExpectation extends Expectation, TValidExpectation { + string tag; + string value; + string knownFailure; + + ValidExpectation() { this = TValidExpectation(comment, tag, value, knownFailure) } + + override string getTag() { result = tag } + + override string getValue() { result = value } + + string getKnownFailure() { result = knownFailure } + + predicate matchesActualResult(ActualResult actualResult) { + getLocation().getStartLine() = actualResult.getLocation().getStartLine() and + getLocation().getFile() = actualResult.getLocation().getFile() and + getTag() = actualResult.getTag() and + getValue() = actualResult.getValue() + } +} + +class GoodExpectation extends ValidExpectation { + GoodExpectation() { getKnownFailure() = "" } +} + +class FalsePositiveExpectation extends ValidExpectation { + FalsePositiveExpectation() { getKnownFailure() = "f+" } +} + +class FalseNegativeExpectation extends ValidExpectation { + FalseNegativeExpectation() { getKnownFailure() = "f-" } +} + +class InvalidExpectation extends Expectation, TInvalidExpectation { + string expectation; + + InvalidExpectation() { this = TInvalidExpectation(comment, expectation) } + + string getExpectation() { result = expectation } +} + +query predicate failures(FailureLocatable element, string message) { + exists(InlineExpectationsTest test | test.hasFailureMessage(element, message)) +} diff --git a/cpp/ql/test/library-tests/dataflow/DefaultTaintTracking/defaulttainttracking.cpp b/cpp/ql/test/library-tests/dataflow/DefaultTaintTracking/defaulttainttracking.cpp index ebe38f1f060..02ae1e07154 100644 --- a/cpp/ql/test/library-tests/dataflow/DefaultTaintTracking/defaulttainttracking.cpp +++ b/cpp/ql/test/library-tests/dataflow/DefaultTaintTracking/defaulttainttracking.cpp @@ -77,4 +77,13 @@ void test_dynamic_cast() { reinterpret_cast(b2)->f(getenv("VAR")); dynamic_cast(b2)->f(getenv("VAR")); // tainted [FALSE POSITIVE] +} + +namespace std { + template< class T > + T&& move( T&& t ) noexcept; +} + +void test_std_move() { + sink(std::move(getenv("VAR"))); } \ No newline at end of file diff --git a/cpp/ql/test/library-tests/dataflow/DefaultTaintTracking/tainted.expected b/cpp/ql/test/library-tests/dataflow/DefaultTaintTracking/tainted.expected index 92948eeffc6..d8ef6c68258 100644 --- a/cpp/ql/test/library-tests/dataflow/DefaultTaintTracking/tainted.expected +++ b/cpp/ql/test/library-tests/dataflow/DefaultTaintTracking/tainted.expected @@ -21,14 +21,18 @@ | defaulttainttracking.cpp:22:20:22:25 | call to getenv | defaulttainttracking.cpp:22:8:22:33 | (const char *)... | | defaulttainttracking.cpp:22:20:22:25 | call to getenv | defaulttainttracking.cpp:22:20:22:25 | call to getenv | | defaulttainttracking.cpp:22:20:22:25 | call to getenv | defaulttainttracking.cpp:22:20:22:32 | (const char *)... | +| defaulttainttracking.cpp:22:20:22:25 | call to getenv | defaulttainttracking.cpp:24:8:24:10 | (const char *)... | +| defaulttainttracking.cpp:22:20:22:25 | call to getenv | defaulttainttracking.cpp:24:8:24:10 | array to pointer conversion | | defaulttainttracking.cpp:22:20:22:25 | call to getenv | defaulttainttracking.cpp:24:8:24:10 | buf | | defaulttainttracking.cpp:22:20:22:25 | call to getenv | test_diff.cpp:1:11:1:20 | p#0 | +| defaulttainttracking.cpp:38:25:38:30 | call to getenv | defaulttainttracking.cpp:31:40:31:53 | dotted_address | | defaulttainttracking.cpp:38:25:38:30 | call to getenv | defaulttainttracking.cpp:32:11:32:26 | p#0 | | defaulttainttracking.cpp:38:25:38:30 | call to getenv | defaulttainttracking.cpp:38:11:38:21 | env_pointer | | defaulttainttracking.cpp:38:25:38:30 | call to getenv | defaulttainttracking.cpp:38:25:38:30 | call to getenv | | defaulttainttracking.cpp:38:25:38:30 | call to getenv | defaulttainttracking.cpp:38:25:38:37 | (void *)... | | defaulttainttracking.cpp:38:25:38:30 | call to getenv | defaulttainttracking.cpp:39:22:39:22 | a | | defaulttainttracking.cpp:38:25:38:30 | call to getenv | defaulttainttracking.cpp:39:26:39:34 | call to inet_addr | +| defaulttainttracking.cpp:38:25:38:30 | call to getenv | defaulttainttracking.cpp:39:36:39:61 | (const char *)... | | defaulttainttracking.cpp:38:25:38:30 | call to getenv | defaulttainttracking.cpp:39:50:39:61 | & ... | | defaulttainttracking.cpp:38:25:38:30 | call to getenv | defaulttainttracking.cpp:40:10:40:10 | a | | defaulttainttracking.cpp:64:10:64:15 | call to getenv | defaulttainttracking.cpp:9:11:9:20 | p#0 | @@ -89,6 +93,14 @@ | defaulttainttracking.cpp:79:30:79:35 | call to getenv | defaulttainttracking.cpp:79:30:79:35 | call to getenv | | defaulttainttracking.cpp:79:30:79:35 | call to getenv | defaulttainttracking.cpp:79:30:79:42 | (const char *)... | | defaulttainttracking.cpp:79:30:79:35 | call to getenv | test_diff.cpp:1:11:1:20 | p#0 | +| defaulttainttracking.cpp:88:18:88:23 | call to getenv | defaulttainttracking.cpp:9:11:9:20 | p#0 | +| defaulttainttracking.cpp:88:18:88:23 | call to getenv | defaulttainttracking.cpp:84:17:84:17 | t | +| defaulttainttracking.cpp:88:18:88:23 | call to getenv | defaulttainttracking.cpp:88:8:88:16 | call to move | +| defaulttainttracking.cpp:88:18:88:23 | call to getenv | defaulttainttracking.cpp:88:8:88:32 | (const char *)... | +| defaulttainttracking.cpp:88:18:88:23 | call to getenv | defaulttainttracking.cpp:88:8:88:32 | (reference dereference) | +| defaulttainttracking.cpp:88:18:88:23 | call to getenv | defaulttainttracking.cpp:88:18:88:23 | call to getenv | +| defaulttainttracking.cpp:88:18:88:23 | call to getenv | defaulttainttracking.cpp:88:18:88:30 | (reference to) | +| defaulttainttracking.cpp:88:18:88:23 | call to getenv | test_diff.cpp:1:11:1:20 | p#0 | | test_diff.cpp:92:10:92:13 | argv | defaulttainttracking.cpp:9:11:9:20 | p#0 | | test_diff.cpp:92:10:92:13 | argv | test_diff.cpp:1:11:1:20 | p#0 | | test_diff.cpp:92:10:92:13 | argv | test_diff.cpp:92:10:92:13 | argv | diff --git a/cpp/ql/test/library-tests/dataflow/DefaultTaintTracking/test_diff.expected b/cpp/ql/test/library-tests/dataflow/DefaultTaintTracking/test_diff.expected index c557f43f1c7..2b464f27607 100644 --- a/cpp/ql/test/library-tests/dataflow/DefaultTaintTracking/test_diff.expected +++ b/cpp/ql/test/library-tests/dataflow/DefaultTaintTracking/test_diff.expected @@ -5,10 +5,16 @@ | defaulttainttracking.cpp:22:20:22:25 | call to getenv | defaulttainttracking.cpp:3:21:3:22 | s1 | AST only | | defaulttainttracking.cpp:22:20:22:25 | call to getenv | defaulttainttracking.cpp:21:8:21:10 | buf | AST only | | defaulttainttracking.cpp:22:20:22:25 | call to getenv | defaulttainttracking.cpp:22:15:22:17 | buf | AST only | -| defaulttainttracking.cpp:38:25:38:30 | call to getenv | defaulttainttracking.cpp:31:40:31:53 | dotted_address | AST only | -| defaulttainttracking.cpp:38:25:38:30 | call to getenv | defaulttainttracking.cpp:39:36:39:61 | (const char *)... | AST only | +| defaulttainttracking.cpp:22:20:22:25 | call to getenv | defaulttainttracking.cpp:24:8:24:10 | (const char *)... | IR only | +| defaulttainttracking.cpp:22:20:22:25 | call to getenv | defaulttainttracking.cpp:24:8:24:10 | array to pointer conversion | IR only | | defaulttainttracking.cpp:38:25:38:30 | call to getenv | defaulttainttracking.cpp:39:51:39:61 | env_pointer | AST only | | defaulttainttracking.cpp:64:10:64:15 | call to getenv | defaulttainttracking.cpp:52:24:52:24 | p | IR only | +| defaulttainttracking.cpp:88:18:88:23 | call to getenv | defaulttainttracking.cpp:9:11:9:20 | p#0 | IR only | +| defaulttainttracking.cpp:88:18:88:23 | call to getenv | defaulttainttracking.cpp:88:8:88:16 | call to move | IR only | +| defaulttainttracking.cpp:88:18:88:23 | call to getenv | defaulttainttracking.cpp:88:8:88:32 | (const char *)... | IR only | +| defaulttainttracking.cpp:88:18:88:23 | call to getenv | defaulttainttracking.cpp:88:8:88:32 | (reference dereference) | IR only | +| defaulttainttracking.cpp:88:18:88:23 | call to getenv | defaulttainttracking.cpp:88:18:88:30 | (reference to) | IR only | +| defaulttainttracking.cpp:88:18:88:23 | call to getenv | test_diff.cpp:1:11:1:20 | p#0 | IR only | | test_diff.cpp:104:12:104:15 | argv | test_diff.cpp:104:11:104:20 | (...) | IR only | | test_diff.cpp:108:10:108:13 | argv | test_diff.cpp:36:24:36:24 | p | AST only | | test_diff.cpp:111:10:111:13 | argv | defaulttainttracking.cpp:9:11:9:20 | p#0 | AST only | diff --git a/cpp/ql/test/library-tests/dataflow/dataflow-tests/BarrierGuard.cpp b/cpp/ql/test/library-tests/dataflow/dataflow-tests/BarrierGuard.cpp index 9c3c8bc4569..1896a066f68 100644 --- a/cpp/ql/test/library-tests/dataflow/dataflow-tests/BarrierGuard.cpp +++ b/cpp/ql/test/library-tests/dataflow/dataflow-tests/BarrierGuard.cpp @@ -48,7 +48,7 @@ struct XY { void bg_stackstruct(XY s1, XY s2) { s1.x = source(); if (guarded(s1.x)) { - sink(s1.x); // no flow + sink(s1.x); // no flow [FALSE POSITIVE in AST] } else if (guarded(s1.y)) { sink(s1.x); // flow } else if (guarded(s2.y)) { @@ -61,8 +61,8 @@ void bg_structptr(XY *p1, XY *p2) { if (guarded(p1->x)) { sink(p1->x); // no flow [FALSE POSITIVE in AST] } else if (guarded(p1->y)) { - sink(p1->x); // flow [NOT DETECTED in IR] + sink(p1->x); // flow } else if (guarded(p2->x)) { - sink(p1->x); // flow [NOT DETECTED in IR] + sink(p1->x); // flow } } diff --git a/cpp/ql/test/library-tests/dataflow/dataflow-tests/test.expected b/cpp/ql/test/library-tests/dataflow/dataflow-tests/test.expected index 24fa6fdb5bd..04ad48cd4d6 100644 --- a/cpp/ql/test/library-tests/dataflow/dataflow-tests/test.expected +++ b/cpp/ql/test/library-tests/dataflow/dataflow-tests/test.expected @@ -3,6 +3,7 @@ | BarrierGuard.cpp:25:10:25:15 | source | BarrierGuard.cpp:21:17:21:22 | source | | BarrierGuard.cpp:31:10:31:15 | source | BarrierGuard.cpp:29:16:29:21 | source | | BarrierGuard.cpp:33:10:33:15 | source | BarrierGuard.cpp:29:16:29:21 | source | +| BarrierGuard.cpp:51:13:51:13 | x | BarrierGuard.cpp:49:10:49:15 | call to source | | BarrierGuard.cpp:53:13:53:13 | x | BarrierGuard.cpp:49:10:49:15 | call to source | | BarrierGuard.cpp:55:13:55:13 | x | BarrierGuard.cpp:49:10:49:15 | call to source | | BarrierGuard.cpp:62:14:62:14 | x | BarrierGuard.cpp:60:11:60:16 | call to source | diff --git a/cpp/ql/test/library-tests/dataflow/dataflow-tests/test_diff.expected b/cpp/ql/test/library-tests/dataflow/dataflow-tests/test_diff.expected index e40505722af..8daa9b4b39b 100644 --- a/cpp/ql/test/library-tests/dataflow/dataflow-tests/test_diff.expected +++ b/cpp/ql/test/library-tests/dataflow/dataflow-tests/test_diff.expected @@ -1,8 +1,6 @@ +| BarrierGuard.cpp:49:10:49:15 | BarrierGuard.cpp:51:13:51:13 | AST only | | BarrierGuard.cpp:60:11:60:16 | BarrierGuard.cpp:62:14:62:14 | AST only | -| BarrierGuard.cpp:60:11:60:16 | BarrierGuard.cpp:64:14:64:14 | AST only | -| BarrierGuard.cpp:60:11:60:16 | BarrierGuard.cpp:66:14:66:14 | AST only | | clang.cpp:12:9:12:20 | clang.cpp:22:8:22:20 | AST only | -| clang.cpp:28:27:28:32 | clang.cpp:29:27:29:28 | AST only | | clang.cpp:28:27:28:32 | clang.cpp:30:27:30:34 | AST only | | clang.cpp:39:42:39:47 | clang.cpp:41:18:41:19 | IR only | | dispatch.cpp:16:37:16:42 | dispatch.cpp:32:16:32:24 | IR only | diff --git a/cpp/ql/test/library-tests/dataflow/dataflow-tests/test_ir.expected b/cpp/ql/test/library-tests/dataflow/dataflow-tests/test_ir.expected index 0e67184f477..bb20ccfc9d7 100644 --- a/cpp/ql/test/library-tests/dataflow/dataflow-tests/test_ir.expected +++ b/cpp/ql/test/library-tests/dataflow/dataflow-tests/test_ir.expected @@ -5,10 +5,13 @@ | BarrierGuard.cpp:33:10:33:15 | source | BarrierGuard.cpp:29:16:29:21 | source | | BarrierGuard.cpp:53:13:53:13 | x | BarrierGuard.cpp:49:10:49:15 | call to source | | BarrierGuard.cpp:55:13:55:13 | x | BarrierGuard.cpp:49:10:49:15 | call to source | +| BarrierGuard.cpp:64:14:64:14 | x | BarrierGuard.cpp:60:11:60:16 | call to source | +| BarrierGuard.cpp:66:14:66:14 | x | BarrierGuard.cpp:60:11:60:16 | call to source | | acrossLinkTargets.cpp:12:8:12:8 | (int)... | acrossLinkTargets.cpp:19:27:19:32 | call to source | | acrossLinkTargets.cpp:12:8:12:8 | x | acrossLinkTargets.cpp:19:27:19:32 | call to source | | clang.cpp:18:8:18:19 | (const int *)... | clang.cpp:12:9:12:20 | sourceArray1 | | clang.cpp:18:8:18:19 | sourceArray1 | clang.cpp:12:9:12:20 | sourceArray1 | +| clang.cpp:29:27:29:28 | m1 | clang.cpp:28:27:28:32 | call to source | | clang.cpp:37:10:37:11 | m2 | clang.cpp:34:32:34:37 | call to source | | clang.cpp:41:18:41:19 | m2 | clang.cpp:39:42:39:47 | call to source | | clang.cpp:45:17:45:18 | m2 | clang.cpp:43:35:43:40 | call to source | diff --git a/cpp/ql/test/library-tests/dataflow/security-taint/tainted_diff.expected b/cpp/ql/test/library-tests/dataflow/security-taint/tainted_diff.expected new file mode 100644 index 00000000000..bd82e48f8c6 --- /dev/null +++ b/cpp/ql/test/library-tests/dataflow/security-taint/tainted_diff.expected @@ -0,0 +1,17 @@ +| test.cpp:38:23:38:28 | call to getenv | test.cpp:40:6:40:33 | ! ... | IR only | +| test.cpp:38:23:38:28 | call to getenv | test.cpp:40:7:40:12 | call to strcmp | IR only | +| test.cpp:38:23:38:28 | call to getenv | test.cpp:40:7:40:33 | (bool)... | IR only | +| test.cpp:49:23:49:28 | call to getenv | test.cpp:50:15:50:24 | envStr_ptr | AST only | +| test.cpp:49:23:49:28 | call to getenv | test.cpp:50:28:50:40 | & ... | AST only | +| test.cpp:49:23:49:28 | call to getenv | test.cpp:50:29:50:40 | envStrGlobal | AST only | +| test.cpp:49:23:49:28 | call to getenv | test.cpp:52:2:52:12 | * ... | AST only | +| test.cpp:49:23:49:28 | call to getenv | test.cpp:52:3:52:12 | envStr_ptr | AST only | +| test.cpp:60:29:60:34 | call to getenv | test.cpp:64:10:64:14 | bytes | IR only | +| test.cpp:60:29:60:34 | call to getenv | test.cpp:64:18:64:23 | call to strlen | IR only | +| test.cpp:60:29:60:34 | call to getenv | test.cpp:64:18:64:37 | (int)... | IR only | +| test.cpp:60:29:60:34 | call to getenv | test.cpp:64:18:64:37 | ... + ... | IR only | +| test.cpp:68:28:68:33 | call to getenv | test.cpp:11:20:11:21 | s1 | AST only | +| test.cpp:68:28:68:33 | call to getenv | test.cpp:67:7:67:13 | copying | AST only | +| test.cpp:68:28:68:33 | call to getenv | test.cpp:69:10:69:13 | copy | AST only | +| test.cpp:68:28:68:33 | call to getenv | test.cpp:70:12:70:15 | copy | AST only | +| test.cpp:68:28:68:33 | call to getenv | test.cpp:71:12:71:15 | copy | AST only | diff --git a/cpp/ql/test/library-tests/dataflow/security-taint/tainted_diff.ql b/cpp/ql/test/library-tests/dataflow/security-taint/tainted_diff.ql new file mode 100644 index 00000000000..9a90a898d7f --- /dev/null +++ b/cpp/ql/test/library-tests/dataflow/security-taint/tainted_diff.ql @@ -0,0 +1,16 @@ +import semmle.code.cpp.security.TaintTracking as AST +import semmle.code.cpp.ir.dataflow.DefaultTaintTracking as IR +import cpp + +from Expr source, Element tainted, string side +where + AST::taintedIncludingGlobalVars(source, tainted, _) and + not IR::taintedIncludingGlobalVars(source, tainted, _) and + not tainted.getLocation().getFile().getExtension() = "h" and + side = "AST only" + or + IR::taintedIncludingGlobalVars(source, tainted, _) and + not AST::taintedIncludingGlobalVars(source, tainted, _) and + not tainted.getLocation().getFile().getExtension() = "h" and + side = "IR only" +select source, tainted, side diff --git a/cpp/ql/test/library-tests/dataflow/security-taint/tainted_ir.expected b/cpp/ql/test/library-tests/dataflow/security-taint/tainted_ir.expected new file mode 100644 index 00000000000..216d583d925 --- /dev/null +++ b/cpp/ql/test/library-tests/dataflow/security-taint/tainted_ir.expected @@ -0,0 +1,49 @@ +| test.cpp:23:23:23:28 | call to getenv | test.cpp:8:24:8:25 | s1 | | +| test.cpp:23:23:23:28 | call to getenv | test.cpp:23:14:23:19 | envStr | | +| test.cpp:23:23:23:28 | call to getenv | test.cpp:23:23:23:28 | call to getenv | | +| test.cpp:23:23:23:28 | call to getenv | test.cpp:23:23:23:40 | (const char *)... | | +| test.cpp:23:23:23:28 | call to getenv | test.cpp:25:6:25:29 | ! ... | | +| test.cpp:23:23:23:28 | call to getenv | test.cpp:25:7:25:12 | call to strcmp | | +| test.cpp:23:23:23:28 | call to getenv | test.cpp:25:7:25:29 | (bool)... | | +| test.cpp:23:23:23:28 | call to getenv | test.cpp:25:14:25:19 | envStr | | +| test.cpp:23:23:23:28 | call to getenv | test.cpp:29:6:29:28 | ! ... | | +| test.cpp:23:23:23:28 | call to getenv | test.cpp:29:7:29:12 | call to strcmp | | +| test.cpp:23:23:23:28 | call to getenv | test.cpp:29:7:29:28 | (bool)... | | +| test.cpp:23:23:23:28 | call to getenv | test.cpp:29:14:29:19 | envStr | | +| test.cpp:38:23:38:28 | call to getenv | test.cpp:8:24:8:25 | s1 | | +| test.cpp:38:23:38:28 | call to getenv | test.cpp:38:14:38:19 | envStr | | +| test.cpp:38:23:38:28 | call to getenv | test.cpp:38:23:38:28 | call to getenv | | +| test.cpp:38:23:38:28 | call to getenv | test.cpp:38:23:38:40 | (const char *)... | | +| test.cpp:38:23:38:28 | call to getenv | test.cpp:40:6:40:33 | ! ... | | +| test.cpp:38:23:38:28 | call to getenv | test.cpp:40:7:40:12 | call to strcmp | | +| test.cpp:38:23:38:28 | call to getenv | test.cpp:40:7:40:33 | (bool)... | | +| test.cpp:38:23:38:28 | call to getenv | test.cpp:40:14:40:19 | envStr | | +| test.cpp:49:23:49:28 | call to getenv | test.cpp:8:24:8:25 | s1 | | +| test.cpp:49:23:49:28 | call to getenv | test.cpp:45:13:45:24 | envStrGlobal | | +| test.cpp:49:23:49:28 | call to getenv | test.cpp:49:14:49:19 | envStr | | +| test.cpp:49:23:49:28 | call to getenv | test.cpp:49:23:49:28 | call to getenv | | +| test.cpp:49:23:49:28 | call to getenv | test.cpp:49:23:49:40 | (const char *)... | | +| test.cpp:49:23:49:28 | call to getenv | test.cpp:52:16:52:21 | envStr | | +| test.cpp:49:23:49:28 | call to getenv | test.cpp:54:6:54:35 | ! ... | | +| test.cpp:49:23:49:28 | call to getenv | test.cpp:54:7:54:12 | call to strcmp | | +| test.cpp:49:23:49:28 | call to getenv | test.cpp:54:7:54:35 | (bool)... | | +| test.cpp:49:23:49:28 | call to getenv | test.cpp:54:14:54:25 | envStrGlobal | | +| test.cpp:60:29:60:34 | call to getenv | test.cpp:10:27:10:27 | s | | +| test.cpp:60:29:60:34 | call to getenv | test.cpp:60:18:60:25 | userName | | +| test.cpp:60:29:60:34 | call to getenv | test.cpp:60:29:60:34 | call to getenv | | +| test.cpp:60:29:60:34 | call to getenv | test.cpp:60:29:60:47 | (const char *)... | | +| test.cpp:60:29:60:34 | call to getenv | test.cpp:64:10:64:14 | bytes | | +| test.cpp:60:29:60:34 | call to getenv | test.cpp:64:18:64:23 | call to strlen | | +| test.cpp:60:29:60:34 | call to getenv | test.cpp:64:18:64:37 | (int)... | | +| test.cpp:60:29:60:34 | call to getenv | test.cpp:64:18:64:37 | ... + ... | | +| test.cpp:60:29:60:34 | call to getenv | test.cpp:64:25:64:32 | userName | | +| test.cpp:68:28:68:33 | call to getenv | test.cpp:11:36:11:37 | s2 | | +| test.cpp:68:28:68:33 | call to getenv | test.cpp:68:17:68:24 | userName | | +| test.cpp:68:28:68:33 | call to getenv | test.cpp:68:28:68:33 | call to getenv | | +| test.cpp:68:28:68:33 | call to getenv | test.cpp:68:28:68:46 | (const char *)... | | +| test.cpp:68:28:68:33 | call to getenv | test.cpp:70:5:70:10 | call to strcpy | | +| test.cpp:68:28:68:33 | call to getenv | test.cpp:70:18:70:25 | userName | | +| test.cpp:75:20:75:25 | call to getenv | test.cpp:15:22:15:25 | nptr | | +| test.cpp:75:20:75:25 | call to getenv | test.cpp:75:15:75:18 | call to atoi | | +| test.cpp:75:20:75:25 | call to getenv | test.cpp:75:20:75:25 | call to getenv | | +| test.cpp:75:20:75:25 | call to getenv | test.cpp:75:20:75:45 | (const char *)... | | diff --git a/cpp/ql/test/library-tests/dataflow/security-taint/tainted_ir.ql b/cpp/ql/test/library-tests/dataflow/security-taint/tainted_ir.ql new file mode 100644 index 00000000000..6d8effe7ffe --- /dev/null +++ b/cpp/ql/test/library-tests/dataflow/security-taint/tainted_ir.ql @@ -0,0 +1,7 @@ +import semmle.code.cpp.ir.dataflow.DefaultTaintTracking + +from Expr source, Element tainted, string globalVar +where + taintedIncludingGlobalVars(source, tainted, globalVar) and + not tainted.getLocation().getFile().getExtension() = "h" +select source, tainted, globalVar diff --git a/cpp/ql/test/library-tests/dataflow/taint-tests/test_diff.expected b/cpp/ql/test/library-tests/dataflow/taint-tests/test_diff.expected index 659ea724637..7680193da16 100644 --- a/cpp/ql/test/library-tests/dataflow/taint-tests/test_diff.expected +++ b/cpp/ql/test/library-tests/dataflow/taint-tests/test_diff.expected @@ -34,7 +34,6 @@ | taint.cpp:352:7:352:7 | taint.cpp:330:6:330:11 | AST only | | taint.cpp:372:7:372:7 | taint.cpp:365:24:365:29 | AST only | | taint.cpp:374:7:374:7 | taint.cpp:365:24:365:29 | AST only | -| taint.cpp:382:7:382:7 | taint.cpp:377:23:377:28 | AST only | | taint.cpp:391:7:391:7 | taint.cpp:385:27:385:32 | AST only | | taint.cpp:423:7:423:7 | taint.cpp:422:14:422:19 | AST only | | taint.cpp:424:9:424:17 | taint.cpp:422:14:422:19 | AST only | diff --git a/cpp/ql/test/library-tests/dataflow/taint-tests/test_ir.expected b/cpp/ql/test/library-tests/dataflow/taint-tests/test_ir.expected index 06010367fd7..b38de345220 100644 --- a/cpp/ql/test/library-tests/dataflow/taint-tests/test_ir.expected +++ b/cpp/ql/test/library-tests/dataflow/taint-tests/test_ir.expected @@ -17,5 +17,6 @@ | taint.cpp:291:7:291:7 | y | taint.cpp:275:6:275:11 | call to source | | taint.cpp:337:7:337:7 | t | taint.cpp:330:6:330:11 | call to source | | taint.cpp:350:7:350:7 | t | taint.cpp:330:6:330:11 | call to source | +| taint.cpp:382:7:382:7 | a | taint.cpp:377:23:377:28 | source | | taint.cpp:429:7:429:7 | b | taint.cpp:428:13:428:18 | call to source | | taint.cpp:430:9:430:14 | member | taint.cpp:428:13:428:18 | call to source | diff --git a/cpp/ql/test/library-tests/ir/escape/escape.ql b/cpp/ql/test/library-tests/ir/escape/escape.ql index 109ff260b7d..9099fea159e 100644 --- a/cpp/ql/test/library-tests/ir/escape/escape.ql +++ b/cpp/ql/test/library-tests/ir/escape/escape.ql @@ -16,9 +16,9 @@ where exists(IRFunction irFunc | irFunc = var.getEnclosingIRFunction() and ( - shouldEscape(var) and variableAddressEscapes(var) + shouldEscape(var) and allocationEscapes(var) or - not shouldEscape(var) and not variableAddressEscapes(var) + not shouldEscape(var) and not allocationEscapes(var) ) ) select var diff --git a/cpp/ql/test/library-tests/ir/escape/points_to.expected b/cpp/ql/test/library-tests/ir/escape/points_to.expected deleted file mode 100644 index 6447bd81a5f..00000000000 --- a/cpp/ql/test/library-tests/ir/escape/points_to.expected +++ /dev/null @@ -1,86 +0,0 @@ -| escape.cpp:111:18:111:21 | CopyValue | no_+0 | no_+0 | -| escape.cpp:115:19:115:28 | PointerAdd[4] | no_+0 | no_+0 | -| escape.cpp:115:20:115:23 | CopyValue | no_+0 | no_+0 | -| escape.cpp:116:19:116:28 | PointerSub[4] | no_+0 | no_+0 | -| escape.cpp:116:20:116:23 | CopyValue | no_+0 | no_+0 | -| escape.cpp:117:19:117:26 | PointerAdd[4] | no_+0 | no_+0 | -| escape.cpp:117:23:117:26 | CopyValue | no_+0 | no_+0 | -| escape.cpp:118:9:118:12 | CopyValue | no_+0 | no_+0 | -| escape.cpp:120:12:120:15 | CopyValue | no_+0 | no_+0 | -| escape.cpp:123:14:123:17 | CopyValue | no_+0 | no_+0 | -| escape.cpp:124:15:124:18 | CopyValue | no_+0 | no_+0 | -| escape.cpp:127:9:127:12 | CopyValue | no_+0 | no_+0 | -| escape.cpp:129:12:129:15 | CopyValue | no_+0 | no_+0 | -| escape.cpp:134:5:134:18 | Convert | no_Array+0 | no_Array+0 | -| escape.cpp:134:11:134:18 | Convert | no_Array+0 | no_Array+0 | -| escape.cpp:135:5:135:12 | Convert | no_Array+0 | no_Array+0 | -| escape.cpp:135:5:135:15 | PointerAdd[4] | no_Array+20 | no_Array+20 | -| escape.cpp:136:5:136:15 | PointerAdd[4] | no_Array+20 | no_Array+20 | -| escape.cpp:136:7:136:14 | Convert | no_Array+0 | no_Array+0 | -| escape.cpp:137:17:137:24 | Convert | no_Array+0 | no_Array+0 | -| escape.cpp:137:17:137:27 | PointerAdd[4] | no_Array+20 | no_Array+20 | -| escape.cpp:138:17:138:27 | PointerAdd[4] | no_Array+20 | no_Array+20 | -| escape.cpp:138:19:138:26 | Convert | no_Array+0 | no_Array+0 | -| escape.cpp:140:21:140:32 | FieldAddress[x] | no_Point+0 | no_Point+0 | -| escape.cpp:140:21:140:32 | FieldAddress[y] | no_Point+4 | no_Point+4 | -| escape.cpp:140:21:140:32 | FieldAddress[z] | no_Point+8 | no_Point+8 | -| escape.cpp:141:27:141:27 | FieldAddress[x] | no_Point+0 | no_Point+0 | -| escape.cpp:142:14:142:14 | FieldAddress[y] | no_Point+4 | no_Point+4 | -| escape.cpp:143:19:143:27 | CopyValue | no_Point+0 | no_Point+0 | -| escape.cpp:143:31:143:31 | FieldAddress[y] | no_Point+4 | no_Point+4 | -| escape.cpp:144:6:144:14 | CopyValue | no_Point+0 | no_Point+0 | -| escape.cpp:144:18:144:18 | FieldAddress[y] | no_Point+4 | no_Point+4 | -| escape.cpp:145:20:145:30 | CopyValue | no_Point+8 | no_Point+8 | -| escape.cpp:145:30:145:30 | FieldAddress[z] | no_Point+8 | no_Point+8 | -| escape.cpp:146:5:146:18 | CopyValue | no_Point+8 | no_Point+8 | -| escape.cpp:146:7:146:17 | CopyValue | no_Point+8 | no_Point+8 | -| escape.cpp:146:17:146:17 | FieldAddress[z] | no_Point+8 | no_Point+8 | -| escape.cpp:149:5:149:14 | ConvertToNonVirtualBase[Derived : Intermediate1] | no_Derived+0 | no_Derived+0 | -| escape.cpp:149:5:149:14 | ConvertToNonVirtualBase[Intermediate1 : Base] | no_Derived+0 | no_Derived+0 | -| escape.cpp:149:16:149:16 | FieldAddress[b] | no_Derived+0 | no_Derived+0 | -| escape.cpp:150:18:150:27 | ConvertToNonVirtualBase[Derived : Intermediate1] | no_Derived+0 | no_Derived+0 | -| escape.cpp:150:18:150:27 | ConvertToNonVirtualBase[Intermediate1 : Base] | no_Derived+0 | no_Derived+0 | -| escape.cpp:150:29:150:29 | FieldAddress[b] | no_Derived+0 | no_Derived+0 | -| escape.cpp:151:5:151:14 | ConvertToNonVirtualBase[Derived : Intermediate2] | no_Derived+12 | no_Derived+12 | -| escape.cpp:151:16:151:17 | FieldAddress[i2] | no_Derived+16 | no_Derived+16 | -| escape.cpp:152:19:152:28 | ConvertToNonVirtualBase[Derived : Intermediate2] | no_Derived+12 | no_Derived+12 | -| escape.cpp:152:30:152:31 | FieldAddress[i2] | no_Derived+16 | no_Derived+16 | -| escape.cpp:155:17:155:30 | CopyValue | no_ssa_addrOf+0 | no_ssa_addrOf+0 | -| escape.cpp:155:17:155:30 | Store | no_ssa_addrOf+0 | no_ssa_addrOf+0 | -| escape.cpp:158:17:158:28 | CopyValue | no_ssa_refTo+0 | no_ssa_refTo+0 | -| escape.cpp:158:17:158:28 | Store | no_ssa_refTo+0 | no_ssa_refTo+0 | -| escape.cpp:161:19:161:42 | Convert | no_ssa_refToArrayElement+0 | no_ssa_refToArrayElement+0 | -| escape.cpp:161:19:161:45 | CopyValue | no_ssa_refToArrayElement+20 | no_ssa_refToArrayElement+20 | -| escape.cpp:161:19:161:45 | PointerAdd[4] | no_ssa_refToArrayElement+20 | no_ssa_refToArrayElement+20 | -| escape.cpp:161:19:161:45 | Store | no_ssa_refToArrayElement+20 | no_ssa_refToArrayElement+20 | -| escape.cpp:164:24:164:40 | CopyValue | no_ssa_refToArray+0 | no_ssa_refToArray+0 | -| escape.cpp:164:24:164:40 | Store | no_ssa_refToArray+0 | no_ssa_refToArray+0 | -| escape.cpp:167:19:167:28 | CopyValue | passByPtr+0 | passByPtr+0 | -| escape.cpp:170:21:170:29 | CopyValue | passByRef+0 | passByRef+0 | -| escape.cpp:173:22:173:38 | CopyValue | no_ssa_passByPtr+0 | no_ssa_passByPtr+0 | -| escape.cpp:176:24:176:39 | CopyValue | no_ssa_passByRef+0 | no_ssa_passByRef+0 | -| escape.cpp:179:22:179:42 | CopyValue | no_ssa_passByPtr_ret+0 | no_ssa_passByPtr_ret+0 | -| escape.cpp:182:24:182:43 | CopyValue | no_ssa_passByRef_ret+0 | no_ssa_passByRef_ret+0 | -| escape.cpp:185:30:185:40 | CopyValue | passByPtr2+0 | passByPtr2+0 | -| escape.cpp:188:32:188:41 | CopyValue | passByRef2+0 | passByRef2+0 | -| escape.cpp:191:30:191:42 | Call | none | passByPtr3+0 | -| escape.cpp:191:44:191:54 | CopyValue | passByPtr3+0 | passByPtr3+0 | -| escape.cpp:194:32:194:46 | Call | none | passByRef3+0 | -| escape.cpp:194:32:194:59 | CopyValue | none | passByRef3+0 | -| escape.cpp:194:48:194:57 | CopyValue | passByRef3+0 | passByRef3+0 | -| escape.cpp:199:17:199:34 | CopyValue | no_ssa_passByPtr4+0 | no_ssa_passByPtr4+0 | -| escape.cpp:199:37:199:54 | CopyValue | no_ssa_passByPtr5+0 | no_ssa_passByPtr5+0 | -| escape.cpp:202:5:202:19 | Call | none | passByRef6+0 | -| escape.cpp:202:5:202:32 | CopyValue | none | passByRef6+0 | -| escape.cpp:202:21:202:30 | CopyValue | passByRef6+0 | passByRef6+0 | -| escape.cpp:205:5:205:19 | Call | none | no_ssa_passByRef7+0 | -| escape.cpp:205:5:205:39 | CopyValue | none | no_ssa_passByRef7+0 | -| escape.cpp:205:21:205:37 | CopyValue | no_ssa_passByRef7+0 | no_ssa_passByRef7+0 | -| escape.cpp:209:14:209:25 | Call | none | no_ssa_c+0 | -| escape.cpp:217:14:217:16 | CopyValue | c2+0 | c2+0 | -| escape.cpp:221:8:221:19 | Call | none | c3+0 | -| escape.cpp:225:17:225:28 | Call | none | c4+0 | -| escape.cpp:247:2:247:27 | Store | condEscape1+0 | condEscape1+0 | -| escape.cpp:247:16:247:27 | CopyValue | condEscape1+0 | condEscape1+0 | -| escape.cpp:249:9:249:34 | Store | condEscape2+0 | condEscape2+0 | -| escape.cpp:249:23:249:34 | CopyValue | condEscape2+0 | condEscape2+0 | diff --git a/cpp/ql/test/library-tests/ir/escape/points_to.ql b/cpp/ql/test/library-tests/ir/escape/points_to.ql deleted file mode 100644 index 7c265974b10..00000000000 --- a/cpp/ql/test/library-tests/ir/escape/points_to.ql +++ /dev/null @@ -1,35 +0,0 @@ -import default -import semmle.code.cpp.ir.implementation.unaliased_ssa.internal.AliasAnalysis as RawAA -import semmle.code.cpp.ir.implementation.raw.IR as Raw -import semmle.code.cpp.ir.implementation.aliased_ssa.internal.AliasAnalysis as UnAA -import semmle.code.cpp.ir.implementation.unaliased_ssa.IR as Un -import semmle.code.cpp.ir.implementation.unaliased_ssa.internal.SSAConstruction -import semmle.code.cpp.ir.internal.IntegerConstant - -from Raw::Instruction rawInstr, Un::Instruction unInstr, string rawPointsTo, string unPointsTo -where - rawInstr = getOldInstruction(unInstr) and - not rawInstr instanceof Raw::VariableAddressInstruction and - ( - exists(Variable var, int rawBitOffset, int unBitOffset | - RawAA::resultPointsTo(rawInstr, Raw::getIRUserVariable(_, var), rawBitOffset) and - rawPointsTo = var.toString() + getBitOffsetString(rawBitOffset) and - UnAA::resultPointsTo(unInstr, Un::getIRUserVariable(_, var), unBitOffset) and - unPointsTo = var.toString() + getBitOffsetString(unBitOffset) - ) - or - exists(Variable var, int unBitOffset | - not RawAA::resultPointsTo(rawInstr, Raw::getIRUserVariable(_, var), _) and - rawPointsTo = "none" and - UnAA::resultPointsTo(unInstr, Un::getIRUserVariable(_, var), unBitOffset) and - unPointsTo = var.toString() + getBitOffsetString(unBitOffset) - ) - or - exists(Variable var, int rawBitOffset | - RawAA::resultPointsTo(rawInstr, Raw::getIRUserVariable(_, var), rawBitOffset) and - rawPointsTo = var.toString() + getBitOffsetString(rawBitOffset) and - not UnAA::resultPointsTo(unInstr, Un::getIRUserVariable(_, var), _) and - unPointsTo = "none" - ) - ) -select rawInstr.getLocation().toString(), rawInstr.getOperationString(), rawPointsTo, unPointsTo diff --git a/cpp/ql/test/library-tests/ir/escape/ssa_escape.ql b/cpp/ql/test/library-tests/ir/escape/ssa_escape.ql index 8025a455fc9..e97cea7670d 100644 --- a/cpp/ql/test/library-tests/ir/escape/ssa_escape.ql +++ b/cpp/ql/test/library-tests/ir/escape/ssa_escape.ql @@ -1,23 +1,25 @@ import default import semmle.code.cpp.ir.implementation.aliased_ssa.internal.AliasAnalysis +import semmle.code.cpp.ir.implementation.aliased_ssa.internal.AliasConfiguration import semmle.code.cpp.ir.implementation.unaliased_ssa.IR import semmle.code.cpp.ir.implementation.UseSoundEscapeAnalysis -predicate shouldEscape(IRAutomaticUserVariable var) { - exists(string name | - name = var.getVariable().getName() and - name.matches("no_%") - ) +class InterestingAllocation extends VariableAllocation { + IRUserVariable userVar; + + InterestingAllocation() { userVar = this.getIRVariable() } + + final predicate shouldEscape() { userVar.getVariable().getName().matches("no_%") } } -from IRAutomaticUserVariable var +from InterestingAllocation var where exists(IRFunction irFunc | irFunc = var.getEnclosingIRFunction() and ( - shouldEscape(var) and variableAddressEscapes(var) + var.shouldEscape() and allocationEscapes(var) or - not shouldEscape(var) and not variableAddressEscapes(var) + not var.shouldEscape() and not allocationEscapes(var) ) ) select var diff --git a/cpp/ql/test/library-tests/ir/ir/PrintAST.expected b/cpp/ql/test/library-tests/ir/ir/PrintAST.expected index 7778ebb76c0..343ffc27db5 100644 --- a/cpp/ql/test/library-tests/ir/ir/PrintAST.expected +++ b/cpp/ql/test/library-tests/ir/ir/PrintAST.expected @@ -5753,9 +5753,9 @@ ir.cpp: # 851| 0: [VariableDeclarationEntry] definition of d # 851| Type = [Struct] PolymorphicDerived # 851| init: [Initializer] initializer for d -# 851| expr: [ConstructorCall] call to PolymorphicDerived -# 851| Type = [VoidType] void -# 851| ValueCategory = prvalue +#-----| expr: [ConstructorCall] call to PolymorphicDerived +#-----| Type = [VoidType] void +#-----| ValueCategory = prvalue # 853| 2: [DeclStmt] declaration # 853| 0: [VariableDeclarationEntry] definition of pb # 853| Type = [PointerType] PolymorphicBase * diff --git a/cpp/ql/test/library-tests/ir/ir/raw_ir.expected b/cpp/ql/test/library-tests/ir/ir/raw_ir.expected index 198be88f971..3f3bfb07400 100644 --- a/cpp/ql/test/library-tests/ir/ir/raw_ir.expected +++ b/cpp/ql/test/library-tests/ir/ir/raw_ir.expected @@ -4223,10 +4223,10 @@ ir.cpp: #-----| mu0_4(PolymorphicBase) = ^IndirectMayWriteSideEffect[-1] : &:r850_1 # 851| r851_1(glval) = VariableAddress[d] : # 851| mu851_2(PolymorphicDerived) = Uninitialized[d] : &:r851_1 -# 851| r851_3(glval) = FunctionAddress[PolymorphicDerived] : -# 851| v851_4(void) = Call : func:r851_3, this:r851_1 -# 851| mu851_5(unknown) = ^CallSideEffect : ~mu849_3 -# 851| mu851_6(PolymorphicDerived) = ^IndirectMayWriteSideEffect[-1] : &:r851_1 +#-----| r0_5(glval) = FunctionAddress[PolymorphicDerived] : +#-----| v0_6(void) = Call : func:r0_5, this:r851_1 +#-----| mu0_7(unknown) = ^CallSideEffect : ~mu849_3 +#-----| mu0_8(PolymorphicDerived) = ^IndirectMayWriteSideEffect[-1] : &:r851_1 # 853| r853_1(glval) = VariableAddress[pb] : # 853| r853_2(glval) = VariableAddress[b] : # 853| r853_3(PolymorphicBase *) = CopyValue : r853_2 diff --git a/cpp/ql/test/library-tests/ir/points_to/points_to.cpp b/cpp/ql/test/library-tests/ir/points_to/points_to.cpp new file mode 100644 index 00000000000..249f08352ab --- /dev/null +++ b/cpp/ql/test/library-tests/ir/points_to/points_to.cpp @@ -0,0 +1,87 @@ +struct Point { + int x; + int y; +}; + +struct Base1 { + int b1; +}; + +struct Base2 { + int b2; +}; + +struct DerivedSI : Base1 { + int dsi; +}; + +struct DerivedMI : Base1, Base2 { + int dmi; +}; + +struct DerivedVI : virtual Base1 { + int dvi; +}; + +void Locals() { + Point pt = { //$ussa=pt + 1, //$ussa=pt[0..4) + 2 //$ussa=pt[4..8) + }; + int i = pt.x; //$ussa=pt[0..4) + i = pt.y; //$ussa=pt[4..8) + int* p = &pt.x; + i = *p; //$ussa=pt[0..4) + p = &pt.y; + i = *p; //$ussa=pt[4..8) +} + +void PointsTo( + int a, //$raw,ussa=a + Point& b, //$raw,ussa=b $ussa=*b + Point* c, //$raw,ussa=c $ussa=*c + int* d, //$raw,ussa=d $ussa=*d + DerivedSI* e, //$raw,ussa=e $ussa=*e + DerivedMI* f, //$raw,ussa=f $ussa=*f + DerivedVI* g //$raw,ussa=g $ussa=*g +) { + + int i = a; //$raw,ussa=a + i = *&a; //$raw,ussa=a + i = *(&a + 0); //$raw,ussa=a + i = b.x; //$raw,ussa=b $ussa=*b[0..4) + i = b.y; //$raw,ussa=b $ussa=*b[4..8) + i = c->x; //$raw,ussa=c $ussa=*c[0..4) + i = c->y; //$raw,ussa=c $ussa=*c[4..8) + i = *d; //$raw,ussa=d $ussa=*d[0..4) + i = *(d + 0); //$raw,ussa=d $ussa=*d[0..4) + i = d[5]; //$raw,ussa=d $ussa=*d[20..24) + i = 5[d]; //$raw,ussa=d $ussa=*d[20..24) + i = d[a]; //$raw,ussa=d $raw,ussa=a $ussa=*d[?..?) + i = a[d]; //$raw,ussa=d $raw,ussa=a $ussa=*d[?..?) + + int* p = &b.x; //$raw,ussa=b + i = *p; //$ussa=*b[0..4) + p = &b.y; //$raw,ussa=b + i = *p; //$ussa=*b[4..8) + p = &c->x; //$raw,ussa=c + i = *p; //$ussa=*c[0..4) + p = &c->y; //$raw,ussa=c + i = *p; //$ussa=*c[4..8) + p = &d[5]; //$raw,ussa=d + i = *p; //$ussa=*d[20..24) + p = &d[a]; //$raw,ussa=d $raw,ussa=a + i = *p; //$ussa=*d[?..?) + + Point* q = &c[a]; //$raw,ussa=c $raw,ussa=a + i = q->x; //$ussa=*c[?..?) + i = q->y; //$ussa=*c[?..?) + + i = e->b1; //$raw,ussa=e $ussa=*e[0..4) + i = e->dsi; //$raw,ussa=e $ussa=*e[4..8) + i = f->b1; //$raw,ussa=f $ussa=*f[0..4) + i = f->b2; //$raw,ussa=f $ussa=*f[4..8) + i = f->dmi; //$raw,ussa=f $ussa=*f[8..12) + i = g->b1; //$raw,ussa=g $ussa=*g[?..?) + i = g->dvi; //$raw,ussa=g $ussa=*g[8..12) +} \ No newline at end of file diff --git a/cpp/ql/test/library-tests/ir/points_to/points_to.expected b/cpp/ql/test/library-tests/ir/points_to/points_to.expected new file mode 100644 index 00000000000..e69de29bb2d diff --git a/cpp/ql/test/library-tests/ir/points_to/points_to.ql b/cpp/ql/test/library-tests/ir/points_to/points_to.ql new file mode 100644 index 00000000000..89d1e31e119 --- /dev/null +++ b/cpp/ql/test/library-tests/ir/points_to/points_to.ql @@ -0,0 +1,65 @@ +import cpp +private import TestUtilities.InlineExpectationsTest +private import semmle.code.cpp.ir.internal.IntegerConstant as Ints + +private predicate ignoreAllocation(string name) { + name = "i" or + name = "p" or + name = "q" +} + +module Raw { + private import semmle.code.cpp.ir.implementation.raw.IR + private import semmle.code.cpp.ir.implementation.unaliased_ssa.internal.SimpleSSA + + private MemoryLocation getAMemoryAccess(Instruction instr) { + result = getResultMemoryLocation(instr) or + result = getOperandMemoryLocation(instr.getAnOperand()) + } + + class RawPointsToTest extends InlineExpectationsTest { + RawPointsToTest() { this = "RawPointsToTest" } + + override string getARelevantTag() { result = "raw" } + + override predicate hasActualResult(Location location, string element, string tag, string value) { + exists(Instruction instr, MemoryLocation memLocation | + memLocation = getAMemoryAccess(instr) and + tag = "raw" and + not ignoreAllocation(memLocation.getAllocation().getAllocationString()) and + value = memLocation.toString() and + element = instr.toString() and + location = instr.getLocation() + ) + } + } +} + +module UnaliasedSSA { + private import semmle.code.cpp.ir.implementation.unaliased_ssa.IR + private import semmle.code.cpp.ir.implementation.aliased_ssa.internal.AliasedSSA + + private MemoryLocation getAMemoryAccess(Instruction instr) { + result = getResultMemoryLocation(instr) or + result = getOperandMemoryLocation(instr.getAnOperand()) + } + + class UnaliasedSSAPointsToTest extends InlineExpectationsTest { + UnaliasedSSAPointsToTest() { this = "UnaliasedSSAPointsToTest" } + + override string getARelevantTag() { result = "ussa" } + + override predicate hasActualResult(Location location, string element, string tag, string value) { + exists(Instruction instr, MemoryLocation memLocation | + memLocation = getAMemoryAccess(instr) and + not memLocation instanceof AliasedVirtualVariable and + not memLocation instanceof AllNonLocalMemory and + tag = "ussa" and + not ignoreAllocation(memLocation.getAllocation().getAllocationString()) and + value = memLocation.toString() and + element = instr.toString() and + location = instr.getLocation() + ) + } + } +} diff --git a/cpp/ql/test/library-tests/ir/ssa/aliased_ssa_ir.expected b/cpp/ql/test/library-tests/ir/ssa/aliased_ssa_ir.expected index cea641f769c..66596969333 100644 --- a/cpp/ql/test/library-tests/ir/ssa/aliased_ssa_ir.expected +++ b/cpp/ql/test/library-tests/ir/ssa/aliased_ssa_ir.expected @@ -8,13 +8,12 @@ ssa.cpp: # 13| m13_5(Point *) = InitializeParameter[p] : &:r13_4 # 13| r13_6(Point *) = Load : &:r13_4, m13_5 # 13| m13_7(unknown) = InitializeIndirection[p] : &:r13_6 -# 13| m13_8(unknown) = Chi : total:m13_2, partial:m13_7 -# 13| r13_9(glval) = VariableAddress[which1] : -# 13| m13_10(bool) = InitializeParameter[which1] : &:r13_9 -# 13| r13_11(glval) = VariableAddress[which2] : -# 13| m13_12(bool) = InitializeParameter[which2] : &:r13_11 +# 13| r13_8(glval) = VariableAddress[which1] : +# 13| m13_9(bool) = InitializeParameter[which1] : &:r13_8 +# 13| r13_10(glval) = VariableAddress[which2] : +# 13| m13_11(bool) = InitializeParameter[which2] : &:r13_10 # 14| r14_1(glval) = VariableAddress[which1] : -# 14| r14_2(bool) = Load : &:r14_1, m13_10 +# 14| r14_2(bool) = Load : &:r14_1, m13_9 # 14| v14_3(void) = ConditionalBranch : r14_2 #-----| False -> Block 2 #-----| True -> Block 1 @@ -23,29 +22,31 @@ ssa.cpp: # 15| r15_1(glval) = VariableAddress[p] : # 15| r15_2(Point *) = Load : &:r15_1, m13_5 # 15| r15_3(glval) = FieldAddress[x] : r15_2 -# 15| r15_4(int) = Load : &:r15_3, ~m13_8 +# 15| r15_4(int) = Load : &:r15_3, ~m13_7 # 15| r15_5(int) = Constant[1] : # 15| r15_6(int) = Add : r15_4, r15_5 # 15| m15_7(int) = Store : &:r15_3, r15_6 -# 15| m15_8(unknown) = Chi : total:m13_8, partial:m15_7 +# 15| m15_8(unknown) = Chi : total:m13_7, partial:m15_7 #-----| Goto -> Block 3 # 18| Block 2 # 18| r18_1(glval) = VariableAddress[p] : # 18| r18_2(Point *) = Load : &:r18_1, m13_5 # 18| r18_3(glval) = FieldAddress[y] : r18_2 -# 18| r18_4(int) = Load : &:r18_3, ~m13_8 +# 18| r18_4(int) = Load : &:r18_3, ~m13_7 # 18| r18_5(int) = Constant[1] : # 18| r18_6(int) = Add : r18_4, r18_5 # 18| m18_7(int) = Store : &:r18_3, r18_6 -# 18| m18_8(unknown) = Chi : total:m13_8, partial:m18_7 +# 18| m18_8(unknown) = Chi : total:m13_7, partial:m18_7 #-----| Goto -> Block 3 # 21| Block 3 -# 21| m21_1(unknown) = Phi : from 1:~m15_8, from 2:~m18_8 -# 21| r21_2(glval) = VariableAddress[which2] : -# 21| r21_3(bool) = Load : &:r21_2, m13_12 -# 21| v21_4(void) = ConditionalBranch : r21_3 +# 21| m21_1(int) = Phi : from 1:~m13_7, from 2:m18_7 +# 21| m21_2(int) = Phi : from 1:m15_7, from 2:~m13_7 +# 21| m21_3(unknown) = Phi : from 1:m15_8, from 2:m18_8 +# 21| r21_4(glval) = VariableAddress[which2] : +# 21| r21_5(bool) = Load : &:r21_4, m13_11 +# 21| v21_6(void) = ConditionalBranch : r21_5 #-----| False -> Block 5 #-----| True -> Block 4 @@ -53,43 +54,45 @@ ssa.cpp: # 22| r22_1(glval) = VariableAddress[p] : # 22| r22_2(Point *) = Load : &:r22_1, m13_5 # 22| r22_3(glval) = FieldAddress[x] : r22_2 -# 22| r22_4(int) = Load : &:r22_3, ~m21_1 +# 22| r22_4(int) = Load : &:r22_3, m21_2 # 22| r22_5(int) = Constant[1] : # 22| r22_6(int) = Add : r22_4, r22_5 # 22| m22_7(int) = Store : &:r22_3, r22_6 -# 22| m22_8(unknown) = Chi : total:m21_1, partial:m22_7 +# 22| m22_8(unknown) = Chi : total:m21_3, partial:m22_7 #-----| Goto -> Block 6 # 25| Block 5 # 25| r25_1(glval) = VariableAddress[p] : # 25| r25_2(Point *) = Load : &:r25_1, m13_5 # 25| r25_3(glval) = FieldAddress[y] : r25_2 -# 25| r25_4(int) = Load : &:r25_3, ~m21_1 +# 25| r25_4(int) = Load : &:r25_3, m21_1 # 25| r25_5(int) = Constant[1] : # 25| r25_6(int) = Add : r25_4, r25_5 # 25| m25_7(int) = Store : &:r25_3, r25_6 -# 25| m25_8(unknown) = Chi : total:m21_1, partial:m25_7 +# 25| m25_8(unknown) = Chi : total:m21_3, partial:m25_7 #-----| Goto -> Block 6 # 28| Block 6 -# 28| m28_1(unknown) = Phi : from 4:~m22_8, from 5:~m25_8 -# 28| r28_2(glval) = VariableAddress[#return] : -# 28| r28_3(glval) = VariableAddress[p] : -# 28| r28_4(Point *) = Load : &:r28_3, m13_5 -# 28| r28_5(glval) = FieldAddress[x] : r28_4 -# 28| r28_6(int) = Load : &:r28_5, ~m28_1 -# 28| r28_7(glval) = VariableAddress[p] : -# 28| r28_8(Point *) = Load : &:r28_7, m13_5 -# 28| r28_9(glval) = FieldAddress[y] : r28_8 -# 28| r28_10(int) = Load : &:r28_9, ~m28_1 -# 28| r28_11(int) = Add : r28_6, r28_10 -# 28| m28_12(int) = Store : &:r28_2, r28_11 -# 13| v13_13(void) = ReturnIndirection : &:r13_6, ~m28_1 -# 13| r13_14(glval) = VariableAddress[#return] : -# 13| v13_15(void) = ReturnValue : &:r13_14, m28_12 -# 13| v13_16(void) = UnmodeledUse : mu* -# 13| v13_17(void) = AliasedUse : ~m28_1 -# 13| v13_18(void) = ExitFunction : +# 28| m28_1(int) = Phi : from 4:m21_1, from 5:m25_7 +# 28| m28_2(int) = Phi : from 4:m22_7, from 5:m21_2 +# 28| m28_3(unknown) = Phi : from 4:m22_8, from 5:m25_8 +# 28| r28_4(glval) = VariableAddress[#return] : +# 28| r28_5(glval) = VariableAddress[p] : +# 28| r28_6(Point *) = Load : &:r28_5, m13_5 +# 28| r28_7(glval) = FieldAddress[x] : r28_6 +# 28| r28_8(int) = Load : &:r28_7, m28_2 +# 28| r28_9(glval) = VariableAddress[p] : +# 28| r28_10(Point *) = Load : &:r28_9, m13_5 +# 28| r28_11(glval) = FieldAddress[y] : r28_10 +# 28| r28_12(int) = Load : &:r28_11, m28_1 +# 28| r28_13(int) = Add : r28_8, r28_12 +# 28| m28_14(int) = Store : &:r28_4, r28_13 +# 13| v13_12(void) = ReturnIndirection : &:r13_6, m28_3 +# 13| r13_13(glval) = VariableAddress[#return] : +# 13| v13_14(void) = ReturnValue : &:r13_13, m28_14 +# 13| v13_15(void) = UnmodeledUse : mu* +# 13| v13_16(void) = AliasedUse : ~m13_2 +# 13| v13_17(void) = ExitFunction : # 31| int UnreachableViaGoto() # 31| Block 0 @@ -212,13 +215,12 @@ ssa.cpp: # 68| m68_7(char *) = InitializeParameter[p] : &:r68_6 # 68| r68_8(char *) = Load : &:r68_6, m68_7 # 68| m68_9(unknown) = InitializeIndirection[p] : &:r68_8 -# 68| m68_10(unknown) = Chi : total:m68_2, partial:m68_9 #-----| Goto -> Block 1 # 69| Block 1 # 69| m69_1(char *) = Phi : from 0:m68_7, from 2:m70_6 # 69| m69_2(int) = Phi : from 0:m68_5, from 2:m69_8 -# 69| m69_3(unknown) = Phi : from 0:~m68_10, from 2:~m70_10 +# 69| m69_3(unknown) = Phi : from 0:~m68_2, from 2:~m70_10 # 69| r69_4(glval) = VariableAddress[n] : # 69| r69_5(int) = Load : &:r69_4, m69_2 # 69| r69_6(int) = Constant[1] : @@ -246,11 +248,11 @@ ssa.cpp: # 71| Block 3 # 71| v71_1(void) = NoOp : -# 68| v68_11(void) = ReturnIndirection : &:r68_8, ~m69_3 -# 68| v68_12(void) = ReturnVoid : -# 68| v68_13(void) = UnmodeledUse : mu* -# 68| v68_14(void) = AliasedUse : ~m69_3 -# 68| v68_15(void) = ExitFunction : +# 68| v68_10(void) = ReturnIndirection : &:r68_8, m68_9 +# 68| v68_11(void) = ReturnVoid : +# 68| v68_12(void) = UnmodeledUse : mu* +# 68| v68_13(void) = AliasedUse : ~m69_3 +# 68| v68_14(void) = ExitFunction : # 75| void ScalarPhi(bool) # 75| Block 0 @@ -761,20 +763,19 @@ ssa.cpp: # 179| m179_5(int *) = InitializeParameter[p] : &:r179_4 # 179| r179_6(int *) = Load : &:r179_4, m179_5 # 179| m179_7(unknown) = InitializeIndirection[p] : &:r179_6 -# 179| m179_8(unknown) = Chi : total:m179_2, partial:m179_7 -# 180| m180_1(unknown) = InlineAsm : ~m179_8 -# 180| m180_2(unknown) = Chi : total:m179_8, partial:m180_1 +# 180| m180_1(unknown) = InlineAsm : ~m179_2 +# 180| m180_2(unknown) = Chi : total:m179_2, partial:m180_1 # 181| r181_1(glval) = VariableAddress[#return] : # 181| r181_2(glval) = VariableAddress[p] : # 181| r181_3(int *) = Load : &:r181_2, m179_5 -# 181| r181_4(int) = Load : &:r181_3, ~m180_2 +# 181| r181_4(int) = Load : &:r181_3, ~m179_7 # 181| m181_5(int) = Store : &:r181_1, r181_4 -# 179| v179_9(void) = ReturnIndirection : &:r179_6, ~m180_2 -# 179| r179_10(glval) = VariableAddress[#return] : -# 179| v179_11(void) = ReturnValue : &:r179_10, m181_5 -# 179| v179_12(void) = UnmodeledUse : mu* -# 179| v179_13(void) = AliasedUse : ~m180_2 -# 179| v179_14(void) = ExitFunction : +# 179| v179_8(void) = ReturnIndirection : &:r179_6, m179_7 +# 179| r179_9(glval) = VariableAddress[#return] : +# 179| v179_10(void) = ReturnValue : &:r179_9, m181_5 +# 179| v179_11(void) = UnmodeledUse : mu* +# 179| v179_12(void) = AliasedUse : ~m180_2 +# 179| v179_13(void) = ExitFunction : # 184| void AsmStmtWithOutputs(unsigned int&, unsigned int&, unsigned int&, unsigned int&) # 184| Block 0 @@ -785,45 +786,41 @@ ssa.cpp: # 184| m184_5(unsigned int &) = InitializeParameter[a] : &:r184_4 # 184| r184_6(unsigned int &) = Load : &:r184_4, m184_5 # 184| m184_7(unknown) = InitializeIndirection[a] : &:r184_6 -# 184| m184_8(unknown) = Chi : total:m184_2, partial:m184_7 -# 184| r184_9(glval) = VariableAddress[b] : -# 184| m184_10(unsigned int &) = InitializeParameter[b] : &:r184_9 -# 184| r184_11(unsigned int &) = Load : &:r184_9, m184_10 -# 184| m184_12(unknown) = InitializeIndirection[b] : &:r184_11 -# 184| m184_13(unknown) = Chi : total:m184_8, partial:m184_12 -# 184| r184_14(glval) = VariableAddress[c] : -# 184| m184_15(unsigned int &) = InitializeParameter[c] : &:r184_14 -# 184| r184_16(unsigned int &) = Load : &:r184_14, m184_15 -# 184| m184_17(unknown) = InitializeIndirection[c] : &:r184_16 -# 184| m184_18(unknown) = Chi : total:m184_13, partial:m184_17 -# 184| r184_19(glval) = VariableAddress[d] : -# 184| m184_20(unsigned int &) = InitializeParameter[d] : &:r184_19 -# 184| r184_21(unsigned int &) = Load : &:r184_19, m184_20 -# 184| m184_22(unknown) = InitializeIndirection[d] : &:r184_21 -# 184| m184_23(unknown) = Chi : total:m184_18, partial:m184_22 +# 184| r184_8(glval) = VariableAddress[b] : +# 184| m184_9(unsigned int &) = InitializeParameter[b] : &:r184_8 +# 184| r184_10(unsigned int &) = Load : &:r184_8, m184_9 +# 184| m184_11(unknown) = InitializeIndirection[b] : &:r184_10 +# 184| r184_12(glval) = VariableAddress[c] : +# 184| m184_13(unsigned int &) = InitializeParameter[c] : &:r184_12 +# 184| r184_14(unsigned int &) = Load : &:r184_12, m184_13 +# 184| m184_15(unknown) = InitializeIndirection[c] : &:r184_14 +# 184| r184_16(glval) = VariableAddress[d] : +# 184| m184_17(unsigned int &) = InitializeParameter[d] : &:r184_16 +# 184| r184_18(unsigned int &) = Load : &:r184_16, m184_17 +# 184| m184_19(unknown) = InitializeIndirection[d] : &:r184_18 # 189| r189_1(glval) = VariableAddress[a] : # 189| r189_2(unsigned int &) = Load : &:r189_1, m184_5 # 189| r189_3(glval) = CopyValue : r189_2 # 189| r189_4(glval) = VariableAddress[b] : -# 189| r189_5(unsigned int &) = Load : &:r189_4, m184_10 +# 189| r189_5(unsigned int &) = Load : &:r189_4, m184_9 # 189| r189_6(glval) = CopyValue : r189_5 # 190| r190_1(glval) = VariableAddress[c] : -# 190| r190_2(unsigned int &) = Load : &:r190_1, m184_15 -# 190| r190_3(unsigned int) = Load : &:r190_2, ~m184_23 +# 190| r190_2(unsigned int &) = Load : &:r190_1, m184_13 +# 190| r190_3(unsigned int) = Load : &:r190_2, ~m184_15 # 190| r190_4(glval) = VariableAddress[d] : -# 190| r190_5(unsigned int &) = Load : &:r190_4, m184_20 -# 190| r190_6(unsigned int) = Load : &:r190_5, ~m184_23 -# 186| m186_1(unknown) = InlineAsm : ~m184_23, 0:r189_3, 1:r189_6, 2:r190_3, 3:r190_6 -# 186| m186_2(unknown) = Chi : total:m184_23, partial:m186_1 +# 190| r190_5(unsigned int &) = Load : &:r190_4, m184_17 +# 190| r190_6(unsigned int) = Load : &:r190_5, ~m184_19 +# 186| m186_1(unknown) = InlineAsm : ~m184_11, 0:r189_3, 1:r189_6, 2:r190_3, 3:r190_6 +# 186| m186_2(unknown) = Chi : total:m184_11, partial:m186_1 # 192| v192_1(void) = NoOp : -# 184| v184_24(void) = ReturnIndirection : &:r184_6, ~m186_2 -# 184| v184_25(void) = ReturnIndirection : &:r184_11, ~m186_2 -# 184| v184_26(void) = ReturnIndirection : &:r184_16, ~m186_2 -# 184| v184_27(void) = ReturnIndirection : &:r184_21, ~m186_2 -# 184| v184_28(void) = ReturnVoid : -# 184| v184_29(void) = UnmodeledUse : mu* -# 184| v184_30(void) = AliasedUse : ~m186_2 -# 184| v184_31(void) = ExitFunction : +# 184| v184_20(void) = ReturnIndirection : &:r184_6, ~m186_2 +# 184| v184_21(void) = ReturnIndirection : &:r184_10, ~m186_2 +# 184| v184_22(void) = ReturnIndirection : &:r184_14, m184_15 +# 184| v184_23(void) = ReturnIndirection : &:r184_18, m184_19 +# 184| v184_24(void) = ReturnVoid : +# 184| v184_25(void) = UnmodeledUse : mu* +# 184| v184_26(void) = AliasedUse : ~m186_2 +# 184| v184_27(void) = ExitFunction : # 198| int PureFunctions(char*, char*, int) # 198| Block 0 @@ -834,41 +831,39 @@ ssa.cpp: # 198| m198_5(char *) = InitializeParameter[str1] : &:r198_4 # 198| r198_6(char *) = Load : &:r198_4, m198_5 # 198| m198_7(unknown) = InitializeIndirection[str1] : &:r198_6 -# 198| m198_8(unknown) = Chi : total:m198_2, partial:m198_7 -# 198| r198_9(glval) = VariableAddress[str2] : -# 198| m198_10(char *) = InitializeParameter[str2] : &:r198_9 -# 198| r198_11(char *) = Load : &:r198_9, m198_10 -# 198| m198_12(unknown) = InitializeIndirection[str2] : &:r198_11 -# 198| m198_13(unknown) = Chi : total:m198_8, partial:m198_12 -# 198| r198_14(glval) = VariableAddress[x] : -# 198| m198_15(int) = InitializeParameter[x] : &:r198_14 +# 198| r198_8(glval) = VariableAddress[str2] : +# 198| m198_9(char *) = InitializeParameter[str2] : &:r198_8 +# 198| r198_10(char *) = Load : &:r198_8, m198_9 +# 198| m198_11(unknown) = InitializeIndirection[str2] : &:r198_10 +# 198| r198_12(glval) = VariableAddress[x] : +# 198| m198_13(int) = InitializeParameter[x] : &:r198_12 # 199| r199_1(glval) = VariableAddress[ret] : # 199| r199_2(glval) = FunctionAddress[strcmp] : # 199| r199_3(glval) = VariableAddress[str1] : # 199| r199_4(char *) = Load : &:r199_3, m198_5 # 199| r199_5(char *) = Convert : r199_4 # 199| r199_6(glval) = VariableAddress[str2] : -# 199| r199_7(char *) = Load : &:r199_6, m198_10 +# 199| r199_7(char *) = Load : &:r199_6, m198_9 # 199| r199_8(char *) = Convert : r199_7 # 199| r199_9(int) = Call : func:r199_2, 0:r199_5, 1:r199_8 -# 199| v199_10(void) = ^CallReadSideEffect : ~m198_13 -# 199| v199_11(void) = ^BufferReadSideEffect[0] : &:r199_5, ~m198_13 -# 199| v199_12(void) = ^BufferReadSideEffect[1] : &:r199_8, ~m198_13 +# 199| v199_10(void) = ^CallReadSideEffect : ~m198_2 +# 199| v199_11(void) = ^BufferReadSideEffect[0] : &:r199_5, ~m198_7 +# 199| v199_12(void) = ^BufferReadSideEffect[1] : &:r199_8, ~m198_11 # 199| m199_13(int) = Store : &:r199_1, r199_9 # 200| r200_1(glval) = FunctionAddress[strlen] : # 200| r200_2(glval) = VariableAddress[str1] : # 200| r200_3(char *) = Load : &:r200_2, m198_5 # 200| r200_4(char *) = Convert : r200_3 # 200| r200_5(int) = Call : func:r200_1, 0:r200_4 -# 200| v200_6(void) = ^CallReadSideEffect : ~m198_13 -# 200| v200_7(void) = ^BufferReadSideEffect[0] : &:r200_4, ~m198_13 +# 200| v200_6(void) = ^CallReadSideEffect : ~m198_2 +# 200| v200_7(void) = ^BufferReadSideEffect[0] : &:r200_4, ~m198_7 # 200| r200_8(glval) = VariableAddress[ret] : # 200| r200_9(int) = Load : &:r200_8, m199_13 # 200| r200_10(int) = Add : r200_9, r200_5 # 200| m200_11(int) = Store : &:r200_8, r200_10 # 201| r201_1(glval) = FunctionAddress[abs] : # 201| r201_2(glval) = VariableAddress[x] : -# 201| r201_3(int) = Load : &:r201_2, m198_15 +# 201| r201_3(int) = Load : &:r201_2, m198_13 # 201| r201_4(int) = Call : func:r201_1, 0:r201_3 # 201| r201_5(glval) = VariableAddress[ret] : # 201| r201_6(int) = Load : &:r201_5, m200_11 @@ -878,13 +873,13 @@ ssa.cpp: # 202| r202_2(glval) = VariableAddress[ret] : # 202| r202_3(int) = Load : &:r202_2, m201_8 # 202| m202_4(int) = Store : &:r202_1, r202_3 -# 198| v198_16(void) = ReturnIndirection : &:r198_6, ~m198_13 -# 198| v198_17(void) = ReturnIndirection : &:r198_11, ~m198_13 -# 198| r198_18(glval) = VariableAddress[#return] : -# 198| v198_19(void) = ReturnValue : &:r198_18, m202_4 -# 198| v198_20(void) = UnmodeledUse : mu* -# 198| v198_21(void) = AliasedUse : ~m198_13 -# 198| v198_22(void) = ExitFunction : +# 198| v198_14(void) = ReturnIndirection : &:r198_6, m198_7 +# 198| v198_15(void) = ReturnIndirection : &:r198_10, m198_11 +# 198| r198_16(glval) = VariableAddress[#return] : +# 198| v198_17(void) = ReturnValue : &:r198_16, m202_4 +# 198| v198_18(void) = UnmodeledUse : mu* +# 198| v198_19(void) = AliasedUse : ~m198_2 +# 198| v198_20(void) = ExitFunction : # 207| int ModeledCallTarget(int) # 207| Block 0 @@ -1090,3 +1085,56 @@ ssa.cpp: # 239| v239_5(void) = UnmodeledUse : mu* # 239| v239_6(void) = AliasedUse : ~m244_5 # 239| v239_7(void) = ExitFunction : + +# 247| char* VoidStarIndirectParameters(char*, int) +# 247| Block 0 +# 247| v247_1(void) = EnterFunction : +# 247| m247_2(unknown) = AliasedDefinition : +# 247| mu247_3(unknown) = UnmodeledDefinition : +# 247| r247_4(glval) = VariableAddress[src] : +# 247| m247_5(char *) = InitializeParameter[src] : &:r247_4 +# 247| r247_6(char *) = Load : &:r247_4, m247_5 +# 247| m247_7(unknown) = InitializeIndirection[src] : &:r247_6 +# 247| r247_8(glval) = VariableAddress[size] : +# 247| m247_9(int) = InitializeParameter[size] : &:r247_8 +# 248| r248_1(glval) = VariableAddress[dst] : +# 248| r248_2(glval) = FunctionAddress[operator new[]] : +# 248| r248_3(glval) = VariableAddress[size] : +# 248| r248_4(int) = Load : &:r248_3, m247_9 +# 248| r248_5(unsigned long) = Convert : r248_4 +# 248| r248_6(unsigned long) = Constant[1] : +# 248| r248_7(unsigned long) = Mul : r248_5, r248_6 +# 248| r248_8(void *) = Call : func:r248_2, 0:r248_7 +# 248| m248_9(unknown) = ^CallSideEffect : ~m247_7 +# 248| m248_10(unknown) = Chi : total:m247_7, partial:m248_9 +# 248| r248_11(char *) = Convert : r248_8 +# 248| m248_12(char *) = Store : &:r248_1, r248_11 +# 249| r249_1(char) = Constant[97] : +# 249| r249_2(glval) = VariableAddress[src] : +# 249| r249_3(char *) = Load : &:r249_2, m247_5 +# 249| r249_4(glval) = CopyValue : r249_3 +# 249| m249_5(char) = Store : &:r249_4, r249_1 +# 249| m249_6(unknown) = Chi : total:m248_10, partial:m249_5 +# 250| r250_1(glval) = FunctionAddress[memcpy] : +# 250| r250_2(glval) = VariableAddress[dst] : +# 250| r250_3(char *) = Load : &:r250_2, m248_12 +# 250| r250_4(void *) = Convert : r250_3 +# 250| r250_5(glval) = VariableAddress[src] : +# 250| r250_6(char *) = Load : &:r250_5, m247_5 +# 250| r250_7(void *) = Convert : r250_6 +# 250| r250_8(glval) = VariableAddress[size] : +# 250| r250_9(int) = Load : &:r250_8, m247_9 +# 250| r250_10(void *) = Call : func:r250_1, 0:r250_4, 1:r250_7, 2:r250_9 +# 250| v250_11(void) = ^SizedBufferReadSideEffect[1] : &:r250_7, r250_9, ~m249_6 +# 250| m250_12(unknown) = ^SizedBufferMustWriteSideEffect[0] : &:r250_4, r250_9 +# 250| m250_13(unknown) = Chi : total:m249_6, partial:m250_12 +# 251| r251_1(glval) = VariableAddress[#return] : +# 251| r251_2(glval) = VariableAddress[dst] : +# 251| r251_3(char *) = Load : &:r251_2, m248_12 +# 251| m251_4(char *) = Store : &:r251_1, r251_3 +# 247| v247_10(void) = ReturnIndirection : &:r247_6, ~m250_13 +# 247| r247_11(glval) = VariableAddress[#return] : +# 247| v247_12(void) = ReturnValue : &:r247_11, m251_4 +# 247| v247_13(void) = UnmodeledUse : mu* +# 247| v247_14(void) = AliasedUse : ~m250_13 +# 247| v247_15(void) = ExitFunction : diff --git a/cpp/ql/test/library-tests/ir/ssa/aliased_ssa_ir_unsound.expected b/cpp/ql/test/library-tests/ir/ssa/aliased_ssa_ir_unsound.expected index 4f8a93d3de9..7311bd686dd 100644 --- a/cpp/ql/test/library-tests/ir/ssa/aliased_ssa_ir_unsound.expected +++ b/cpp/ql/test/library-tests/ir/ssa/aliased_ssa_ir_unsound.expected @@ -8,13 +8,12 @@ ssa.cpp: # 13| m13_5(Point *) = InitializeParameter[p] : &:r13_4 # 13| r13_6(Point *) = Load : &:r13_4, m13_5 # 13| m13_7(unknown) = InitializeIndirection[p] : &:r13_6 -# 13| m13_8(unknown) = Chi : total:m13_2, partial:m13_7 -# 13| r13_9(glval) = VariableAddress[which1] : -# 13| m13_10(bool) = InitializeParameter[which1] : &:r13_9 -# 13| r13_11(glval) = VariableAddress[which2] : -# 13| m13_12(bool) = InitializeParameter[which2] : &:r13_11 +# 13| r13_8(glval) = VariableAddress[which1] : +# 13| m13_9(bool) = InitializeParameter[which1] : &:r13_8 +# 13| r13_10(glval) = VariableAddress[which2] : +# 13| m13_11(bool) = InitializeParameter[which2] : &:r13_10 # 14| r14_1(glval) = VariableAddress[which1] : -# 14| r14_2(bool) = Load : &:r14_1, m13_10 +# 14| r14_2(bool) = Load : &:r14_1, m13_9 # 14| v14_3(void) = ConditionalBranch : r14_2 #-----| False -> Block 2 #-----| True -> Block 1 @@ -23,29 +22,31 @@ ssa.cpp: # 15| r15_1(glval) = VariableAddress[p] : # 15| r15_2(Point *) = Load : &:r15_1, m13_5 # 15| r15_3(glval) = FieldAddress[x] : r15_2 -# 15| r15_4(int) = Load : &:r15_3, ~m13_8 +# 15| r15_4(int) = Load : &:r15_3, ~m13_7 # 15| r15_5(int) = Constant[1] : # 15| r15_6(int) = Add : r15_4, r15_5 # 15| m15_7(int) = Store : &:r15_3, r15_6 -# 15| m15_8(unknown) = Chi : total:m13_8, partial:m15_7 +# 15| m15_8(unknown) = Chi : total:m13_7, partial:m15_7 #-----| Goto -> Block 3 # 18| Block 2 # 18| r18_1(glval) = VariableAddress[p] : # 18| r18_2(Point *) = Load : &:r18_1, m13_5 # 18| r18_3(glval) = FieldAddress[y] : r18_2 -# 18| r18_4(int) = Load : &:r18_3, ~m13_8 +# 18| r18_4(int) = Load : &:r18_3, ~m13_7 # 18| r18_5(int) = Constant[1] : # 18| r18_6(int) = Add : r18_4, r18_5 # 18| m18_7(int) = Store : &:r18_3, r18_6 -# 18| m18_8(unknown) = Chi : total:m13_8, partial:m18_7 +# 18| m18_8(unknown) = Chi : total:m13_7, partial:m18_7 #-----| Goto -> Block 3 # 21| Block 3 -# 21| m21_1(unknown) = Phi : from 1:~m15_8, from 2:~m18_8 -# 21| r21_2(glval) = VariableAddress[which2] : -# 21| r21_3(bool) = Load : &:r21_2, m13_12 -# 21| v21_4(void) = ConditionalBranch : r21_3 +# 21| m21_1(int) = Phi : from 1:~m13_7, from 2:m18_7 +# 21| m21_2(int) = Phi : from 1:m15_7, from 2:~m13_7 +# 21| m21_3(unknown) = Phi : from 1:m15_8, from 2:m18_8 +# 21| r21_4(glval) = VariableAddress[which2] : +# 21| r21_5(bool) = Load : &:r21_4, m13_11 +# 21| v21_6(void) = ConditionalBranch : r21_5 #-----| False -> Block 5 #-----| True -> Block 4 @@ -53,43 +54,45 @@ ssa.cpp: # 22| r22_1(glval) = VariableAddress[p] : # 22| r22_2(Point *) = Load : &:r22_1, m13_5 # 22| r22_3(glval) = FieldAddress[x] : r22_2 -# 22| r22_4(int) = Load : &:r22_3, ~m21_1 +# 22| r22_4(int) = Load : &:r22_3, m21_2 # 22| r22_5(int) = Constant[1] : # 22| r22_6(int) = Add : r22_4, r22_5 # 22| m22_7(int) = Store : &:r22_3, r22_6 -# 22| m22_8(unknown) = Chi : total:m21_1, partial:m22_7 +# 22| m22_8(unknown) = Chi : total:m21_3, partial:m22_7 #-----| Goto -> Block 6 # 25| Block 5 # 25| r25_1(glval) = VariableAddress[p] : # 25| r25_2(Point *) = Load : &:r25_1, m13_5 # 25| r25_3(glval) = FieldAddress[y] : r25_2 -# 25| r25_4(int) = Load : &:r25_3, ~m21_1 +# 25| r25_4(int) = Load : &:r25_3, m21_1 # 25| r25_5(int) = Constant[1] : # 25| r25_6(int) = Add : r25_4, r25_5 # 25| m25_7(int) = Store : &:r25_3, r25_6 -# 25| m25_8(unknown) = Chi : total:m21_1, partial:m25_7 +# 25| m25_8(unknown) = Chi : total:m21_3, partial:m25_7 #-----| Goto -> Block 6 # 28| Block 6 -# 28| m28_1(unknown) = Phi : from 4:~m22_8, from 5:~m25_8 -# 28| r28_2(glval) = VariableAddress[#return] : -# 28| r28_3(glval) = VariableAddress[p] : -# 28| r28_4(Point *) = Load : &:r28_3, m13_5 -# 28| r28_5(glval) = FieldAddress[x] : r28_4 -# 28| r28_6(int) = Load : &:r28_5, ~m28_1 -# 28| r28_7(glval) = VariableAddress[p] : -# 28| r28_8(Point *) = Load : &:r28_7, m13_5 -# 28| r28_9(glval) = FieldAddress[y] : r28_8 -# 28| r28_10(int) = Load : &:r28_9, ~m28_1 -# 28| r28_11(int) = Add : r28_6, r28_10 -# 28| m28_12(int) = Store : &:r28_2, r28_11 -# 13| v13_13(void) = ReturnIndirection : &:r13_6, ~m28_1 -# 13| r13_14(glval) = VariableAddress[#return] : -# 13| v13_15(void) = ReturnValue : &:r13_14, m28_12 -# 13| v13_16(void) = UnmodeledUse : mu* -# 13| v13_17(void) = AliasedUse : ~m28_1 -# 13| v13_18(void) = ExitFunction : +# 28| m28_1(int) = Phi : from 4:m21_1, from 5:m25_7 +# 28| m28_2(int) = Phi : from 4:m22_7, from 5:m21_2 +# 28| m28_3(unknown) = Phi : from 4:m22_8, from 5:m25_8 +# 28| r28_4(glval) = VariableAddress[#return] : +# 28| r28_5(glval) = VariableAddress[p] : +# 28| r28_6(Point *) = Load : &:r28_5, m13_5 +# 28| r28_7(glval) = FieldAddress[x] : r28_6 +# 28| r28_8(int) = Load : &:r28_7, m28_2 +# 28| r28_9(glval) = VariableAddress[p] : +# 28| r28_10(Point *) = Load : &:r28_9, m13_5 +# 28| r28_11(glval) = FieldAddress[y] : r28_10 +# 28| r28_12(int) = Load : &:r28_11, m28_1 +# 28| r28_13(int) = Add : r28_8, r28_12 +# 28| m28_14(int) = Store : &:r28_4, r28_13 +# 13| v13_12(void) = ReturnIndirection : &:r13_6, m28_3 +# 13| r13_13(glval) = VariableAddress[#return] : +# 13| v13_14(void) = ReturnValue : &:r13_13, m28_14 +# 13| v13_15(void) = UnmodeledUse : mu* +# 13| v13_16(void) = AliasedUse : ~m13_2 +# 13| v13_17(void) = ExitFunction : # 31| int UnreachableViaGoto() # 31| Block 0 @@ -212,13 +215,12 @@ ssa.cpp: # 68| m68_7(char *) = InitializeParameter[p] : &:r68_6 # 68| r68_8(char *) = Load : &:r68_6, m68_7 # 68| m68_9(unknown) = InitializeIndirection[p] : &:r68_8 -# 68| m68_10(unknown) = Chi : total:m68_2, partial:m68_9 #-----| Goto -> Block 1 # 69| Block 1 # 69| m69_1(char *) = Phi : from 0:m68_7, from 2:m70_6 # 69| m69_2(int) = Phi : from 0:m68_5, from 2:m69_8 -# 69| m69_3(unknown) = Phi : from 0:~m68_10, from 2:~m70_10 +# 69| m69_3(unknown) = Phi : from 0:~m68_2, from 2:~m70_10 # 69| r69_4(glval) = VariableAddress[n] : # 69| r69_5(int) = Load : &:r69_4, m69_2 # 69| r69_6(int) = Constant[1] : @@ -246,11 +248,11 @@ ssa.cpp: # 71| Block 3 # 71| v71_1(void) = NoOp : -# 68| v68_11(void) = ReturnIndirection : &:r68_8, ~m69_3 -# 68| v68_12(void) = ReturnVoid : -# 68| v68_13(void) = UnmodeledUse : mu* -# 68| v68_14(void) = AliasedUse : ~m69_3 -# 68| v68_15(void) = ExitFunction : +# 68| v68_10(void) = ReturnIndirection : &:r68_8, m68_9 +# 68| v68_11(void) = ReturnVoid : +# 68| v68_12(void) = UnmodeledUse : mu* +# 68| v68_13(void) = AliasedUse : ~m69_3 +# 68| v68_14(void) = ExitFunction : # 75| void ScalarPhi(bool) # 75| Block 0 @@ -758,20 +760,19 @@ ssa.cpp: # 179| m179_5(int *) = InitializeParameter[p] : &:r179_4 # 179| r179_6(int *) = Load : &:r179_4, m179_5 # 179| m179_7(unknown) = InitializeIndirection[p] : &:r179_6 -# 179| m179_8(unknown) = Chi : total:m179_2, partial:m179_7 -# 180| m180_1(unknown) = InlineAsm : ~m179_8 -# 180| m180_2(unknown) = Chi : total:m179_8, partial:m180_1 +# 180| m180_1(unknown) = InlineAsm : ~m179_2 +# 180| m180_2(unknown) = Chi : total:m179_2, partial:m180_1 # 181| r181_1(glval) = VariableAddress[#return] : # 181| r181_2(glval) = VariableAddress[p] : # 181| r181_3(int *) = Load : &:r181_2, m179_5 -# 181| r181_4(int) = Load : &:r181_3, ~m180_2 +# 181| r181_4(int) = Load : &:r181_3, ~m179_7 # 181| m181_5(int) = Store : &:r181_1, r181_4 -# 179| v179_9(void) = ReturnIndirection : &:r179_6, ~m180_2 -# 179| r179_10(glval) = VariableAddress[#return] : -# 179| v179_11(void) = ReturnValue : &:r179_10, m181_5 -# 179| v179_12(void) = UnmodeledUse : mu* -# 179| v179_13(void) = AliasedUse : ~m180_2 -# 179| v179_14(void) = ExitFunction : +# 179| v179_8(void) = ReturnIndirection : &:r179_6, m179_7 +# 179| r179_9(glval) = VariableAddress[#return] : +# 179| v179_10(void) = ReturnValue : &:r179_9, m181_5 +# 179| v179_11(void) = UnmodeledUse : mu* +# 179| v179_12(void) = AliasedUse : ~m180_2 +# 179| v179_13(void) = ExitFunction : # 184| void AsmStmtWithOutputs(unsigned int&, unsigned int&, unsigned int&, unsigned int&) # 184| Block 0 @@ -782,45 +783,41 @@ ssa.cpp: # 184| m184_5(unsigned int &) = InitializeParameter[a] : &:r184_4 # 184| r184_6(unsigned int &) = Load : &:r184_4, m184_5 # 184| m184_7(unknown) = InitializeIndirection[a] : &:r184_6 -# 184| m184_8(unknown) = Chi : total:m184_2, partial:m184_7 -# 184| r184_9(glval) = VariableAddress[b] : -# 184| m184_10(unsigned int &) = InitializeParameter[b] : &:r184_9 -# 184| r184_11(unsigned int &) = Load : &:r184_9, m184_10 -# 184| m184_12(unknown) = InitializeIndirection[b] : &:r184_11 -# 184| m184_13(unknown) = Chi : total:m184_8, partial:m184_12 -# 184| r184_14(glval) = VariableAddress[c] : -# 184| m184_15(unsigned int &) = InitializeParameter[c] : &:r184_14 -# 184| r184_16(unsigned int &) = Load : &:r184_14, m184_15 -# 184| m184_17(unknown) = InitializeIndirection[c] : &:r184_16 -# 184| m184_18(unknown) = Chi : total:m184_13, partial:m184_17 -# 184| r184_19(glval) = VariableAddress[d] : -# 184| m184_20(unsigned int &) = InitializeParameter[d] : &:r184_19 -# 184| r184_21(unsigned int &) = Load : &:r184_19, m184_20 -# 184| m184_22(unknown) = InitializeIndirection[d] : &:r184_21 -# 184| m184_23(unknown) = Chi : total:m184_18, partial:m184_22 +# 184| r184_8(glval) = VariableAddress[b] : +# 184| m184_9(unsigned int &) = InitializeParameter[b] : &:r184_8 +# 184| r184_10(unsigned int &) = Load : &:r184_8, m184_9 +# 184| m184_11(unknown) = InitializeIndirection[b] : &:r184_10 +# 184| r184_12(glval) = VariableAddress[c] : +# 184| m184_13(unsigned int &) = InitializeParameter[c] : &:r184_12 +# 184| r184_14(unsigned int &) = Load : &:r184_12, m184_13 +# 184| m184_15(unknown) = InitializeIndirection[c] : &:r184_14 +# 184| r184_16(glval) = VariableAddress[d] : +# 184| m184_17(unsigned int &) = InitializeParameter[d] : &:r184_16 +# 184| r184_18(unsigned int &) = Load : &:r184_16, m184_17 +# 184| m184_19(unknown) = InitializeIndirection[d] : &:r184_18 # 189| r189_1(glval) = VariableAddress[a] : # 189| r189_2(unsigned int &) = Load : &:r189_1, m184_5 # 189| r189_3(glval) = CopyValue : r189_2 # 189| r189_4(glval) = VariableAddress[b] : -# 189| r189_5(unsigned int &) = Load : &:r189_4, m184_10 +# 189| r189_5(unsigned int &) = Load : &:r189_4, m184_9 # 189| r189_6(glval) = CopyValue : r189_5 # 190| r190_1(glval) = VariableAddress[c] : -# 190| r190_2(unsigned int &) = Load : &:r190_1, m184_15 -# 190| r190_3(unsigned int) = Load : &:r190_2, ~m184_23 +# 190| r190_2(unsigned int &) = Load : &:r190_1, m184_13 +# 190| r190_3(unsigned int) = Load : &:r190_2, ~m184_15 # 190| r190_4(glval) = VariableAddress[d] : -# 190| r190_5(unsigned int &) = Load : &:r190_4, m184_20 -# 190| r190_6(unsigned int) = Load : &:r190_5, ~m184_23 -# 186| m186_1(unknown) = InlineAsm : ~m184_23, 0:r189_3, 1:r189_6, 2:r190_3, 3:r190_6 -# 186| m186_2(unknown) = Chi : total:m184_23, partial:m186_1 +# 190| r190_5(unsigned int &) = Load : &:r190_4, m184_17 +# 190| r190_6(unsigned int) = Load : &:r190_5, ~m184_19 +# 186| m186_1(unknown) = InlineAsm : ~m184_2, 0:r189_3, 1:r189_6, 2:r190_3, 3:r190_6 +# 186| m186_2(unknown) = Chi : total:m184_2, partial:m186_1 # 192| v192_1(void) = NoOp : -# 184| v184_24(void) = ReturnIndirection : &:r184_6, ~m186_2 -# 184| v184_25(void) = ReturnIndirection : &:r184_11, ~m186_2 -# 184| v184_26(void) = ReturnIndirection : &:r184_16, ~m186_2 -# 184| v184_27(void) = ReturnIndirection : &:r184_21, ~m186_2 -# 184| v184_28(void) = ReturnVoid : -# 184| v184_29(void) = UnmodeledUse : mu* -# 184| v184_30(void) = AliasedUse : ~m186_2 -# 184| v184_31(void) = ExitFunction : +# 184| v184_20(void) = ReturnIndirection : &:r184_6, m184_7 +# 184| v184_21(void) = ReturnIndirection : &:r184_10, m184_11 +# 184| v184_22(void) = ReturnIndirection : &:r184_14, m184_15 +# 184| v184_23(void) = ReturnIndirection : &:r184_18, m184_19 +# 184| v184_24(void) = ReturnVoid : +# 184| v184_25(void) = UnmodeledUse : mu* +# 184| v184_26(void) = AliasedUse : ~m186_2 +# 184| v184_27(void) = ExitFunction : # 198| int PureFunctions(char*, char*, int) # 198| Block 0 @@ -831,41 +828,39 @@ ssa.cpp: # 198| m198_5(char *) = InitializeParameter[str1] : &:r198_4 # 198| r198_6(char *) = Load : &:r198_4, m198_5 # 198| m198_7(unknown) = InitializeIndirection[str1] : &:r198_6 -# 198| m198_8(unknown) = Chi : total:m198_2, partial:m198_7 -# 198| r198_9(glval) = VariableAddress[str2] : -# 198| m198_10(char *) = InitializeParameter[str2] : &:r198_9 -# 198| r198_11(char *) = Load : &:r198_9, m198_10 -# 198| m198_12(unknown) = InitializeIndirection[str2] : &:r198_11 -# 198| m198_13(unknown) = Chi : total:m198_8, partial:m198_12 -# 198| r198_14(glval) = VariableAddress[x] : -# 198| m198_15(int) = InitializeParameter[x] : &:r198_14 +# 198| r198_8(glval) = VariableAddress[str2] : +# 198| m198_9(char *) = InitializeParameter[str2] : &:r198_8 +# 198| r198_10(char *) = Load : &:r198_8, m198_9 +# 198| m198_11(unknown) = InitializeIndirection[str2] : &:r198_10 +# 198| r198_12(glval) = VariableAddress[x] : +# 198| m198_13(int) = InitializeParameter[x] : &:r198_12 # 199| r199_1(glval) = VariableAddress[ret] : # 199| r199_2(glval) = FunctionAddress[strcmp] : # 199| r199_3(glval) = VariableAddress[str1] : # 199| r199_4(char *) = Load : &:r199_3, m198_5 # 199| r199_5(char *) = Convert : r199_4 # 199| r199_6(glval) = VariableAddress[str2] : -# 199| r199_7(char *) = Load : &:r199_6, m198_10 +# 199| r199_7(char *) = Load : &:r199_6, m198_9 # 199| r199_8(char *) = Convert : r199_7 # 199| r199_9(int) = Call : func:r199_2, 0:r199_5, 1:r199_8 -# 199| v199_10(void) = ^CallReadSideEffect : ~m198_13 -# 199| v199_11(void) = ^BufferReadSideEffect[0] : &:r199_5, ~m198_13 -# 199| v199_12(void) = ^BufferReadSideEffect[1] : &:r199_8, ~m198_13 +# 199| v199_10(void) = ^CallReadSideEffect : ~m198_2 +# 199| v199_11(void) = ^BufferReadSideEffect[0] : &:r199_5, ~m198_7 +# 199| v199_12(void) = ^BufferReadSideEffect[1] : &:r199_8, ~m198_11 # 199| m199_13(int) = Store : &:r199_1, r199_9 # 200| r200_1(glval) = FunctionAddress[strlen] : # 200| r200_2(glval) = VariableAddress[str1] : # 200| r200_3(char *) = Load : &:r200_2, m198_5 # 200| r200_4(char *) = Convert : r200_3 # 200| r200_5(int) = Call : func:r200_1, 0:r200_4 -# 200| v200_6(void) = ^CallReadSideEffect : ~m198_13 -# 200| v200_7(void) = ^BufferReadSideEffect[0] : &:r200_4, ~m198_13 +# 200| v200_6(void) = ^CallReadSideEffect : ~m198_2 +# 200| v200_7(void) = ^BufferReadSideEffect[0] : &:r200_4, ~m198_7 # 200| r200_8(glval) = VariableAddress[ret] : # 200| r200_9(int) = Load : &:r200_8, m199_13 # 200| r200_10(int) = Add : r200_9, r200_5 # 200| m200_11(int) = Store : &:r200_8, r200_10 # 201| r201_1(glval) = FunctionAddress[abs] : # 201| r201_2(glval) = VariableAddress[x] : -# 201| r201_3(int) = Load : &:r201_2, m198_15 +# 201| r201_3(int) = Load : &:r201_2, m198_13 # 201| r201_4(int) = Call : func:r201_1, 0:r201_3 # 201| r201_5(glval) = VariableAddress[ret] : # 201| r201_6(int) = Load : &:r201_5, m200_11 @@ -875,13 +870,13 @@ ssa.cpp: # 202| r202_2(glval) = VariableAddress[ret] : # 202| r202_3(int) = Load : &:r202_2, m201_8 # 202| m202_4(int) = Store : &:r202_1, r202_3 -# 198| v198_16(void) = ReturnIndirection : &:r198_6, ~m198_13 -# 198| v198_17(void) = ReturnIndirection : &:r198_11, ~m198_13 -# 198| r198_18(glval) = VariableAddress[#return] : -# 198| v198_19(void) = ReturnValue : &:r198_18, m202_4 -# 198| v198_20(void) = UnmodeledUse : mu* -# 198| v198_21(void) = AliasedUse : ~m198_13 -# 198| v198_22(void) = ExitFunction : +# 198| v198_14(void) = ReturnIndirection : &:r198_6, m198_7 +# 198| v198_15(void) = ReturnIndirection : &:r198_10, m198_11 +# 198| r198_16(glval) = VariableAddress[#return] : +# 198| v198_17(void) = ReturnValue : &:r198_16, m202_4 +# 198| v198_18(void) = UnmodeledUse : mu* +# 198| v198_19(void) = AliasedUse : ~m198_2 +# 198| v198_20(void) = ExitFunction : # 207| int ModeledCallTarget(int) # 207| Block 0 @@ -1085,3 +1080,56 @@ ssa.cpp: # 239| v239_5(void) = UnmodeledUse : mu* # 239| v239_6(void) = AliasedUse : ~m244_5 # 239| v239_7(void) = ExitFunction : + +# 247| char* VoidStarIndirectParameters(char*, int) +# 247| Block 0 +# 247| v247_1(void) = EnterFunction : +# 247| m247_2(unknown) = AliasedDefinition : +# 247| mu247_3(unknown) = UnmodeledDefinition : +# 247| r247_4(glval) = VariableAddress[src] : +# 247| m247_5(char *) = InitializeParameter[src] : &:r247_4 +# 247| r247_6(char *) = Load : &:r247_4, m247_5 +# 247| m247_7(unknown) = InitializeIndirection[src] : &:r247_6 +# 247| r247_8(glval) = VariableAddress[size] : +# 247| m247_9(int) = InitializeParameter[size] : &:r247_8 +# 248| r248_1(glval) = VariableAddress[dst] : +# 248| r248_2(glval) = FunctionAddress[operator new[]] : +# 248| r248_3(glval) = VariableAddress[size] : +# 248| r248_4(int) = Load : &:r248_3, m247_9 +# 248| r248_5(unsigned long) = Convert : r248_4 +# 248| r248_6(unsigned long) = Constant[1] : +# 248| r248_7(unsigned long) = Mul : r248_5, r248_6 +# 248| r248_8(void *) = Call : func:r248_2, 0:r248_7 +# 248| m248_9(unknown) = ^CallSideEffect : ~m247_2 +# 248| m248_10(unknown) = Chi : total:m247_2, partial:m248_9 +# 248| r248_11(char *) = Convert : r248_8 +# 248| m248_12(char *) = Store : &:r248_1, r248_11 +# 249| r249_1(char) = Constant[97] : +# 249| r249_2(glval) = VariableAddress[src] : +# 249| r249_3(char *) = Load : &:r249_2, m247_5 +# 249| r249_4(glval) = CopyValue : r249_3 +# 249| m249_5(char) = Store : &:r249_4, r249_1 +# 249| m249_6(unknown) = Chi : total:m247_7, partial:m249_5 +# 250| r250_1(glval) = FunctionAddress[memcpy] : +# 250| r250_2(glval) = VariableAddress[dst] : +# 250| r250_3(char *) = Load : &:r250_2, m248_12 +# 250| r250_4(void *) = Convert : r250_3 +# 250| r250_5(glval) = VariableAddress[src] : +# 250| r250_6(char *) = Load : &:r250_5, m247_5 +# 250| r250_7(void *) = Convert : r250_6 +# 250| r250_8(glval) = VariableAddress[size] : +# 250| r250_9(int) = Load : &:r250_8, m247_9 +# 250| r250_10(void *) = Call : func:r250_1, 0:r250_4, 1:r250_7, 2:r250_9 +# 250| v250_11(void) = ^SizedBufferReadSideEffect[1] : &:r250_7, r250_9, ~m249_6 +# 250| m250_12(unknown) = ^SizedBufferMustWriteSideEffect[0] : &:r250_4, r250_9 +# 250| m250_13(unknown) = Chi : total:m248_10, partial:m250_12 +# 251| r251_1(glval) = VariableAddress[#return] : +# 251| r251_2(glval) = VariableAddress[dst] : +# 251| r251_3(char *) = Load : &:r251_2, m248_12 +# 251| m251_4(char *) = Store : &:r251_1, r251_3 +# 247| v247_10(void) = ReturnIndirection : &:r247_6, ~m249_6 +# 247| r247_11(glval) = VariableAddress[#return] : +# 247| v247_12(void) = ReturnValue : &:r247_11, m251_4 +# 247| v247_13(void) = UnmodeledUse : mu* +# 247| v247_14(void) = AliasedUse : ~m250_13 +# 247| v247_15(void) = ExitFunction : diff --git a/cpp/ql/test/library-tests/ir/ssa/ssa.cpp b/cpp/ql/test/library-tests/ir/ssa/ssa.cpp index 761289c5017..40a8017b9ef 100644 --- a/cpp/ql/test/library-tests/ir/ssa/ssa.cpp +++ b/cpp/ql/test/library-tests/ir/ssa/ssa.cpp @@ -243,3 +243,10 @@ void ExplicitConstructorCalls() { Constructible c2 = Constructible(2); c2.g(); } + +char *VoidStarIndirectParameters(char *src, int size) { + char *dst = new char[size]; + *src = 'a'; + memcpy(dst, src, size); + return dst; +} \ No newline at end of file diff --git a/cpp/ql/test/library-tests/ir/ssa/unaliased_ssa_ir.expected b/cpp/ql/test/library-tests/ir/ssa/unaliased_ssa_ir.expected index 59e1c01b3de..94d383a29ab 100644 --- a/cpp/ql/test/library-tests/ir/ssa/unaliased_ssa_ir.expected +++ b/cpp/ql/test/library-tests/ir/ssa/unaliased_ssa_ir.expected @@ -1028,3 +1028,53 @@ ssa.cpp: # 239| v239_5(void) = UnmodeledUse : mu* # 239| v239_6(void) = AliasedUse : ~mu239_3 # 239| v239_7(void) = ExitFunction : + +# 247| char* VoidStarIndirectParameters(char*, int) +# 247| Block 0 +# 247| v247_1(void) = EnterFunction : +# 247| mu247_2(unknown) = AliasedDefinition : +# 247| mu247_3(unknown) = UnmodeledDefinition : +# 247| r247_4(glval) = VariableAddress[src] : +# 247| m247_5(char *) = InitializeParameter[src] : &:r247_4 +# 247| r247_6(char *) = Load : &:r247_4, m247_5 +# 247| mu247_7(unknown) = InitializeIndirection[src] : &:r247_6 +# 247| r247_8(glval) = VariableAddress[size] : +# 247| m247_9(int) = InitializeParameter[size] : &:r247_8 +# 248| r248_1(glval) = VariableAddress[dst] : +# 248| r248_2(glval) = FunctionAddress[operator new[]] : +# 248| r248_3(glval) = VariableAddress[size] : +# 248| r248_4(int) = Load : &:r248_3, m247_9 +# 248| r248_5(unsigned long) = Convert : r248_4 +# 248| r248_6(unsigned long) = Constant[1] : +# 248| r248_7(unsigned long) = Mul : r248_5, r248_6 +# 248| r248_8(void *) = Call : func:r248_2, 0:r248_7 +# 248| mu248_9(unknown) = ^CallSideEffect : ~mu247_3 +# 248| r248_10(char *) = Convert : r248_8 +# 248| m248_11(char *) = Store : &:r248_1, r248_10 +# 249| r249_1(char) = Constant[97] : +# 249| r249_2(glval) = VariableAddress[src] : +# 249| r249_3(char *) = Load : &:r249_2, m247_5 +# 249| r249_4(glval) = CopyValue : r249_3 +# 249| mu249_5(char) = Store : &:r249_4, r249_1 +# 250| r250_1(glval) = FunctionAddress[memcpy] : +# 250| r250_2(glval) = VariableAddress[dst] : +# 250| r250_3(char *) = Load : &:r250_2, m248_11 +# 250| r250_4(void *) = Convert : r250_3 +# 250| r250_5(glval) = VariableAddress[src] : +# 250| r250_6(char *) = Load : &:r250_5, m247_5 +# 250| r250_7(void *) = Convert : r250_6 +# 250| r250_8(glval) = VariableAddress[size] : +# 250| r250_9(int) = Load : &:r250_8, m247_9 +# 250| r250_10(void *) = Call : func:r250_1, 0:r250_4, 1:r250_7, 2:r250_9 +# 250| v250_11(void) = ^SizedBufferReadSideEffect[1] : &:r250_7, r250_9, ~mu247_3 +# 250| mu250_12(unknown) = ^SizedBufferMustWriteSideEffect[0] : &:r250_4, r250_9 +# 251| r251_1(glval) = VariableAddress[#return] : +# 251| r251_2(glval) = VariableAddress[dst] : +# 251| r251_3(char *) = Load : &:r251_2, m248_11 +# 251| m251_4(char *) = Store : &:r251_1, r251_3 +# 247| v247_10(void) = ReturnIndirection : &:r247_6, ~mu247_3 +# 247| r247_11(glval) = VariableAddress[#return] : +# 247| v247_12(void) = ReturnValue : &:r247_11, m251_4 +# 247| v247_13(void) = UnmodeledUse : mu* +# 247| v247_14(void) = AliasedUse : ~mu247_3 +# 247| v247_15(void) = ExitFunction : diff --git a/cpp/ql/test/library-tests/ir/ssa/unaliased_ssa_ir_unsound.expected b/cpp/ql/test/library-tests/ir/ssa/unaliased_ssa_ir_unsound.expected index 59e1c01b3de..94d383a29ab 100644 --- a/cpp/ql/test/library-tests/ir/ssa/unaliased_ssa_ir_unsound.expected +++ b/cpp/ql/test/library-tests/ir/ssa/unaliased_ssa_ir_unsound.expected @@ -1028,3 +1028,53 @@ ssa.cpp: # 239| v239_5(void) = UnmodeledUse : mu* # 239| v239_6(void) = AliasedUse : ~mu239_3 # 239| v239_7(void) = ExitFunction : + +# 247| char* VoidStarIndirectParameters(char*, int) +# 247| Block 0 +# 247| v247_1(void) = EnterFunction : +# 247| mu247_2(unknown) = AliasedDefinition : +# 247| mu247_3(unknown) = UnmodeledDefinition : +# 247| r247_4(glval) = VariableAddress[src] : +# 247| m247_5(char *) = InitializeParameter[src] : &:r247_4 +# 247| r247_6(char *) = Load : &:r247_4, m247_5 +# 247| mu247_7(unknown) = InitializeIndirection[src] : &:r247_6 +# 247| r247_8(glval) = VariableAddress[size] : +# 247| m247_9(int) = InitializeParameter[size] : &:r247_8 +# 248| r248_1(glval) = VariableAddress[dst] : +# 248| r248_2(glval) = FunctionAddress[operator new[]] : +# 248| r248_3(glval) = VariableAddress[size] : +# 248| r248_4(int) = Load : &:r248_3, m247_9 +# 248| r248_5(unsigned long) = Convert : r248_4 +# 248| r248_6(unsigned long) = Constant[1] : +# 248| r248_7(unsigned long) = Mul : r248_5, r248_6 +# 248| r248_8(void *) = Call : func:r248_2, 0:r248_7 +# 248| mu248_9(unknown) = ^CallSideEffect : ~mu247_3 +# 248| r248_10(char *) = Convert : r248_8 +# 248| m248_11(char *) = Store : &:r248_1, r248_10 +# 249| r249_1(char) = Constant[97] : +# 249| r249_2(glval) = VariableAddress[src] : +# 249| r249_3(char *) = Load : &:r249_2, m247_5 +# 249| r249_4(glval) = CopyValue : r249_3 +# 249| mu249_5(char) = Store : &:r249_4, r249_1 +# 250| r250_1(glval) = FunctionAddress[memcpy] : +# 250| r250_2(glval) = VariableAddress[dst] : +# 250| r250_3(char *) = Load : &:r250_2, m248_11 +# 250| r250_4(void *) = Convert : r250_3 +# 250| r250_5(glval) = VariableAddress[src] : +# 250| r250_6(char *) = Load : &:r250_5, m247_5 +# 250| r250_7(void *) = Convert : r250_6 +# 250| r250_8(glval) = VariableAddress[size] : +# 250| r250_9(int) = Load : &:r250_8, m247_9 +# 250| r250_10(void *) = Call : func:r250_1, 0:r250_4, 1:r250_7, 2:r250_9 +# 250| v250_11(void) = ^SizedBufferReadSideEffect[1] : &:r250_7, r250_9, ~mu247_3 +# 250| mu250_12(unknown) = ^SizedBufferMustWriteSideEffect[0] : &:r250_4, r250_9 +# 251| r251_1(glval) = VariableAddress[#return] : +# 251| r251_2(glval) = VariableAddress[dst] : +# 251| r251_3(char *) = Load : &:r251_2, m248_11 +# 251| m251_4(char *) = Store : &:r251_1, r251_3 +# 247| v247_10(void) = ReturnIndirection : &:r247_6, ~mu247_3 +# 247| r247_11(glval) = VariableAddress[#return] : +# 247| v247_12(void) = ReturnValue : &:r247_11, m251_4 +# 247| v247_13(void) = UnmodeledUse : mu* +# 247| v247_14(void) = AliasedUse : ~mu247_3 +# 247| v247_15(void) = ExitFunction : diff --git a/cpp/ql/test/library-tests/locations/overloaded_operators/locations.expected b/cpp/ql/test/library-tests/locations/overloaded_operators/locations.expected index e512cb4794c..7e105ce5085 100644 --- a/cpp/ql/test/library-tests/locations/overloaded_operators/locations.expected +++ b/cpp/ql/test/library-tests/locations/overloaded_operators/locations.expected @@ -1,4 +1,4 @@ -| test.cpp:9:12:9:35 | call to MyInt | +| test.cpp:9:5:9:36 | call to MyInt | | test.cpp:26:18:26:23 | call to MyInt | | test.cpp:42:15:42:15 | call to operator+ | | test.cpp:43:5:43:5 | call to operator+= | diff --git a/cpp/ql/test/library-tests/permissive/calls.expected b/cpp/ql/test/library-tests/permissive/calls.expected index 35e31c1b691..9d780e76405 100644 --- a/cpp/ql/test/library-tests/permissive/calls.expected +++ b/cpp/ql/test/library-tests/permissive/calls.expected @@ -1,2 +1 @@ -| non_permissive.cpp:6:3:6:3 | call to f | non_permissive.cpp:2:13:2:13 | f | | permissive.cpp:6:3:6:3 | call to f | permissive.cpp:2:13:2:13 | f | diff --git a/cpp/ql/test/library-tests/syntax-zoo/aliased_ssa_sanity.expected b/cpp/ql/test/library-tests/syntax-zoo/aliased_ssa_sanity.expected index ebd22e357c2..c0671e967cc 100644 --- a/cpp/ql/test/library-tests/syntax-zoo/aliased_ssa_sanity.expected +++ b/cpp/ql/test/library-tests/syntax-zoo/aliased_ssa_sanity.expected @@ -35,14 +35,14 @@ missingOperandType duplicateChiOperand sideEffectWithoutPrimary instructionWithoutSuccessor -| VacuousDestructorCall.cpp:2:29:2:29 | Chi: y | +| VacuousDestructorCall.cpp:2:29:2:29 | InitializeIndirection: y | | condition_decls.cpp:16:19:16:20 | Chi: call to BoxedInt | | condition_decls.cpp:26:23:26:24 | Chi: call to BoxedInt | | condition_decls.cpp:41:22:41:23 | Chi: call to BoxedInt | | condition_decls.cpp:48:52:48:53 | Chi: call to BoxedInt | | cpp17.cpp:15:11:15:21 | Convert: (void *)... | | misc.c:171:10:171:13 | Uninitialized: definition of str2 | -| misc.c:219:47:219:48 | Chi: sp | +| misc.c:219:47:219:48 | InitializeIndirection: sp | | ms_try_except.cpp:3:9:3:9 | Uninitialized: definition of x | | ms_try_mix.cpp:11:12:11:15 | Chi: call to C | | ms_try_mix.cpp:28:12:28:15 | Chi: call to C | diff --git a/cpp/ql/test/library-tests/valuenumbering/GlobalValueNumbering/ir_gvn.expected b/cpp/ql/test/library-tests/valuenumbering/GlobalValueNumbering/ir_gvn.expected index 38386b9c2fd..d8cda203eff 100644 --- a/cpp/ql/test/library-tests/valuenumbering/GlobalValueNumbering/ir_gvn.expected +++ b/cpp/ql/test/library-tests/valuenumbering/GlobalValueNumbering/ir_gvn.expected @@ -266,8 +266,6 @@ test.cpp: # 39| valnum = m39_9 # 39| m39_11(unknown) = InitializeIndirection[p2] : &:r39_10 # 39| valnum = unique -# 39| m39_12(unknown) = Chi : total:m39_2, partial:m39_11 -# 39| valnum = unique # 40| r40_1(glval) = VariableAddress[x] : # 40| valnum = r40_1 # 40| m40_2(int) = Uninitialized[x] : &:r40_1 @@ -292,7 +290,7 @@ test.cpp: # 43| valnum = r43_5 # 43| r43_6(glval) = VariableAddress[global03] : # 43| valnum = r43_6 -# 43| r43_7(int) = Load : &:r43_6, ~m39_12 +# 43| r43_7(int) = Load : &:r43_6, ~m39_2 # 43| valnum = unique # 43| r43_8(int) = Add : r43_5, r43_7 # 43| valnum = r43_8 @@ -310,7 +308,7 @@ test.cpp: # 44| valnum = m39_9 # 44| m44_5(int) = Store : &:r44_4, r44_1 # 44| valnum = r44_1 -# 44| m44_6(unknown) = Chi : total:m39_12, partial:m44_5 +# 44| m44_6(unknown) = Chi : total:m39_11, partial:m44_5 # 44| valnum = unique # 45| r45_1(glval) = VariableAddress[p0] : # 45| valnum = r39_4 @@ -324,7 +322,7 @@ test.cpp: # 45| valnum = r43_5 # 45| r45_6(glval) = VariableAddress[global03] : # 45| valnum = r43_6 -# 45| r45_7(int) = Load : &:r45_6, ~m44_6 +# 45| r45_7(int) = Load : &:r45_6, ~m39_2 # 45| valnum = unique # 45| r45_8(int) = Add : r45_5, r45_7 # 45| valnum = r45_8 @@ -341,13 +339,13 @@ test.cpp: # 46| m46_4(int) = Store : &:r46_3, r46_2 # 46| valnum = r45_8 # 47| v47_1(void) = NoOp : -# 39| v39_13(void) = ReturnIndirection : &:r39_10, ~m44_6 -# 39| r39_14(glval) = VariableAddress[#return] : +# 39| v39_12(void) = ReturnIndirection : &:r39_10, ~m44_6 +# 39| r39_13(glval) = VariableAddress[#return] : # 39| valnum = unique -# 39| v39_15(void) = ReturnValue : &:r39_14 -# 39| v39_16(void) = UnmodeledUse : mu* -# 39| v39_17(void) = AliasedUse : ~m44_6 -# 39| v39_18(void) = ExitFunction : +# 39| v39_14(void) = ReturnValue : &:r39_13 +# 39| v39_15(void) = UnmodeledUse : mu* +# 39| v39_16(void) = AliasedUse : ~m39_2 +# 39| v39_17(void) = ExitFunction : # 49| unsigned int my_strspn(char const*, char const*) # 49| Block 0 @@ -364,17 +362,13 @@ test.cpp: # 49| valnum = m49_5 # 49| m49_7(unknown) = InitializeIndirection[str] : &:r49_6 # 49| valnum = unique -# 49| m49_8(unknown) = Chi : total:m49_2, partial:m49_7 -# 49| valnum = unique -# 49| r49_9(glval) = VariableAddress[chars] : -# 49| valnum = r49_9 -# 49| m49_10(char *) = InitializeParameter[chars] : &:r49_9 -# 49| valnum = m49_10 -# 49| r49_11(char *) = Load : &:r49_9, m49_10 -# 49| valnum = m49_10 -# 49| m49_12(unknown) = InitializeIndirection[chars] : &:r49_11 -# 49| valnum = unique -# 49| m49_13(unknown) = Chi : total:m49_8, partial:m49_12 +# 49| r49_8(glval) = VariableAddress[chars] : +# 49| valnum = r49_8 +# 49| m49_9(char *) = InitializeParameter[chars] : &:r49_8 +# 49| valnum = m49_9 +# 49| r49_10(char *) = Load : &:r49_8, m49_9 +# 49| valnum = m49_9 +# 49| m49_11(unknown) = InitializeIndirection[chars] : &:r49_10 # 49| valnum = unique # 50| r50_1(glval) = VariableAddress[ptr] : # 50| valnum = r50_1 @@ -395,7 +389,7 @@ test.cpp: # 53| valnum = r49_4 # 53| r53_3(char *) = Load : &:r53_2, m49_5 # 53| valnum = m49_5 -# 53| r53_4(char) = Load : &:r53_3, ~m49_13 +# 53| r53_4(char) = Load : &:r53_3, ~m49_7 # 53| valnum = unique # 53| r53_5(int) = Convert : r53_4 # 53| valnum = unique @@ -409,13 +403,13 @@ test.cpp: # 55| Block 2 # 55| r55_1(glval) = VariableAddress[chars] : -# 55| valnum = r49_9 -# 55| r55_2(char *) = Load : &:r55_1, m49_10 -# 55| valnum = m49_10 +# 55| valnum = r49_8 +# 55| r55_2(char *) = Load : &:r55_1, m49_9 +# 55| valnum = m49_9 # 55| r55_3(glval) = VariableAddress[ptr] : # 55| valnum = r50_1 # 55| m55_4(char *) = Store : &:r55_3, r55_2 -# 55| valnum = m49_10 +# 55| valnum = m49_9 #-----| Goto -> Block 3 # 56| Block 3 @@ -425,7 +419,7 @@ test.cpp: # 56| valnum = r50_1 # 56| r56_3(char *) = Load : &:r56_2, m56_1 # 56| valnum = m56_1 -# 56| r56_4(char) = Load : &:r56_3, ~m49_13 +# 56| r56_4(char) = Load : &:r56_3, ~m49_2 # 56| valnum = unique # 56| r56_5(int) = Convert : r56_4 # 56| valnum = unique @@ -433,7 +427,7 @@ test.cpp: # 56| valnum = r49_4 # 56| r56_7(char *) = Load : &:r56_6, m49_5 # 56| valnum = m49_5 -# 56| r56_8(char) = Load : &:r56_7, ~m49_13 +# 56| r56_8(char) = Load : &:r56_7, ~m49_7 # 56| valnum = unique # 56| r56_9(int) = Convert : r56_8 # 56| valnum = unique @@ -448,7 +442,7 @@ test.cpp: # 56| valnum = r50_1 # 56| r56_13(char *) = Load : &:r56_12, m56_1 # 56| valnum = m56_1 -# 56| r56_14(char) = Load : &:r56_13, ~m49_13 +# 56| r56_14(char) = Load : &:r56_13, ~m49_2 # 56| valnum = unique # 56| r56_15(int) = Convert : r56_14 # 56| valnum = unique @@ -478,7 +472,7 @@ test.cpp: # 59| valnum = r50_1 # 59| r59_2(char *) = Load : &:r59_1, m56_1 # 59| valnum = m56_1 -# 59| r59_3(char) = Load : &:r59_2, ~m49_13 +# 59| r59_3(char) = Load : &:r59_2, ~m49_2 # 59| valnum = unique # 59| r59_4(int) = Convert : r59_3 # 59| valnum = unique @@ -517,14 +511,14 @@ test.cpp: # 65| valnum = m53_1 # 65| m65_4(unsigned int) = Store : &:r65_1, r65_3 # 65| valnum = m53_1 -# 49| v49_14(void) = ReturnIndirection : &:r49_6, ~m49_13 -# 49| v49_15(void) = ReturnIndirection : &:r49_11, ~m49_13 -# 49| r49_16(glval) = VariableAddress[#return] : +# 49| v49_12(void) = ReturnIndirection : &:r49_6, m49_7 +# 49| v49_13(void) = ReturnIndirection : &:r49_10, m49_11 +# 49| r49_14(glval) = VariableAddress[#return] : # 49| valnum = r65_1 -# 49| v49_17(void) = ReturnValue : &:r49_16, m65_4 -# 49| v49_18(void) = UnmodeledUse : mu* -# 49| v49_19(void) = AliasedUse : ~m49_13 -# 49| v49_20(void) = ExitFunction : +# 49| v49_15(void) = ReturnValue : &:r49_14, m65_4 +# 49| v49_16(void) = UnmodeledUse : mu* +# 49| v49_17(void) = AliasedUse : ~m49_2 +# 49| v49_18(void) = ExitFunction : # 75| void test04(two_values*) # 75| Block 0 @@ -541,17 +535,15 @@ test.cpp: # 75| valnum = m75_5 # 75| m75_7(unknown) = InitializeIndirection[vals] : &:r75_6 # 75| valnum = unique -# 75| m75_8(unknown) = Chi : total:m75_2, partial:m75_7 -# 75| valnum = unique # 77| r77_1(glval) = VariableAddress[v] : # 77| valnum = r77_1 # 77| r77_2(glval) = FunctionAddress[getAValue] : # 77| valnum = unique # 77| r77_3(int) = Call : func:r77_2 # 77| valnum = unique -# 77| m77_4(unknown) = ^CallSideEffect : ~m75_8 +# 77| m77_4(unknown) = ^CallSideEffect : ~m75_2 # 77| valnum = unique -# 77| m77_5(unknown) = Chi : total:m75_8, partial:m77_4 +# 77| m77_5(unknown) = Chi : total:m75_2, partial:m77_4 # 77| valnum = unique # 77| r77_6(signed short) = Convert : r77_3 # 77| valnum = r77_6 @@ -569,7 +561,7 @@ test.cpp: # 79| valnum = m75_5 # 79| r79_6(glval) = FieldAddress[val1] : r79_5 # 79| valnum = unique -# 79| r79_7(signed short) = Load : &:r79_6, ~m77_5 +# 79| r79_7(signed short) = Load : &:r79_6, ~m75_7 # 79| valnum = unique # 79| r79_8(int) = Convert : r79_7 # 79| valnum = unique @@ -579,7 +571,7 @@ test.cpp: # 79| valnum = m75_5 # 79| r79_11(glval) = FieldAddress[val2] : r79_10 # 79| valnum = unique -# 79| r79_12(signed short) = Load : &:r79_11, ~m77_5 +# 79| r79_12(signed short) = Load : &:r79_11, ~m75_7 # 79| valnum = unique # 79| r79_13(int) = Convert : r79_12 # 79| valnum = unique @@ -612,11 +604,11 @@ test.cpp: # 82| m82_1(unknown) = Phi : from 0:~m77_5, from 1:~m80_4 # 82| valnum = unique # 82| v82_2(void) = NoOp : -# 75| v75_9(void) = ReturnIndirection : &:r75_6, ~m82_1 -# 75| v75_10(void) = ReturnVoid : -# 75| v75_11(void) = UnmodeledUse : mu* -# 75| v75_12(void) = AliasedUse : ~m82_1 -# 75| v75_13(void) = ExitFunction : +# 75| v75_8(void) = ReturnIndirection : &:r75_6, m75_7 +# 75| v75_9(void) = ReturnVoid : +# 75| v75_10(void) = UnmodeledUse : mu* +# 75| v75_11(void) = AliasedUse : ~m82_1 +# 75| v75_12(void) = ExitFunction : # 84| void test05(int, int, void*) # 84| Block 0 @@ -641,8 +633,6 @@ test.cpp: # 84| valnum = m84_9 # 84| m84_11(unknown) = InitializeIndirection[p] : &:r84_10 # 84| valnum = unique -# 84| m84_12(unknown) = Chi : total:m84_2, partial:m84_11 -# 84| valnum = unique # 86| r86_1(glval) = VariableAddress[v] : # 86| valnum = r86_1 # 86| m86_2(int) = Uninitialized[v] : &:r86_1 @@ -671,11 +661,11 @@ test.cpp: # 88| m88_10(int) = Store : &:r88_9, r88_8 # 88| valnum = m88_6 # 89| v89_1(void) = NoOp : -# 84| v84_13(void) = ReturnIndirection : &:r84_10, ~m84_12 -# 84| v84_14(void) = ReturnVoid : -# 84| v84_15(void) = UnmodeledUse : mu* -# 84| v84_16(void) = AliasedUse : ~m84_12 -# 84| v84_17(void) = ExitFunction : +# 84| v84_12(void) = ReturnIndirection : &:r84_10, m84_11 +# 84| v84_13(void) = ReturnVoid : +# 84| v84_14(void) = UnmodeledUse : mu* +# 84| v84_15(void) = AliasedUse : ~m84_2 +# 84| v84_16(void) = ExitFunction : # 88| Block 2 # 88| r88_11(glval) = VariableAddress[x] : @@ -748,8 +738,6 @@ test.cpp: # 104| valnum = m104_5 # 104| m104_7(unknown) = InitializeIndirection[pd] : &:r104_6 # 104| valnum = unique -# 104| m104_8(unknown) = Chi : total:m104_2, partial:m104_7 -# 104| valnum = unique # 105| r105_1(glval) = VariableAddress[x] : # 105| valnum = unique # 105| r105_2(glval) = VariableAddress[pd] : @@ -760,7 +748,7 @@ test.cpp: # 105| valnum = r105_4 # 105| r105_5(glval) = FieldAddress[b] : r105_4 # 105| valnum = r105_5 -# 105| r105_6(int) = Load : &:r105_5, ~m104_8 +# 105| r105_6(int) = Load : &:r105_5, ~m104_7 # 105| valnum = r105_6 # 105| m105_7(int) = Store : &:r105_1, r105_6 # 105| valnum = r105_6 @@ -782,7 +770,7 @@ test.cpp: # 107| valnum = r105_4 # 107| r107_4(glval) = FieldAddress[b] : r107_3 # 107| valnum = r105_5 -# 107| r107_5(int) = Load : &:r107_4, ~m104_8 +# 107| r107_5(int) = Load : &:r107_4, ~m104_7 # 107| valnum = r107_5 # 107| m107_6(int) = Store : &:r107_1, r107_5 # 107| valnum = r107_5 @@ -794,13 +782,13 @@ test.cpp: # 109| valnum = r107_5 # 109| m109_4(int) = Store : &:r109_1, r109_3 # 109| valnum = r107_5 -# 104| v104_9(void) = ReturnIndirection : &:r104_6, ~m104_8 -# 104| r104_10(glval) = VariableAddress[#return] : +# 104| v104_8(void) = ReturnIndirection : &:r104_6, m104_7 +# 104| r104_9(glval) = VariableAddress[#return] : # 104| valnum = r109_1 -# 104| v104_11(void) = ReturnValue : &:r104_10, m109_4 -# 104| v104_12(void) = UnmodeledUse : mu* -# 104| v104_13(void) = AliasedUse : ~m104_8 -# 104| v104_14(void) = ExitFunction : +# 104| v104_10(void) = ReturnValue : &:r104_9, m109_4 +# 104| v104_11(void) = UnmodeledUse : mu* +# 104| v104_12(void) = AliasedUse : ~m104_2 +# 104| v104_13(void) = ExitFunction : # 112| void test06() # 112| Block 0 diff --git a/cpp/ql/test/query-tests/definitions/definitions.expected b/cpp/ql/test/query-tests/definitions/definitions.expected index 497daf3da05..1da060770e8 100644 --- a/cpp/ql/test/query-tests/definitions/definitions.expected +++ b/cpp/ql/test/query-tests/definitions/definitions.expected @@ -42,7 +42,7 @@ | class.cpp:91:27:91:29 | num | class.cpp:87:17:87:19 | num | V | | class.cpp:100:24:100:34 | type mention | class.cpp:94:18:94:28 | string_type | T | | class.cpp:105:1:105:15 | type mention | class.cpp:97:7:97:21 | StringContainer | T | -| class.cpp:106:9:106:23 | type mention | class.cpp:100:2:100:16 | StringContainer | M | +| class.cpp:106:9:106:23 | type mention | class.cpp:97:7:97:21 | StringContainer | T | | class.cpp:106:25:106:27 | STR(x) | class.cpp:95:1:95:18 | #define STR(x) L ## x | X | | class.cpp:117:2:117:29 | type mention | class.cpp:109:7:109:34 | myClassWithConstructorParams | T | | class.cpp:117:37:117:37 | a | class.cpp:115:27:115:27 | a | V | diff --git a/csharp/ql/src/Likely Bugs/DangerousNonShortCircuitLogic.ql b/csharp/ql/src/Likely Bugs/DangerousNonShortCircuitLogic.ql index f8fcc1d1e12..a789aeab8d7 100644 --- a/csharp/ql/src/Likely Bugs/DangerousNonShortCircuitLogic.ql +++ b/csharp/ql/src/Likely Bugs/DangerousNonShortCircuitLogic.ql @@ -27,7 +27,8 @@ class DangerousExpression extends Expr { e instanceof MethodCall or e instanceof ArrayAccess - ) + ) and + not exists(Expr e | this = e.getParent*() | e.(Call).getTarget().getAParameter().isOutOrRef()) } } diff --git a/csharp/ql/src/semmle/code/csharp/ir/implementation/MemoryAccessKind.qll b/csharp/ql/src/semmle/code/csharp/ir/implementation/MemoryAccessKind.qll index f5c6de314ab..eac4d333afc 100644 --- a/csharp/ql/src/semmle/code/csharp/ir/implementation/MemoryAccessKind.qll +++ b/csharp/ql/src/semmle/code/csharp/ir/implementation/MemoryAccessKind.qll @@ -1,6 +1,7 @@ private newtype TMemoryAccessKind = TIndirectMemoryAccess() or TBufferMemoryAccess() or + TEntireAllocationMemoryAccess() or TEscapedMemoryAccess() or TNonLocalMemoryAccess() or TPhiMemoryAccess() or @@ -43,6 +44,16 @@ class BufferMemoryAccess extends MemoryAccessKind, TBufferMemoryAccess { final override predicate usesAddressOperand() { any() } } +/** + * The operand or results accesses all memory in the contiguous allocation that contains the address + * specified by the `AddressOperand` on the same instruction. + */ +class EntireAllocationMemoryAccess extends MemoryAccessKind, TEntireAllocationMemoryAccess { + override string toString() { result = "alloc" } + + final override predicate usesAddressOperand() { any() } +} + /** * The operand or result accesses all memory whose address has escaped. */ diff --git a/csharp/ql/src/semmle/code/csharp/ir/implementation/Opcode.qll b/csharp/ql/src/semmle/code/csharp/ir/implementation/Opcode.qll index d39d8c30a1a..4b1124cf27e 100644 --- a/csharp/ql/src/semmle/code/csharp/ir/implementation/Opcode.qll +++ b/csharp/ql/src/semmle/code/csharp/ir/implementation/Opcode.qll @@ -232,6 +232,31 @@ abstract class BufferReadOpcode extends BufferAccessOpcode { final override MemoryAccessKind getReadMemoryAccess() { result instanceof BufferMemoryAccess } } +/** + * An opcode that access an entire memory allocation. + */ +abstract class EntireAllocationAccessOpcode extends Opcode { + final override predicate hasAddressOperand() { any() } +} + +/** + * An opcode that write to an entire memory allocation. + */ +abstract class EntireAllocationWriteOpcode extends EntireAllocationAccessOpcode { + final override MemoryAccessKind getWriteMemoryAccess() { + result instanceof EntireAllocationMemoryAccess + } +} + +/** + * An opcode that reads from an entire memory allocation. + */ +abstract class EntireAllocationReadOpcode extends EntireAllocationAccessOpcode { + final override MemoryAccessKind getReadMemoryAccess() { + result instanceof EntireAllocationMemoryAccess + } +} + /** * An opcode that accesses a memory buffer whose size is determined by a `BufferSizeOperand`. */ @@ -325,7 +350,7 @@ module Opcode { final override string toString() { result = "InitializeParameter" } } - class InitializeIndirection extends IndirectWriteOpcode, TInitializeIndirection { + class InitializeIndirection extends EntireAllocationWriteOpcode, TInitializeIndirection { final override string toString() { result = "InitializeIndirection" } } @@ -349,7 +374,7 @@ module Opcode { final override string toString() { result = "ReturnVoid" } } - class ReturnIndirection extends IndirectReadOpcode, TReturnIndirection { + class ReturnIndirection extends EntireAllocationReadOpcode, TReturnIndirection { final override string toString() { result = "ReturnIndirection" } final override predicate hasOperandInternal(OperandTag tag) { diff --git a/csharp/ql/src/semmle/code/csharp/ir/implementation/unaliased_ssa/internal/AliasAnalysis.qll b/csharp/ql/src/semmle/code/csharp/ir/implementation/unaliased_ssa/internal/AliasAnalysis.qll index 6b73b601737..e2d3828fc52 100644 --- a/csharp/ql/src/semmle/code/csharp/ir/implementation/unaliased_ssa/internal/AliasAnalysis.qll +++ b/csharp/ql/src/semmle/code/csharp/ir/implementation/unaliased_ssa/internal/AliasAnalysis.qll @@ -1,40 +1,11 @@ private import AliasAnalysisInternal -private import csharp private import InputIR -private import semmle.code.csharp.ir.internal.IntegerConstant as Ints +private import AliasAnalysisImports private class IntValue = Ints::IntValue; /** - * Converts the bit count in `bits` to a byte count and a bit count in the form - * bytes:bits. - */ -bindingset[bits] -string bitsToBytesAndBits(int bits) { result = (bits / 8).toString() + ":" + (bits % 8).toString() } - -/** - * Gets a printable string for a bit offset with possibly unknown value. - */ -bindingset[bitOffset] -string getBitOffsetString(IntValue bitOffset) { - if Ints::hasValue(bitOffset) - then - if bitOffset >= 0 - then result = "+" + bitsToBytesAndBits(bitOffset) - else result = "-" + bitsToBytesAndBits(Ints::neg(bitOffset)) - else result = "+?" -} - -///** -// * Gets the offset of field `field` in bits. -// */ -//private IntValue getFieldBitOffset(Field field) { -// if field instanceof BitField -// then result = Ints::add(Ints::mul(field.getByteOffset(), 8), field.(BitField).getBitOffset()) -// else result = Ints::mul(field.getByteOffset(), 8) -//} -/** - * Holds if the operand `operand` of instruction `instr` is used in a way that does + * Holds if the operand `tag` of instruction `instr` is used in a way that does * not result in any address held in that operand from escaping beyond the * instruction. */ @@ -51,6 +22,9 @@ private predicate operandIsConsumedWithoutEscaping(Operand operand) { or // Neither operand of a PointerDiff escapes. instr instanceof PointerDiffInstruction + or + // Converting an address to a `bool` does not escape the address. + instr.(ConvertInstruction).getResultIRType() instanceof IRBooleanType ) ) or @@ -64,6 +38,7 @@ private predicate operandEscapesDomain(Operand operand) { not isArgumentForParameter(_, operand, _) and not isOnlyEscapesViaReturnArgument(operand) and not operand.getUse() instanceof ReturnValueInstruction and + not operand.getUse() instanceof ReturnIndirectionInstruction and not operand instanceof PhiInputOperand } @@ -94,7 +69,7 @@ IntValue getPointerBitOffset(PointerOffsetInstruction instr) { } /** - * Holds if any address held in operand `operand` of instruction `instr` is + * Holds if any address held in operand `tag` of instruction `instr` is * propagated to the result of `instr`, offset by the number of bits in * `bitOffset`. If the address is propagated, but the offset is not known to be * a constant, then `bitOffset` is unknown. @@ -103,48 +78,46 @@ private predicate operandIsPropagated(Operand operand, IntValue bitOffset) { exists(Instruction instr | instr = operand.getUse() and ( - // REVIEW: See the REVIEW comment bellow - // // Converting to a non-virtual base class adds the offset of the base class. - // exists(ConvertToNonVirtualBaseInstruction convert | - // convert = instr and - // bitOffset = Ints::mul(convert.getDerivation().getByteOffset(), 8) - // ) - // or - // // Converting to a derived class subtracts the offset of the base class. - // exists(ConvertToDerivedInstruction convert | - // convert = instr and - // bitOffset = Ints::neg(Ints::mul(convert.getDerivation().getByteOffset(), 8)) - // ) - // or - // // Converting to a virtual base class adds an unknown offset. - // instr instanceof ConvertToVirtualBaseInstruction and - // bitOffset = Ints::unknown() - // or - // REVIEW: In the C# IR, we should ignore the above types of conversion all together, - // since first of all they do not provide correct information (nothing is known - // for sure about heap allocated objects) and second of all even if we create a - // virtual memory model for the IR I don't think such conversions provide any meaningful - // information; - // Conversion to another pointer type propagates the source address. - exists(ConvertInstruction convert, Type resultType | + // Converting to a non-virtual base class adds the offset of the base class. + exists(ConvertToNonVirtualBaseInstruction convert | convert = instr and - resultType = convert.getResultType() and - ( - resultType instanceof PointerType or - resultType instanceof RefType - ) and + bitOffset = Ints::mul(convert.getDerivation().getByteOffset(), 8) + ) + or + // Conversion using dynamic_cast results in an unknown offset + instr instanceof CheckedConvertOrNullInstruction and + bitOffset = Ints::unknown() + or + // Converting to a derived class subtracts the offset of the base class. + exists(ConvertToDerivedInstruction convert | + convert = instr and + bitOffset = Ints::neg(Ints::mul(convert.getDerivation().getByteOffset(), 8)) + ) + or + // Converting to a virtual base class adds an unknown offset. + instr instanceof ConvertToVirtualBaseInstruction and + bitOffset = Ints::unknown() + or + // Conversion to another pointer type propagates the source address. + exists(ConvertInstruction convert, IRType resultType | + convert = instr and + resultType = convert.getResultIRType() and + resultType instanceof IRAddressType and bitOffset = 0 ) or // Adding an integer to or subtracting an integer from a pointer propagates // the address with an offset. - bitOffset = getPointerBitOffset(instr.(PointerOffsetInstruction)) + exists(PointerOffsetInstruction ptrOffset | + ptrOffset = instr and + operand = ptrOffset.getLeftOperand() and + bitOffset = getPointerBitOffset(ptrOffset) + ) or // Computing a field address from a pointer propagates the address plus the // offset of the field. - // TODO: Fix once class layout is synthesized - // bitOffset = Ints::unknown() - //or + bitOffset = Language::getFieldBitOffset(instr.(FieldAddressInstruction).getField()) + or // A copy propagates the source value. operand = instr.(CopyInstruction).getSourceValueOperand() and bitOffset = 0 or @@ -224,27 +197,42 @@ private predicate operandReturned(Operand operand, IntValue bitOffset) { } private predicate isArgumentForParameter(CallInstruction ci, Operand operand, Instruction init) { - exists(Callable c | + exists(Language::Function f | ci = operand.getUse() and - c = ci.getStaticCallTarget() and + f = ci.getStaticCallTarget() and ( init.(InitializeParameterInstruction).getParameter() = - c.getParameter(operand.(PositionalArgumentOperand).getIndex()) + f.getParameter(operand.(PositionalArgumentOperand).getIndex()) or init instanceof InitializeThisInstruction and - init.getEnclosingFunction() = c and + init.getEnclosingFunction() = f and operand instanceof ThisArgumentOperand - ) // and - // not f.isVirtual() and - // not f instanceof AliasFunction + ) and + not Language::isFunctionVirtual(f) and + not f instanceof AliasModels::AliasFunction ) } -private predicate isAlwaysReturnedArgument(Operand operand) { none() } +private predicate isAlwaysReturnedArgument(Operand operand) { + exists(AliasModels::AliasFunction f | + f = operand.getUse().(CallInstruction).getStaticCallTarget() and + f.parameterIsAlwaysReturned(operand.(PositionalArgumentOperand).getIndex()) + ) +} -private predicate isOnlyEscapesViaReturnArgument(Operand operand) { none() } +private predicate isOnlyEscapesViaReturnArgument(Operand operand) { + exists(AliasModels::AliasFunction f | + f = operand.getUse().(CallInstruction).getStaticCallTarget() and + f.parameterEscapesOnlyViaReturn(operand.(PositionalArgumentOperand).getIndex()) + ) +} -private predicate isNeverEscapesArgument(Operand operand) { none() } +private predicate isNeverEscapesArgument(Operand operand) { + exists(AliasModels::AliasFunction f | + f = operand.getUse().(CallInstruction).getStaticCallTarget() and + f.parameterNeverEscapes(operand.(PositionalArgumentOperand).getIndex()) + ) +} private predicate resultReturned(Instruction instr, IntValue bitOffset) { operandReturned(instr.getAUse(), bitOffset) @@ -262,52 +250,86 @@ private predicate resultEscapesNonReturn(Instruction instr) { } /** - * Holds if the address of the specified local variable or parameter escapes the - * domain of the analysis. + * Holds if the address of `allocation` escapes outside the domain of the analysis. This can occur + * either because the allocation's address is taken within the function and escapes, or because the + * allocation is marked as always escaping via `alwaysEscapes()`. */ -private predicate automaticVariableAddressEscapes(IRAutomaticVariable var) { - // The variable's address escapes if the result of any - // VariableAddressInstruction that computes the variable's address escapes. - exists(VariableAddressInstruction instr | - instr.getIRVariable() = var and - resultEscapesNonReturn(instr) +predicate allocationEscapes(Configuration::Allocation allocation) { + allocation.alwaysEscapes() + or + exists(IREscapeAnalysisConfiguration config | + config.useSoundEscapeAnalysis() and resultEscapesNonReturn(allocation.getABaseInstruction()) ) } /** - * Holds if the address of the specified variable escapes the domain of the - * analysis. + * Equivalent to `operandIsPropagated()`, but includes interprocedural propagation. */ -predicate variableAddressEscapes(IRVariable var) { - automaticVariableAddressEscapes(var.(IRAutomaticVariable)) +private predicate operandIsPropagatedIncludingByCall(Operand operand, IntValue bitOffset) { + operandIsPropagated(operand, bitOffset) or - // All variables with static storage duration have their address escape. - not var instanceof IRAutomaticVariable + exists(CallInstruction call, Instruction init | + isArgumentForParameter(call, operand, init) and + resultReturned(init, bitOffset) + ) } /** - * Holds if the result of instruction `instr` points within variable `var`, at - * bit offset `bitOffset` within the variable. If the result points within - * `var`, but at an unknown or non-constant offset, then `bitOffset` is unknown. + * Holds if `addrOperand` is at offset `bitOffset` from the value of instruction `base`. The offset + * may be `unknown()`. */ -predicate resultPointsTo(Instruction instr, IRVariable var, IntValue bitOffset) { - // The address of a variable points to that variable, at offset 0. - instr.(VariableAddressInstruction).getIRVariable() = var and - bitOffset = 0 +private predicate hasBaseAndOffset(AddressOperand addrOperand, Instruction base, IntValue bitOffset) { + base = addrOperand.getDef() and bitOffset = 0 // Base case or - exists(Operand operand, IntValue originalBitOffset, IntValue propagatedBitOffset | - operand = instr.getAnOperand() and - // If an operand is propagated, then the result points to the same variable, - // offset by the bit offset from the propagation. - resultPointsTo(operand.getAnyDef(), var, originalBitOffset) and - ( - operandIsPropagated(operand, propagatedBitOffset) - or - exists(CallInstruction ci, Instruction init | - isArgumentForParameter(ci, operand, init) and - resultReturned(init, propagatedBitOffset) - ) - ) and - bitOffset = Ints::add(originalBitOffset, propagatedBitOffset) + exists( + Instruction middle, int previousBitOffset, Operand middleOperand, IntValue additionalBitOffset + | + // We already have an offset from `middle`. + hasBaseAndOffset(addrOperand, middle, previousBitOffset) and + // `middle` is propagated from `base`. + middleOperand = middle.getAnOperand() and + operandIsPropagatedIncludingByCall(middleOperand, additionalBitOffset) and + base = middleOperand.getDef() and + bitOffset = Ints::add(previousBitOffset, additionalBitOffset) + ) +} + +/** + * Holds if `addrOperand` is at constant offset `bitOffset` from the value of instruction `base`. + * Only holds for the `base` with the longest chain of propagation to `addrOperand`. + */ +predicate addressOperandBaseAndConstantOffset( + AddressOperand addrOperand, Instruction base, int bitOffset +) { + hasBaseAndOffset(addrOperand, base, bitOffset) and + Ints::hasValue(bitOffset) and + not exists(Instruction previousBase, int previousBitOffset | + hasBaseAndOffset(addrOperand, previousBase, previousBitOffset) and + previousBase = base.getAnOperand().getDef() and + Ints::hasValue(previousBitOffset) + ) +} + +/** + * Gets the allocation into which `addrOperand` points, if known. + */ +Configuration::Allocation getAddressOperandAllocation(AddressOperand addrOperand) { + addressOperandAllocationAndOffset(addrOperand, result, _) +} + +/** + * Holds if `addrOperand` is at offset `bitOffset` from a base instruction of `allocation`. The + * offset may be `unknown()`. + */ +predicate addressOperandAllocationAndOffset( + AddressOperand addrOperand, Configuration::Allocation allocation, IntValue bitOffset +) { + exists(Instruction base | + allocation.getABaseInstruction() = base and + hasBaseAndOffset(addrOperand, base, bitOffset) and + not exists(Instruction previousBase | + hasBaseAndOffset(addrOperand, previousBase, _) and + previousBase = base.getAnOperand().getDef() + ) ) } diff --git a/csharp/ql/src/semmle/code/csharp/ir/implementation/unaliased_ssa/internal/AliasAnalysisImports.qll b/csharp/ql/src/semmle/code/csharp/ir/implementation/unaliased_ssa/internal/AliasAnalysisImports.qll new file mode 100644 index 00000000000..11d7d37063e --- /dev/null +++ b/csharp/ql/src/semmle/code/csharp/ir/implementation/unaliased_ssa/internal/AliasAnalysisImports.qll @@ -0,0 +1,48 @@ +private import csharp +import semmle.code.csharp.ir.implementation.IRConfiguration +import semmle.code.csharp.ir.internal.IntegerConstant as Ints + +module AliasModels { + /** + * Models the aliasing behavior of a library function. + */ + abstract class AliasFunction extends Callable { + /** + * Holds if the address passed to the parameter at the specified index is never retained after + * the function returns. + * + * Example: + * ``` + * int* g; + * int* func(int* p, int* q, int* r, int* s, int n) { + * *s = 1; // `s` does not escape. + * g = p; // Stored in global. `p` escapes. + * if (rand()) { + * return q; // `q` escapes via the return value. + * } + * else { + * return r + n; // `r` escapes via the return value, even though an offset has been added. + * } + * } + * ``` + * + * For the above function, the following terms hold: + * - `parameterEscapesOnlyViaReturn(1)` + * - `parameterEscapesOnlyViaReturn(2)` + * - `parameterNeverEscapes(3)` + */ + abstract predicate parameterNeverEscapes(int index); + + /** + * Holds if the address passed to the parameter at the specified index escapes via the return + * value of the function, but does not otherwise escape. See the comment for + * `parameterNeverEscapes` for an example. + */ + abstract predicate parameterEscapesOnlyViaReturn(int index); + + /** + * Holds if the function always returns the value of the parameter at the specified index. + */ + abstract predicate parameterIsAlwaysReturned(int index); + } +} diff --git a/csharp/ql/src/semmle/code/csharp/ir/implementation/unaliased_ssa/internal/AliasAnalysisInternal.qll b/csharp/ql/src/semmle/code/csharp/ir/implementation/unaliased_ssa/internal/AliasAnalysisInternal.qll index 64d671c76c4..e7884ce20b6 100644 --- a/csharp/ql/src/semmle/code/csharp/ir/implementation/unaliased_ssa/internal/AliasAnalysisInternal.qll +++ b/csharp/ql/src/semmle/code/csharp/ir/implementation/unaliased_ssa/internal/AliasAnalysisInternal.qll @@ -1 +1,3 @@ +import semmle.code.csharp.ir.internal.IRCSharpLanguage as Language import semmle.code.csharp.ir.implementation.raw.IR as InputIR +import AliasConfiguration as Configuration diff --git a/csharp/ql/src/semmle/code/csharp/ir/implementation/unaliased_ssa/internal/AliasConfiguration.qll b/csharp/ql/src/semmle/code/csharp/ir/implementation/unaliased_ssa/internal/AliasConfiguration.qll new file mode 100644 index 00000000000..5be476e12ee --- /dev/null +++ b/csharp/ql/src/semmle/code/csharp/ir/implementation/unaliased_ssa/internal/AliasConfiguration.qll @@ -0,0 +1,16 @@ +private import AliasConfigurationImports + +/** + * A memory allocation that can be tracked by the SimpleSSA alias analysis. + * All automatic variables are tracked. + */ +class Allocation extends IRAutomaticVariable { + VariableAddressInstruction getABaseInstruction() { result.getIRVariable() = this } + + final string getAllocationString() { result = toString() } + + predicate alwaysEscapes() { + // An automatic variable only escapes if its address is taken and escapes. + none() + } +} diff --git a/csharp/ql/src/semmle/code/csharp/ir/implementation/unaliased_ssa/internal/AliasConfigurationImports.qll b/csharp/ql/src/semmle/code/csharp/ir/implementation/unaliased_ssa/internal/AliasConfigurationImports.qll new file mode 100644 index 00000000000..0db2b956610 --- /dev/null +++ b/csharp/ql/src/semmle/code/csharp/ir/implementation/unaliased_ssa/internal/AliasConfigurationImports.qll @@ -0,0 +1 @@ +import semmle.code.csharp.ir.implementation.raw.IR diff --git a/csharp/ql/src/semmle/code/csharp/ir/implementation/unaliased_ssa/internal/SimpleSSA.qll b/csharp/ql/src/semmle/code/csharp/ir/implementation/unaliased_ssa/internal/SimpleSSA.qll index ddff6444b90..7de1ab8d72e 100644 --- a/csharp/ql/src/semmle/code/csharp/ir/implementation/unaliased_ssa/internal/SimpleSSA.qll +++ b/csharp/ql/src/semmle/code/csharp/ir/implementation/unaliased_ssa/internal/SimpleSSA.qll @@ -1,66 +1,57 @@ import AliasAnalysis private import SimpleSSAImports import SimpleSSAPublicImports +private import AliasConfiguration -private class IntValue = Ints::IntValue; - -private predicate hasResultMemoryAccess( - Instruction instr, IRVariable var, Language::LanguageType type, IntValue bitOffset -) { - resultPointsTo(instr.getResultAddressOperand().getAnyDef(), var, bitOffset) and - type = instr.getResultLanguageType() -} - -private predicate hasOperandMemoryAccess( - MemoryOperand operand, IRVariable var, Language::LanguageType type, IntValue bitOffset -) { - resultPointsTo(operand.getAddressOperand().getAnyDef(), var, bitOffset) and - type = operand.getLanguageType() -} - -/** - * Holds if the specified variable should be modeled in SSA form. For unaliased SSA, we only model a variable if its - * address never escapes and all reads and writes of that variable access the entire variable using the original type - * of the variable. - */ -private predicate isVariableModeled(IRVariable var) { - not variableAddressEscapes(var) and - // There's no need to check for the right size. An `IRVariable` never has an `UnknownType`, so the test for - // `type = var.getType()` is sufficient. - forall(Instruction instr, Language::LanguageType type, IntValue bitOffset | - hasResultMemoryAccess(instr, var, type, bitOffset) and - not instr.hasResultMayMemoryAccess() - | +private predicate isTotalAccess(Allocation var, AddressOperand addrOperand, IRType type) { + exists(Instruction constantBase, int bitOffset | + addressOperandBaseAndConstantOffset(addrOperand, constantBase, bitOffset) and bitOffset = 0 and - type.getIRType() = var.getIRType() and - not instr.hasResultMayMemoryAccess() - ) and - forall(MemoryOperand operand, Language::LanguageType type, IntValue bitOffset | - hasOperandMemoryAccess(operand, var, type, bitOffset) - | - bitOffset = 0 and - type.getIRType() = var.getIRType() and - not operand.hasMayReadMemoryAccess() + constantBase = var.getABaseInstruction() and + type = var.getIRType() ) } -private newtype TMemoryLocation = MkMemoryLocation(IRVariable var) { isVariableModeled(var) } +/** + * Holds if the specified variable should be modeled in SSA form. For unaliased SSA, we only model a + * variable if its address never escapes and all reads and writes of that variable access the entire + * variable using the original type of the variable. + */ +private predicate isVariableModeled(Allocation var) { + not allocationEscapes(var) and + forall(Instruction instr, AddressOperand addrOperand, IRType type | + addrOperand = instr.getResultAddressOperand() and + type = instr.getResultIRType() and + var = getAddressOperandAllocation(addrOperand) + | + isTotalAccess(var, addrOperand, type) and not instr.hasResultMayMemoryAccess() + ) and + forall(MemoryOperand memOperand, AddressOperand addrOperand, IRType type | + addrOperand = memOperand.getAddressOperand() and + type = memOperand.getIRType() and + var = getAddressOperandAllocation(addrOperand) + | + isTotalAccess(var, addrOperand, type) and not memOperand.hasMayReadMemoryAccess() + ) +} -private MemoryLocation getMemoryLocation(IRVariable var) { result.getIRVariable() = var } +private newtype TMemoryLocation = MkMemoryLocation(Allocation var) { isVariableModeled(var) } + +private MemoryLocation getMemoryLocation(Allocation var) { result.getAllocation() = var } class MemoryLocation extends TMemoryLocation { - IRVariable var; + Allocation var; MemoryLocation() { this = MkMemoryLocation(var) } - final string toString() { result = var.toString() } + final string toString() { result = var.getAllocationString() } + + final Allocation getAllocation() { result = var } final Language::Location getLocation() { result = var.getLocation() } final IRFunction getIRFunction() { result = var.getEnclosingIRFunction() } - final IRVariable getIRVariable() { result = var } - final VirtualVariable getVirtualVariable() { result = this } final Language::LanguageType getType() { result = var.getLanguageType() } @@ -77,15 +68,9 @@ Overlap getOverlap(MemoryLocation def, MemoryLocation use) { } MemoryLocation getResultMemoryLocation(Instruction instr) { - exists(IRVariable var | - hasResultMemoryAccess(instr, var, _, _) and - result = getMemoryLocation(var) - ) + result = getMemoryLocation(getAddressOperandAllocation(instr.getResultAddressOperand())) } MemoryLocation getOperandMemoryLocation(MemoryOperand operand) { - exists(IRVariable var | - hasOperandMemoryAccess(operand, var, _, _) and - result = getMemoryLocation(var) - ) + result = getMemoryLocation(getAddressOperandAllocation(operand.getAddressOperand())) } diff --git a/csharp/ql/src/semmle/code/csharp/ir/internal/IRCSharpLanguage.qll b/csharp/ql/src/semmle/code/csharp/ir/internal/IRCSharpLanguage.qll index 97121f46453..cd4e96a25a3 100644 --- a/csharp/ql/src/semmle/code/csharp/ir/internal/IRCSharpLanguage.qll +++ b/csharp/ql/src/semmle/code/csharp/ir/internal/IRCSharpLanguage.qll @@ -101,3 +101,16 @@ predicate hasPotentialLoop(Function f) { } predicate hasGoto(Function f) { exists(CSharp::GotoStmt s | s.getEnclosingCallable() = f) } + +/** + * Gets the offset of field `field` in bits. + */ +int getFieldBitOffset(Field f) { + //REVIEW: Implement this once layout has been synthesized. + none() +} + +/** + * Holds if the specified `Function` can be overridden in a derived class. + */ +predicate isFunctionVirtual(Function f) { f.(CSharp::Virtualizable).isOverridableOrImplementable() } diff --git a/csharp/ql/test/query-tests/Likely Bugs/DangerousNonShortCircuitLogic/DangerousNonShortCircuitLogic.cs b/csharp/ql/test/query-tests/Likely Bugs/DangerousNonShortCircuitLogic/DangerousNonShortCircuitLogic.cs index d52d45397f6..0d10e11b7f6 100644 --- a/csharp/ql/test/query-tests/Likely Bugs/DangerousNonShortCircuitLogic/DangerousNonShortCircuitLogic.cs +++ b/csharp/ql/test/query-tests/Likely Bugs/DangerousNonShortCircuitLogic/DangerousNonShortCircuitLogic.cs @@ -20,6 +20,9 @@ class Test var b = true; b &= c.Method(); // GOOD b |= c[0]; // GOOD + + if (c == null | c.Method(out _)) ; // GOOD + if (c == null | (c.Method() | c.Method(out _))) ; // GOOD } class C @@ -28,6 +31,7 @@ class Test public string Property { get; set; } public bool this[int i] { get { return false; } set { } } public bool Method() { return false; } + public bool Method(out int x) { x = 0; return false; } } } diff --git a/java/ql/src/Language Abuse/UselessUpcast.ql b/java/ql/src/Language Abuse/UselessUpcast.ql index 5c34d4d4cff..d8b5761c909 100644 --- a/java/ql/src/Language Abuse/UselessUpcast.ql +++ b/java/ql/src/Language Abuse/UselessUpcast.ql @@ -15,7 +15,7 @@ import java predicate usefulUpcast(CastExpr e) { // Upcasts that may be performed to affect resolution of methods or constructors. exists(Call c, int i, Callable target | - c.getArgument(i).getProperExpr() = e and + c.getArgument(i) = e and target = c.getCallee() and // An upcast to the type of the corresponding parameter. e.getType() = target.getParameterType(i) @@ -31,14 +31,12 @@ predicate usefulUpcast(CastExpr e) { ) or // Upcasts of a varargs argument. - exists(Call c, int iArg, int iParam | c.getArgument(iArg).getProperExpr() = e | + exists(Call c, int iArg, int iParam | c.getArgument(iArg) = e | c.getCallee().getParameter(iParam).isVarargs() and iArg >= iParam ) or // Upcasts that are performed on an operand of a ternary expression. - exists(ConditionalExpr ce | - e = ce.getTrueExpr().getProperExpr() or e = ce.getFalseExpr().getProperExpr() - ) + exists(ConditionalExpr ce | e = ce.getTrueExpr() or e = ce.getFalseExpr()) or // Upcasts to raw types. e.getType() instanceof RawType @@ -46,12 +44,12 @@ predicate usefulUpcast(CastExpr e) { e.getType().(Array).getElementType() instanceof RawType or // Upcasts that are performed to affect field, private method, or static method resolution. - exists(FieldAccess fa | e = fa.getQualifier().getProperExpr() | + exists(FieldAccess fa | e = fa.getQualifier() | not e.getExpr().getType().(RefType).inherits(fa.getField()) ) or exists(MethodAccess ma, Method m | - e = ma.getQualifier().getProperExpr() and + e = ma.getQualifier() and m = ma.getMethod() and (m.isStatic() or m.isPrivate()) | diff --git a/java/ql/src/Likely Bugs/Arithmetic/BadCheckOdd.ql b/java/ql/src/Likely Bugs/Arithmetic/BadCheckOdd.ql index ead6919f8be..70f34bcb077 100644 --- a/java/ql/src/Likely Bugs/Arithmetic/BadCheckOdd.ql +++ b/java/ql/src/Likely Bugs/Arithmetic/BadCheckOdd.ql @@ -15,7 +15,7 @@ import java import semmle.code.java.Collections predicate isDefinitelyPositive(Expr e) { - isDefinitelyPositive(e.getProperExpr()) or + isDefinitelyPositive(e) or e.(IntegerLiteral).getIntValue() >= 0 or e.(MethodAccess).getMethod() instanceof CollectionSizeMethod or e.(MethodAccess).getMethod() instanceof StringLengthMethod or @@ -24,10 +24,10 @@ predicate isDefinitelyPositive(Expr e) { from BinaryExpr t, RemExpr lhs, IntegerLiteral rhs, string parity where - t.getLeftOperand().getProperExpr() = lhs and - t.getRightOperand().getProperExpr() = rhs and + t.getLeftOperand() = lhs and + t.getRightOperand() = rhs and not isDefinitelyPositive(lhs.getLeftOperand()) and - lhs.getRightOperand().getProperExpr().(IntegerLiteral).getIntValue() = 2 and + lhs.getRightOperand().(IntegerLiteral).getIntValue() = 2 and ( t instanceof EQExpr and rhs.getIntValue() = 1 and parity = "oddness" or diff --git a/java/ql/src/Likely Bugs/Arithmetic/ConstantExpAppearsNonConstant.ql b/java/ql/src/Likely Bugs/Arithmetic/ConstantExpAppearsNonConstant.ql index fb55aeb7b2b..90471c9d0c5 100644 --- a/java/ql/src/Likely Bugs/Arithmetic/ConstantExpAppearsNonConstant.ql +++ b/java/ql/src/Likely Bugs/Arithmetic/ConstantExpAppearsNonConstant.ql @@ -17,9 +17,6 @@ predicate isConstantExp(Expr e) { // A literal is constant. e instanceof Literal or - // A parenthesized expression is constant if its proper expression is. - isConstantExp(e.(ParExpr).getProperExpr()) - or e instanceof TypeAccess or e instanceof ArrayTypeAccess @@ -33,26 +30,22 @@ predicate isConstantExp(Expr e) { ) or // A cast expression is constant if its expression is. - exists(CastExpr c | c = e | isConstantExp(c.getExpr().getProperExpr())) + exists(CastExpr c | c = e | isConstantExp(c.getExpr())) or // Multiplication by 0 is constant. - exists(MulExpr m | m = e | eval(m.getAnOperand().getProperExpr()) = 0) + exists(MulExpr m | m = e | eval(m.getAnOperand()) = 0) or // Integer remainder by 1 is constant. exists(RemExpr r | r = e | r.getLeftOperand().getType() instanceof IntegralType and - eval(r.getRightOperand().getProperExpr()) = 1 + eval(r.getRightOperand()) = 1 ) or - exists(AndBitwiseExpr a | a = e | eval(a.getAnOperand().getProperExpr()) = 0) + exists(AndBitwiseExpr a | a = e | eval(a.getAnOperand()) = 0) or - exists(AndLogicalExpr a | a = e | - a.getAnOperand().getProperExpr().(BooleanLiteral).getBooleanValue() = false - ) + exists(AndLogicalExpr a | a = e | a.getAnOperand().(BooleanLiteral).getBooleanValue() = false) or - exists(OrLogicalExpr o | o = e | - o.getAnOperand().getProperExpr().(BooleanLiteral).getBooleanValue() = true - ) + exists(OrLogicalExpr o | o = e | o.getAnOperand().(BooleanLiteral).getBooleanValue() = true) } from Expr e diff --git a/java/ql/src/Likely Bugs/Arithmetic/IntMultToLong.ql b/java/ql/src/Likely Bugs/Arithmetic/IntMultToLong.ql index 9371e57b029..d6b7b97199e 100644 --- a/java/ql/src/Likely Bugs/Arithmetic/IntMultToLong.ql +++ b/java/ql/src/Likely Bugs/Arithmetic/IntMultToLong.ql @@ -35,8 +35,8 @@ float exprBound(Expr e) { /** A multiplication that does not overflow. */ predicate small(MulExpr e) { exists(NumType t, float lhs, float rhs, float res | t = e.getType() | - lhs = exprBound(e.getLeftOperand().getProperExpr()) and - rhs = exprBound(e.getRightOperand().getProperExpr()) and + lhs = exprBound(e.getLeftOperand()) and + rhs = exprBound(e.getRightOperand()) and lhs * rhs = res and res <= t.getOrdPrimitiveType().getMaxValue() ) @@ -47,7 +47,7 @@ predicate small(MulExpr e) { */ Expr getRestrictedParent(Expr e) { result = e.getParent() and - (result instanceof ArithExpr or result instanceof ConditionalExpr or result instanceof ParExpr) + (result instanceof ArithExpr or result instanceof ConditionalExpr) } from ConversionSite c, MulExpr e, NumType sourceType, NumType destType diff --git a/java/ql/src/Likely Bugs/Comparison/DefineEqualsWhenAddingFields.ql b/java/ql/src/Likely Bugs/Comparison/DefineEqualsWhenAddingFields.ql index c32095ccbb3..be186d2b2e2 100644 --- a/java/ql/src/Likely Bugs/Comparison/DefineEqualsWhenAddingFields.ql +++ b/java/ql/src/Likely Bugs/Comparison/DefineEqualsWhenAddingFields.ql @@ -32,11 +32,11 @@ predicate checksReferenceEquality(EqualsMethod em) { eq.getAnOperand().(VarAccess).getVariable() = em.getParameter(0) and ( // `{ return (ojb==this); }` - eq = blk.getStmt().(ReturnStmt).getResult().getProperExpr() + eq = blk.getStmt().(ReturnStmt).getResult() or // `{ if (ojb==this) return true; else return false; }` exists(IfStmt ifStmt | ifStmt = blk.getStmt() | - eq = ifStmt.getCondition().getProperExpr() and + eq = ifStmt.getCondition() and ifStmt.getThen().(ReturnStmt).getResult().(BooleanLiteral).getBooleanValue() = true and ifStmt.getElse().(ReturnStmt).getResult().(BooleanLiteral).getBooleanValue() = false ) diff --git a/java/ql/src/Likely Bugs/Comparison/MissingInstanceofInEquals.ql b/java/ql/src/Likely Bugs/Comparison/MissingInstanceofInEquals.ql index eb044e4ecb0..eacba3ad4ec 100644 --- a/java/ql/src/Likely Bugs/Comparison/MissingInstanceofInEquals.ql +++ b/java/ql/src/Likely Bugs/Comparison/MissingInstanceofInEquals.ql @@ -45,7 +45,7 @@ class ReferenceEquals extends EqualsMethod { exists(Block b, ReturnStmt ret, EQExpr eq | this.getBody() = b and b.getStmt(0) = ret and - ret.getResult().getProperExpr() = eq and + ret.getResult() = eq and eq.getAnOperand() = this.getAParameter().getAnAccess() and (eq.getAnOperand() instanceof ThisAccess or eq.getAnOperand() instanceof FieldAccess) ) diff --git a/java/ql/src/Likely Bugs/Comparison/NoAssignInBooleanExprs.ql b/java/ql/src/Likely Bugs/Comparison/NoAssignInBooleanExprs.ql index b04f8de49b5..600257d1c60 100644 --- a/java/ql/src/Likely Bugs/Comparison/NoAssignInBooleanExprs.ql +++ b/java/ql/src/Likely Bugs/Comparison/NoAssignInBooleanExprs.ql @@ -26,7 +26,7 @@ class BooleanExpr extends Expr { } private predicate assignAndCheck(AssignExpr e) { - exists(BinaryExpr c | e = c.getAChildExpr().getProperExpr() | + exists(BinaryExpr c | e = c.getAChildExpr() | c instanceof ComparisonExpr or c instanceof EqualityTest ) diff --git a/java/ql/src/Likely Bugs/Comparison/StringComparison.ql b/java/ql/src/Likely Bugs/Comparison/StringComparison.ql index 1bb67c04a67..4681feafc00 100644 --- a/java/ql/src/Likely Bugs/Comparison/StringComparison.ql +++ b/java/ql/src/Likely Bugs/Comparison/StringComparison.ql @@ -24,9 +24,6 @@ class StringValue extends Expr { this.(MethodAccess).getMethod() = intern ) or - // Parenthesized expressions. - this.(ParExpr).getExpr().(StringValue).isInterned() - or // Ternary conditional operator. this.(ConditionalExpr).getTrueExpr().(StringValue).isInterned() and this.(ConditionalExpr).getFalseExpr().(StringValue).isInterned() @@ -52,7 +49,7 @@ predicate variableValuesInterned(Variable v) { not v instanceof Parameter and // If the string is modified with `+=`, then the new string is not interned // even if the components are. - not exists(AssignOp append | append.getDest().getProperExpr() = v.getAnAccess()) + not exists(AssignOp append | append.getDest() = v.getAnAccess()) } from EqualityTest e, StringValue lhs, StringValue rhs diff --git a/java/ql/src/Likely Bugs/Comparison/UselessComparisonTest.ql b/java/ql/src/Likely Bugs/Comparison/UselessComparisonTest.ql index af37df936e0..964a5a6060a 100644 --- a/java/ql/src/Likely Bugs/Comparison/UselessComparisonTest.ql +++ b/java/ql/src/Likely Bugs/Comparison/UselessComparisonTest.ql @@ -127,8 +127,6 @@ Expr overFlowCand() { or exists(SsaExplicitUpdate x | result = x.getAUse() and x.getDefiningExpr() = overFlowCand()) or - result.(ParExpr).getExpr() = overFlowCand() - or result.(AssignExpr).getRhs() = overFlowCand() or result.(LocalVariableDeclExpr).getInit() = overFlowCand() @@ -165,8 +163,6 @@ Expr increaseOrDecreaseOfVar(SsaVariable v) { result = x.getAUse() and x.getDefiningExpr() = increaseOrDecreaseOfVar(v) ) or - result.(ParExpr).getExpr() = increaseOrDecreaseOfVar(v) - or result.(AssignExpr).getRhs() = increaseOrDecreaseOfVar(v) or result.(LocalVariableDeclExpr).getInit() = increaseOrDecreaseOfVar(v) diff --git a/java/ql/src/Likely Bugs/Concurrency/DoubleCheckedLocking.qll b/java/ql/src/Likely Bugs/Concurrency/DoubleCheckedLocking.qll index 63293099c5b..ac279049ed1 100644 --- a/java/ql/src/Likely Bugs/Concurrency/DoubleCheckedLocking.qll +++ b/java/ql/src/Likely Bugs/Concurrency/DoubleCheckedLocking.qll @@ -13,8 +13,6 @@ private Expr getAFieldRead(Field f) { v.getDefiningExpr().(VariableAssign).getSource() = getAFieldRead(f) ) or - result.(ParExpr).getExpr() = getAFieldRead(f) - or result.(AssignExpr).getSource() = getAFieldRead(f) } diff --git a/java/ql/src/Likely Bugs/Concurrency/NonSynchronizedOverride.ql b/java/ql/src/Likely Bugs/Concurrency/NonSynchronizedOverride.ql index de7e5bc5f6c..b6518ba9bdb 100644 --- a/java/ql/src/Likely Bugs/Concurrency/NonSynchronizedOverride.ql +++ b/java/ql/src/Likely Bugs/Concurrency/NonSynchronizedOverride.ql @@ -27,8 +27,6 @@ predicate delegatingSuperCall(Expr e, Method target) { ) or delegatingSuperCall(e.(CastExpr).getExpr(), target) - or - delegatingSuperCall(e.(ParExpr).getExpr(), target) } /** diff --git a/java/ql/src/Likely Bugs/Concurrency/SynchSetUnsynchGet.ql b/java/ql/src/Likely Bugs/Concurrency/SynchSetUnsynchGet.ql index df620ca08d8..8f91ae89211 100644 --- a/java/ql/src/Likely Bugs/Concurrency/SynchSetUnsynchGet.ql +++ b/java/ql/src/Likely Bugs/Concurrency/SynchSetUnsynchGet.ql @@ -22,9 +22,7 @@ import java * (for static methods) or a `synchronized(this){...}` block (for instance methods). */ predicate isSynchronizedByBlock(Method m) { - exists(SynchronizedStmt sync, Expr on | - sync = m.getBody().getAChild*() and on = sync.getExpr().getProperExpr() - | + exists(SynchronizedStmt sync, Expr on | sync = m.getBody().getAChild*() and on = sync.getExpr() | if m.isStatic() then on.(TypeLiteral).getTypeName().getType() = m.getDeclaringType() else on.(ThisAccess).getType().(RefType).getSourceDeclaration() = m.getDeclaringType() diff --git a/java/ql/src/Likely Bugs/Likely Typos/ContradictoryTypeChecks.ql b/java/ql/src/Likely Bugs/Likely Typos/ContradictoryTypeChecks.ql index 6a9fc9b7051..73cb99be0c3 100644 --- a/java/ql/src/Likely Bugs/Likely Typos/ContradictoryTypeChecks.ql +++ b/java/ql/src/Likely Bugs/Likely Typos/ContradictoryTypeChecks.ql @@ -17,7 +17,7 @@ import semmle.code.java.dataflow.SSA /** `ioe` is of the form `va instanceof t`. */ predicate instanceOfCheck(InstanceOfExpr ioe, VarAccess va, RefType t) { - ioe.getExpr().getProperExpr() = va and + ioe.getExpr() = va and ioe.getTypeName().getType().(RefType).getSourceDeclaration() = t } diff --git a/java/ql/src/Likely Bugs/Resource Leaks/CloseType.qll b/java/ql/src/Likely Bugs/Resource Leaks/CloseType.qll index c319f2568d6..16f8d6f8bcc 100644 --- a/java/ql/src/Likely Bugs/Resource Leaks/CloseType.qll +++ b/java/ql/src/Likely Bugs/Resource Leaks/CloseType.qll @@ -9,8 +9,6 @@ import semmle.code.java.frameworks.Mockito private predicate flowsInto(Expr e, Variable v) { e = v.getAnAssignedValue() or - exists(ParExpr p | flowsInto(p, v) | e = p.getExpr()) - or exists(CastExpr c | flowsInto(c, v) | e = c.getExpr()) or exists(ConditionalExpr c | flowsInto(c, v) | e = c.getTrueExpr() or e = c.getFalseExpr()) diff --git a/java/ql/src/Likely Bugs/Statements/Chaining.qll b/java/ql/src/Likely Bugs/Statements/Chaining.qll index 315f0e32c09..1e1c7187e30 100644 --- a/java/ql/src/Likely Bugs/Statements/Chaining.qll +++ b/java/ql/src/Likely Bugs/Statements/Chaining.qll @@ -48,7 +48,7 @@ private predicate nonChainingReturn(Method m, ReturnStmt ret) { or // A method on the wrong object is called. not ( - delegateCall.getQualifier().getProperExpr() instanceof ThisAccess or + delegateCall.getQualifier() instanceof ThisAccess or not exists(delegateCall.getQualifier()) ) or diff --git a/java/ql/src/Likely Bugs/Statements/ReturnValueIgnored.ql b/java/ql/src/Likely Bugs/Statements/ReturnValueIgnored.ql index 88d786a7159..d2c2c555ae2 100644 --- a/java/ql/src/Likely Bugs/Statements/ReturnValueIgnored.ql +++ b/java/ql/src/Likely Bugs/Statements/ReturnValueIgnored.ql @@ -78,7 +78,7 @@ predicate relevantMethodCall(MethodAccess ma, Method m) { ma.getMethod() = m and not m.getReturnType().hasName("void") and (not isMockingMethod(m) or isMustBeQualifierMockingMethod(m)) and - not isMockingMethod(ma.getQualifier().getProperExpr().(MethodAccess).getMethod()) + not isMockingMethod(ma.getQualifier().(MethodAccess).getMethod()) } predicate methodStats(Method m, int used, int total, int percentage) { diff --git a/java/ql/src/Security/CWE/CWE-089/SqlInjectionLib.qll b/java/ql/src/Security/CWE/CWE-089/SqlInjectionLib.qll index 3d2e2b91434..2572d91cb99 100644 --- a/java/ql/src/Security/CWE/CWE-089/SqlInjectionLib.qll +++ b/java/ql/src/Security/CWE/CWE-089/SqlInjectionLib.qll @@ -54,7 +54,9 @@ private class QueryInjectionFlowConfig extends TaintTracking::Configuration { override predicate isSink(DataFlow::Node sink) { sink instanceof QueryInjectionSink } override predicate isSanitizer(DataFlow::Node node) { - node.getType() instanceof PrimitiveType or node.getType() instanceof BoxedType + node.getType() instanceof PrimitiveType or + node.getType() instanceof BoxedType or + node.getType() instanceof NumberType } } diff --git a/java/ql/src/Security/CWE/CWE-090/LdapInjection.qhelp b/java/ql/src/Security/CWE/CWE-090/LdapInjection.qhelp new file mode 100644 index 00000000000..af066852d88 --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-090/LdapInjection.qhelp @@ -0,0 +1,63 @@ + + + +

If an LDAP query is built using string concatenation, and the +components of the concatenation include user input, a user +is likely to be able to run malicious LDAP queries.

+
+ + +

If user input must be included in an LDAP query, it should be escaped to +avoid a malicious user providing special characters that change the meaning +of the query. If possible build the LDAP query using framework helper methods, for example +from Spring's LdapQueryBuilder and LdapNameBuilder, +instead of string concatenation. Alternatively, escape user input using an appropriate +LDAP encoding method, for example: encodeForLDAP or encodeForDN +from OWASP ESAPI, LdapEncoder.filterEncode or LdapEncoder.nameEncode +from Spring LDAP, or Filter.encodeValue from UnboundID library.

+
+ + +

In the following examples, the code accepts an "organization name" and a "username" +from the user, which it uses to query LDAP.

+ +

The first example concatenates the unvalidated and unencoded user input directly +into both the DN (Distinguished Name) and the search filter used for the LDAP query. +A malicious user could provide special characters to change the meaning of these +queries, and search for a completely different set of values. The LDAP query is executed +using Java JNDI API. +

+ +

The second example uses the OWASP ESAPI library to encode the user values +before they are included in the DN and search filters. This ensures the meaning of +the query cannot be changed by a malicious user.

+ + + +

The third example uses Spring LdapQueryBuilder to build an LDAP query. In addition to +simplifying the building of complex search parameters, it also provides proper escaping of any +unsafe characters in search filters. The DN is built using LdapNameBuilder, which also provides +proper escaping.

+ + + +

The fourth example uses UnboundID classes, Filter and DN, to construct a safe filter and +base DN.

+ + + +

The fifth example shows how to build a safe filter and DN using the Apache LDAP API.

+ + +
+ + +
  • OWASP: LDAP Injection Prevention Cheat Sheet.
  • +
  • OWASP ESAPI: OWASP ESAPI.
  • +
  • Spring LdapQueryBuilder doc: LdapQueryBuilder.
  • +
  • Spring LdapNameBuilder doc: LdapNameBuilder.
  • +
  • UnboundID: Understanding and Defending Against LDAP Injection Attacks.
  • +
    +
    diff --git a/java/ql/src/Security/CWE/CWE-090/LdapInjection.ql b/java/ql/src/Security/CWE/CWE-090/LdapInjection.ql new file mode 100644 index 00000000000..6b5b37f1093 --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-090/LdapInjection.ql @@ -0,0 +1,21 @@ +/** + * @name LDAP query built from user-controlled sources + * @description Building an LDAP query from user-controlled sources is vulnerable to insertion of + * malicious LDAP code by the user. + * @kind path-problem + * @problem.severity error + * @precision high + * @id java/ldap-injection + * @tags security + * external/cwe/cwe-090 + */ + +import java +import semmle.code.java.dataflow.FlowSources +import LdapInjectionLib +import DataFlow::PathGraph + +from DataFlow::PathNode source, DataFlow::PathNode sink, LdapInjectionFlowConfig conf +where conf.hasFlowPath(source, sink) +select sink.getNode(), source, sink, "LDAP query might include code from $@.", source.getNode(), + "this user input" diff --git a/java/ql/src/Security/CWE/CWE-090/LdapInjectionApache.java b/java/ql/src/Security/CWE/CWE-090/LdapInjectionApache.java new file mode 100644 index 00000000000..5a7b2be5e3f --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-090/LdapInjectionApache.java @@ -0,0 +1,22 @@ +import org.apache.directory.ldap.client.api.LdapConnection; +import org.apache.directory.api.ldap.model.name.Dn; +import org.apache.directory.api.ldap.model.name.Rdn; +import org.apache.directory.api.ldap.model.message.SearchRequest; +import org.apache.directory.api.ldap.model.message.SearchRequestImpl; +import static org.apache.directory.ldap.client.api.search.FilterBuilder.equal; + +public void ldapQueryGood(HttpServletRequest request, LdapConnection c) { + String organizationName = request.getParameter("organization_name"); + String username = request.getParameter("username"); + + // GOOD: Organization name is encoded before being used in DN + Dn safeDn = new Dn(new Rdn("OU", "People"), new Rdn("O", organizationName)); + + // GOOD: User input is encoded before being used in search filter + String safeFilter = equal("username", username); + + SearchRequest searchRequest = new SearchRequestImpl(); + searchRequest.setBase(safeDn); + searchRequest.setFilter(safeFilter); + c.search(searchRequest); +} \ No newline at end of file diff --git a/java/ql/src/Security/CWE/CWE-090/LdapInjectionJndi.java b/java/ql/src/Security/CWE/CWE-090/LdapInjectionJndi.java new file mode 100644 index 00000000000..6bcb481c51f --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-090/LdapInjectionJndi.java @@ -0,0 +1,34 @@ +import javax.naming.directory.DirContext; +import org.owasp.esapi.Encoder; +import org.owasp.esapi.reference.DefaultEncoder; + +public void ldapQueryBad(HttpServletRequest request, DirContext ctx) throws NamingException { + String organizationName = request.getParameter("organization_name"); + String username = request.getParameter("username"); + + // BAD: User input used in DN (Distinguished Name) without encoding + String dn = "OU=People,O=" + organizationName; + + // BAD: User input used in search filter without encoding + String filter = "username=" + userName; + + ctx.search(dn, filter, new SearchControls()); +} + +public void ldapQueryGood(HttpServletRequest request, DirContext ctx) throws NamingException { + String organizationName = request.getParameter("organization_name"); + String username = request.getParameter("username"); + + // ESAPI encoder + Encoder encoder = DefaultEncoder.getInstance(); + + // GOOD: Organization name is encoded before being used in DN + String safeOrganizationName = encoder.encodeForDN(organizationName); + String safeDn = "OU=People,O=" + safeOrganizationName; + + // GOOD: User input is encoded before being used in search filter + String safeUsername = encoder.encodeForLDAP(username); + String safeFilter = "username=" + safeUsername; + + ctx.search(safeDn, safeFilter, new SearchControls()); +} \ No newline at end of file diff --git a/java/ql/src/Security/CWE/CWE-090/LdapInjectionLib.qll b/java/ql/src/Security/CWE/CWE-090/LdapInjectionLib.qll new file mode 100644 index 00000000000..d2131dadb80 --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-090/LdapInjectionLib.qll @@ -0,0 +1,406 @@ +import java +import semmle.code.java.dataflow.FlowSources +import DataFlow +import semmle.code.java.frameworks.Jndi +import semmle.code.java.frameworks.UnboundId +import semmle.code.java.frameworks.SpringLdap +import semmle.code.java.frameworks.ApacheLdap + +/** + * A taint-tracking configuration for unvalidated user input that is used to construct LDAP queries. + */ +class LdapInjectionFlowConfig extends TaintTracking::Configuration { + LdapInjectionFlowConfig() { this = "LdapInjectionFlowConfig" } + + override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource } + + override predicate isSink(DataFlow::Node sink) { sink instanceof LdapInjectionSink } + + override predicate isSanitizer(DataFlow::Node node) { + node.getType() instanceof PrimitiveType or node.getType() instanceof BoxedType + } + + override predicate isAdditionalTaintStep(DataFlow::Node node1, DataFlow::Node node2) { + ldapNameStep(node1, node2) or + ldapNameAddAllStep(node1, node2) or + ldapNameGetCloneStep(node1, node2) or + filterStep(node1, node2) or + filterToStringStep(node1, node2) or + unboundIdSearchRequestStep(node1, node2) or + unboundIdSearchRequestDuplicateStep(node1, node2) or + unboundIdSearchRequestSetStep(node1, node2) or + ldapQueryStep(node1, node2) or + ldapQueryBaseStep(node1, node2) or + ldapQueryBuilderStep(node1, node2) or + hardcodedFilterStep(node1, node2) or + springLdapFilterToStringStep(node1, node2) or + ldapNameBuilderStep(node1, node2) or + ldapNameBuilderBuildStep(node1, node2) or + ldapUtilsStep(node1, node2) or + apacheSearchRequestStep(node1, node2) or + apacheSearchRequestGetStep(node1, node2) or + apacheLdapDnStep(node1, node2) or + apacheLdapDnGetStep(node1, node2) + } +} + +/** + * JNDI sink for LDAP injection vulnerabilities, i.e. 1st (DN) or 2nd (filter) argument to + * `search` method from `DirContext`. + */ +predicate jndiLdapInjectionSinkMethod(Method m, int index) { + m.getDeclaringType().getAnAncestor() instanceof TypeDirContext and + m.hasName("search") and + index in [0 .. 1] +} + +/** + * UnboundID sink for LDAP injection vulnerabilities, + * i.e. LDAPConnection.search, LDAPConnection.asyncSearch or LDAPConnection.searchForEntry method. + */ +predicate unboundIdLdapInjectionSinkMethod(Method m, int index) { + exists(Parameter param | m.getParameter(index) = param and not param.isVarargs() | + m instanceof MethodUnboundIdLDAPConnectionSearch or + m instanceof MethodUnboundIdLDAPConnectionAsyncSearch or + m instanceof MethodUnboundIdLDAPConnectionSearchForEntry + ) +} + +/** + * Spring LDAP sink for LDAP injection vulnerabilities, + * i.e. LdapTemplate.authenticate, LdapTemplate.find* or LdapTemplate.search* method. + */ +predicate springLdapInjectionSinkMethod(Method m, int index) { + // LdapTemplate.authenticate, LdapTemplate.find* or LdapTemplate.search* method + ( + m instanceof MethodSpringLdapTemplateAuthenticate or + m instanceof MethodSpringLdapTemplateFind or + m instanceof MethodSpringLdapTemplateFindOne or + m instanceof MethodSpringLdapTemplateSearch or + m instanceof MethodSpringLdapTemplateSearchForContext or + m instanceof MethodSpringLdapTemplateSearchForObject + ) and + ( + // Parameter index is 1 (DN or query) or 2 (filter) if method is not authenticate + index in [0 .. 1] and + not m instanceof MethodSpringLdapTemplateAuthenticate + or + // But it's not the last parameter in case of authenticate method (last param is password) + index in [0 .. 1] and + index < m.getNumberOfParameters() - 1 and + m instanceof MethodSpringLdapTemplateAuthenticate + ) +} + +/** Apache LDAP API sink for LDAP injection vulnerabilities, i.e. LdapConnection.search method. */ +predicate apacheLdapInjectionSinkMethod(Method m, int index) { + exists(Parameter param | m.getParameter(index) = param and not param.isVarargs() | + m.getDeclaringType().getAnAncestor() instanceof TypeApacheLdapConnection and + m.hasName("search") + ) +} + +/** Holds if parameter at index `index` in method `m` is LDAP injection sink. */ +predicate ldapInjectionSinkMethod(Method m, int index) { + jndiLdapInjectionSinkMethod(m, index) or + unboundIdLdapInjectionSinkMethod(m, index) or + springLdapInjectionSinkMethod(m, index) or + apacheLdapInjectionSinkMethod(m, index) +} + +/** A data flow sink for unvalidated user input that is used to construct LDAP queries. */ +class LdapInjectionSink extends DataFlow::ExprNode { + LdapInjectionSink() { + exists(MethodAccess ma, Method m, int index | + ma.getMethod() = m and + ma.getArgument(index) = this.getExpr() and + ldapInjectionSinkMethod(m, index) + ) + } +} + +/** + * Holds if `n1` to `n2` is a dataflow step that converts between `String` and `LdapName`, + * i.e. `new LdapName(tainted)`. + */ +predicate ldapNameStep(ExprNode n1, ExprNode n2) { + exists(ConstructorCall cc | cc.getConstructedType() instanceof TypeLdapName | + n1.asExpr() = cc.getAnArgument() and + n2.asExpr() = cc + ) +} + +/** + * Holds if `n1` to `n2` is a dataflow step that converts between `List` and `LdapName`, + * i.e. `new LdapName().addAll(tainted)`. + */ +predicate ldapNameAddAllStep(ExprNode n1, ExprNode n2) { + exists(MethodAccess ma | + n1.asExpr() = ma.getAnArgument() and + (n2.asExpr() = ma or n2.asExpr() = ma.getQualifier()) + | + ma.getMethod() instanceof MethodLdapNameAddAll + ) +} + +/** + * Holds if `n1` to `n2` is a dataflow step that converts between `LdapName` and `LdapName` or + * `String`, i.e. `taintedLdapName.clone()`, `taintedLdapName.getAll()`, + * `taintedLdapName.getRdns()` or `taintedLdapName.toString()`. + */ +predicate ldapNameGetCloneStep(ExprNode n1, ExprNode n2) { + exists(MethodAccess ma, Method m | + n1.asExpr() = ma.getQualifier() and + n2.asExpr() = ma and + ma.getMethod() = m + | + m instanceof MethodLdapNameClone or + m instanceof MethodLdapNameGetAll or + m instanceof MethodLdapNameGetRdns or + m instanceof MethodLdapNameToString + ) +} + +/** + * Holds if `n1` to `n2` is a dataflow step that converts between `String` and UnboundID `Filter`, + * i.e. `Filter.create*(tainted)`. + */ +predicate filterStep(ExprNode n1, ExprNode n2) { + exists(MethodAccess ma, Method m | + n1.asExpr() = ma.getAnArgument() and + n2.asExpr() = ma and + ma.getMethod() = m + | + m instanceof MethodUnboundIdFilterCreate or + m instanceof MethodUnboundIdFilterCreateANDFilter or + m instanceof MethodUnboundIdFilterCreateNOTFilter or + m instanceof MethodUnboundIdFilterCreateORFilter or + m instanceof MethodUnboundIdFilterSimplifyFilter + ) +} + +/** + * Holds if `n1` to `n2` is a dataflow step that converts between UnboundID `Filter` and `String`, + * i.e. `taintedFilter.toString()` or `taintedFilter.toString(buffer)`. + */ +predicate filterToStringStep(ExprNode n1, ExprNode n2) { + exists(MethodAccess ma, Method m | + n1.asExpr() = ma.getQualifier() and + (n2.asExpr() = ma or n2.asExpr() = ma.getAnArgument()) + | + ma.getMethod() = m and + m.getDeclaringType() instanceof TypeUnboundIdLdapFilter and + (m.hasName("toString") or m.hasName("toNormalizedString")) + ) +} + +/** + * Holds if `n1` to `n2` is a dataflow step that converts between `String` and UnboundID + * `SearchRequest`, i.e. `new SearchRequest(tainted)`. + */ +predicate unboundIdSearchRequestStep(ExprNode n1, ExprNode n2) { + exists(ConstructorCall cc, int index, Parameter param | + cc.getConstructedType() instanceof TypeUnboundIdSearchRequest + | + n1.asExpr() = cc.getArgument(index) and + n2.asExpr() = cc and + cc.getConstructor().getParameter(index) = param and + not param.isVarargs() + ) +} + +/** + * Holds if `n1` to `n2` is a dataflow step that converts between UnboundID `SearchRequest` + * and UnboundID `SearchRequest`, i.e. `taintedSearchRequest.duplicate()`. + */ +predicate unboundIdSearchRequestDuplicateStep(ExprNode n1, ExprNode n2) { + exists(MethodAccess ma, Method m | n1.asExpr() = ma.getQualifier() and n2.asExpr() = ma | + ma.getMethod() = m and + m.getDeclaringType().getAnAncestor() instanceof TypeUnboundIdReadOnlySearchRequest and + m.hasName("duplicate") + ) +} + +/** + * Holds if `n1` to `n2` is a dataflow step that converts between DN or filter and UnboundID + * `SearchRequest`, i.e. `searchRequest.setBaseDN(tainted)` or `searchRequest.setFilter(tainted)`. + */ +predicate unboundIdSearchRequestSetStep(ExprNode n1, ExprNode n2) { + exists(MethodAccess ma, Method m | + n1.asExpr() = ma.getAnArgument() and + n2.asExpr() = ma.getQualifier() and + ma.getMethod() = m + | + m instanceof MethodUnboundIdSearchRequestSetBaseDN or + m instanceof MethodUnboundIdSearchRequestSetFilter + ) +} + +/** + * Holds if `n1` to `n2` is a dataflow step that converts between `String` and Spring `LdapQuery`, + * i.e. `LdapQueryBuilder.query().filter(tainted)` or `LdapQueryBuilder.query().base(tainted)`. + */ +predicate ldapQueryStep(ExprNode n1, ExprNode n2) { + exists(MethodAccess ma, Method m, int index | + n1.asExpr() = ma.getArgument(index) and + n2.asExpr() = ma and + ma.getMethod() = m and + index = 0 + | + m instanceof MethodSpringLdapQueryBuilderFilter or + m instanceof MethodSpringLdapQueryBuilderBase + ) +} + +/** + * Holds if `n1` to `n2` is a dataflow step that converts between Spring `LdapQueryBuilder` and + * `Name`, i.e. `taintedLdapQueryBuilder.base()`. + */ +predicate ldapQueryBaseStep(ExprNode n1, ExprNode n2) { + exists(MethodAccess ma, Method m | + n1.asExpr() = ma.getQualifier() and + n2.asExpr() = ma and + ma.getMethod() = m + | + m instanceof MethodSpringLdapQueryBuilderBase and + m.getNumberOfParameters() = 0 + ) +} + +/** + * Holds if `n1` to `n2` is a dataflow step that converts between Spring `LdapQueryBuilder`, + * `ConditionCriteria` or `ContainerCriteria`, i.e. when the query is built, for example + * `query().base(tainted).where("objectclass").is("person")`. + */ +predicate ldapQueryBuilderStep(ExprNode n1, ExprNode n2) { + exists(MethodAccess ma, Method m | + n1.asExpr() = ma.getQualifier() and + n2.asExpr() = ma and + ma.getMethod() = m + | + ( + m.getDeclaringType() instanceof TypeSpringLdapQueryBuilder or + m.getDeclaringType() instanceof TypeSpringConditionCriteria or + m.getDeclaringType() instanceof TypeSpringContainerCriteria + ) and + ( + m.getReturnType() instanceof TypeSpringLdapQueryBuilder or + m.getReturnType() instanceof TypeSpringConditionCriteria or + m.getReturnType() instanceof TypeSpringContainerCriteria + ) + ) +} + +/** + * Holds if `n1` to `n2` is a dataflow step that converts between `String` and Spring + * `HardcodedFilter`, i.e. `new HardcodedFilter(tainted)`. + */ +predicate hardcodedFilterStep(ExprNode n1, ExprNode n2) { + exists(ConstructorCall cc | cc.getConstructedType() instanceof TypeSpringHardcodedFilter | + n1.asExpr() = cc.getAnArgument() and + n2.asExpr() = cc + ) +} + +/** + * Holds if `n1` to `n2` is a dataflow step that converts between Spring `Filter` and + * `String`, i.e. `taintedFilter.toString()`, `taintedFilter.encode()` or + * `taintedFilter.encode(buffer)`. + */ +predicate springLdapFilterToStringStep(ExprNode n1, ExprNode n2) { + exists(MethodAccess ma, Method m | + n1.asExpr() = ma.getQualifier() and + (n2.asExpr() = ma or n2.asExpr() = ma.getAnArgument()) and + ma.getMethod() = m + | + m.getDeclaringType().getAnAncestor() instanceof TypeSpringLdapFilter and + (m.hasName("encode") or m.hasName("toString")) + ) +} + +/** + * Holds if `n1` to `n2` is a dataflow step that converts between `String` and Spring + * `LdapNameBuilder`, i.e. `LdapNameBuilder.newInstance(tainted)` or + * `LdapNameBuilder.newInstance().add(tainted)`. + */ +predicate ldapNameBuilderStep(ExprNode n1, ExprNode n2) { + exists(MethodAccess ma, Method m | + n1.asExpr() = ma.getAnArgument() and + (n2.asExpr() = ma or n2.asExpr() = ma.getQualifier()) and + ma.getMethod() = m and + m.getNumberOfParameters() = 1 + | + m instanceof MethodSpringLdapNameBuilderNewInstance or + m instanceof MethodSpringLdapNameBuilderAdd + ) +} + +/** + * Holds if `n1` to `n2` is a dataflow step that converts between tainted Spring `LdapNameBuilder` + * and `LdapName`, `LdapNameBuilder.build()`. + */ +predicate ldapNameBuilderBuildStep(ExprNode n1, ExprNode n2) { + exists(MethodAccess ma | n1.asExpr() = ma.getQualifier() and n2.asExpr() = ma | + ma.getMethod() instanceof MethodSpringLdapNameBuilderBuild + ) +} + +/** + * Holds if `n1` to `n2` is a dataflow step that converts between `String` and `LdapName` via + * Spring `LdapUtils.newLdapName`, i.e. `LdapUtils.newLdapName(tainted)`. + */ +predicate ldapUtilsStep(ExprNode n1, ExprNode n2) { + exists(MethodAccess ma | n1.asExpr() = ma.getAnArgument() and n2.asExpr() = ma | + ma.getMethod() instanceof MethodSpringLdapUtilsNewLdapName + ) +} + +/** + * Holds if `n1` to `n2` is a dataflow step that converts between `String` and Apache LDAP API + * `SearchRequest`, i.e. `searchRequest.setFilter(tainted)` or `searchRequest.setBase(tainted)`. + */ +predicate apacheSearchRequestStep(ExprNode n1, ExprNode n2) { + exists(MethodAccess ma, Method m | + n1.asExpr() = ma.getAnArgument() and + n2.asExpr() = ma.getQualifier() + | + ma.getMethod() = m and + m.getDeclaringType().getAnAncestor() instanceof TypeApacheSearchRequest and + (m.hasName("setFilter") or m.hasName("setBase")) + ) +} + +/** + * Holds if `n1` to `n2` is a dataflow step that converts between Apache LDAP API `SearchRequest` + * and filter or DN i.e. `tainterSearchRequest.getFilter()` or `taintedSearchRequest.getBase()`. + */ +predicate apacheSearchRequestGetStep(ExprNode n1, ExprNode n2) { + exists(MethodAccess ma, Method m | n1.asExpr() = ma.getQualifier() and n2.asExpr() = ma | + ma.getMethod() = m and + m.getDeclaringType().getAnAncestor() instanceof TypeApacheSearchRequest and + (m.hasName("getFilter") or m.hasName("getBase")) + ) +} + +/** + * Holds if `n1` to `n2` is a dataflow step that converts between `String` and Apache LDAP API + * `Dn`, i.e. `new Dn(tainted)`. + */ +predicate apacheLdapDnStep(ExprNode n1, ExprNode n2) { + exists(ConstructorCall cc | cc.getConstructedType() instanceof TypeApacheDn | + n1.asExpr() = cc.getAnArgument() and + n2.asExpr() = cc + ) +} + +/** + * Holds if `n1` to `n2` is a dataflow step that converts between Apache LDAP API `Dn` + * and `String` i.e. `taintedDn.getName()`, `taintedDn.getNormName()` or `taintedDn.toString()`. + */ +predicate apacheLdapDnGetStep(ExprNode n1, ExprNode n2) { + exists(MethodAccess ma, Method m | n1.asExpr() = ma.getQualifier() and n2.asExpr() = ma | + ma.getMethod() = m and + m.getDeclaringType().getAnAncestor() instanceof TypeApacheDn and + (m.hasName("getName") or m.hasName("getNormName") or m.hasName("toString")) + ) +} diff --git a/java/ql/src/Security/CWE/CWE-090/LdapInjectionSpring.java b/java/ql/src/Security/CWE/CWE-090/LdapInjectionSpring.java new file mode 100644 index 00000000000..e3265ace995 --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-090/LdapInjectionSpring.java @@ -0,0 +1,17 @@ +import static org.springframework.ldap.query.LdapQueryBuilder.query; +import org.springframework.ldap.support.LdapNameBuilder; + +public void ldapQueryGood(@RequestParam String organizationName, @RequestParam String username) { + // GOOD: Organization name is encoded before being used in DN + String safeDn = LdapNameBuilder.newInstance() + .add("O", organizationName) + .add("OU=People") + .build().toString(); + + // GOOD: User input is encoded before being used in search filter + LdapQuery query = query() + .base(safeDn) + .where("username").is(username); + + ldapTemplate.search(query, new AttributeCheckAttributesMapper()); +} \ No newline at end of file diff --git a/java/ql/src/Security/CWE/CWE-090/LdapInjectionUnboundId.java b/java/ql/src/Security/CWE/CWE-090/LdapInjectionUnboundId.java new file mode 100644 index 00000000000..fac4cbfc0ef --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-090/LdapInjectionUnboundId.java @@ -0,0 +1,17 @@ +import com.unboundid.ldap.sdk.LDAPConnection; +import com.unboundid.ldap.sdk.DN; +import com.unboundid.ldap.sdk.RDN; +import com.unboundid.ldap.sdk.Filter; + +public void ldapQueryGood(HttpServletRequest request, LDAPConnection c) { + String organizationName = request.getParameter("organization_name"); + String username = request.getParameter("username"); + + // GOOD: Organization name is encoded before being used in DN + DN safeDn = new DN(new RDN("OU", "People"), new RDN("O", organizationName)); + + // GOOD: User input is encoded before being used in search filter + Filter safeFilter = Filter.createEqualityFilter("username", username); + + c.search(safeDn.toString(), SearchScope.ONE, safeFilter); +} \ No newline at end of file diff --git a/java/ql/src/Security/CWE/CWE-190/ArithmeticCommon.qll b/java/ql/src/Security/CWE/CWE-190/ArithmeticCommon.qll index c59c6376ae9..9d081e34420 100644 --- a/java/ql/src/Security/CWE/CWE-190/ArithmeticCommon.qll +++ b/java/ql/src/Security/CWE/CWE-190/ArithmeticCommon.qll @@ -14,7 +14,7 @@ private import semmle.code.java.controlflow.internal.GuardsLogic predicate narrowerThanOrEqualTo(ArithExpr exp, NumType numType) { exp.getType().(NumType).widerThan(numType) implies - exists(CastExpr cast | cast.getAChildExpr().getProperExpr() = exp | + exists(CastExpr cast | cast.getAChildExpr() = exp | numType.widerThanOrEqualTo(cast.getType().(NumType)) ) } @@ -54,11 +54,11 @@ private Guard sizeGuard(SsaVariable v, boolean branch, boolean upper) { positive(pos) and upper = true | - comp.getLesserOperand().getProperExpr() = add and + comp.getLesserOperand() = add and comp.getGreaterOperand().(IntegerLiteral).getIntValue() = 0 and branch = false or - comp.getGreaterOperand().getProperExpr() = add and + comp.getGreaterOperand() = add and comp.getLesserOperand().(IntegerLiteral).getIntValue() = 0 and branch = true ) @@ -157,7 +157,6 @@ predicate upcastToWiderType(Expr e) { /** Holds if the result of `exp` has certain bits filtered by a bitwise and. */ private predicate inBitwiseAnd(Expr exp) { exists(AndBitwiseExpr a | a.getAnOperand() = exp) or - inBitwiseAnd(exp.(ParExpr).getExpr()) or inBitwiseAnd(exp.(LShiftExpr).getAnOperand()) or inBitwiseAnd(exp.(RShiftExpr).getAnOperand()) or inBitwiseAnd(exp.(URShiftExpr).getAnOperand()) diff --git a/java/ql/src/Security/CWE/CWE-681/NumericCastCommon.qll b/java/ql/src/Security/CWE/CWE-681/NumericCastCommon.qll index e66e71ebd9f..4887c3fefad 100644 --- a/java/ql/src/Security/CWE/CWE-681/NumericCastCommon.qll +++ b/java/ql/src/Security/CWE/CWE-681/NumericCastCommon.qll @@ -29,7 +29,7 @@ class RightShiftOp extends Expr { Variable getShiftedVariable() { getLhs() = result.getAnAccess() or - getLhs().getProperExpr().(AndBitwiseExpr).getAnOperand() = result.getAnAccess() + getLhs().(AndBitwiseExpr).getAnOperand() = result.getAnAccess() } } diff --git a/java/ql/src/Security/CWE/CWE-835/InfiniteLoop.ql b/java/ql/src/Security/CWE/CWE-835/InfiniteLoop.ql index 728d2b17930..cc02dfb3f09 100644 --- a/java/ql/src/Security/CWE/CWE-835/InfiniteLoop.ql +++ b/java/ql/src/Security/CWE/CWE-835/InfiniteLoop.ql @@ -48,8 +48,6 @@ predicate subCondition(Expr cond, Expr subcond, boolean negated) { or subCondition(cond.(OrLogicalExpr).getAnOperand(), subcond, negated) or - subCondition(cond.(ParExpr).getExpr(), subcond, negated) - or subCondition(cond.(LogNotExpr).getExpr(), subcond, negated.booleanNot()) } diff --git a/java/ql/src/Violations of Best Practice/Boolean Logic/SimplifyBoolExpr.ql b/java/ql/src/Violations of Best Practice/Boolean Logic/SimplifyBoolExpr.ql index db8fc26614e..178f5dbd847 100644 --- a/java/ql/src/Violations of Best Practice/Boolean Logic/SimplifyBoolExpr.ql +++ b/java/ql/src/Violations of Best Practice/Boolean Logic/SimplifyBoolExpr.ql @@ -84,9 +84,9 @@ where or conditionalWithBool(e, pattern, rewrite) or - e.(LogNotExpr).getExpr().getProperExpr().(ComparisonOrEquality).negate(pattern, rewrite) + e.(LogNotExpr).getExpr().(ComparisonOrEquality).negate(pattern, rewrite) or - e.(LogNotExpr).getExpr().getProperExpr() instanceof LogNotExpr and + e.(LogNotExpr).getExpr() instanceof LogNotExpr and pattern = "!!A" and rewrite = "A" select e, "Expressions of the form \"" + pattern + "\" can be simplified to \"" + rewrite + "\"." diff --git a/java/ql/src/Violations of Best Practice/Naming Conventions/ConfusingOverloading.ql b/java/ql/src/Violations of Best Practice/Naming Conventions/ConfusingOverloading.ql index 07c071ad2d4..1d8509c6e03 100644 --- a/java/ql/src/Violations of Best Practice/Naming Conventions/ConfusingOverloading.ql +++ b/java/ql/src/Violations of Best Practice/Naming Conventions/ConfusingOverloading.ql @@ -84,7 +84,6 @@ private predicate confusinglyOverloaded(Method m, Method n) { private predicate wrappedAccess(Expr e, MethodAccess ma) { e = ma or - wrappedAccess(e.(ParExpr).getExpr(), ma) or wrappedAccess(e.(CastExpr).getExpr(), ma) } diff --git a/java/ql/src/semmle/code/java/Concurrency.qll b/java/ql/src/semmle/code/java/Concurrency.qll index 29202c8cc40..61e76525ec8 100644 --- a/java/ql/src/semmle/code/java/Concurrency.qll +++ b/java/ql/src/semmle/code/java/Concurrency.qll @@ -14,8 +14,7 @@ predicate locallySynchronizedOn(Expr e, SynchronizedStmt sync, Variable v) { */ predicate locallySynchronizedOnThis(Expr e, RefType thisType) { exists(SynchronizedStmt sync | e.getEnclosingStmt().getEnclosingStmt+() = sync | - sync.getExpr().getProperExpr().(ThisAccess).getType().(RefType).getSourceDeclaration() = - thisType + sync.getExpr().(ThisAccess).getType().(RefType).getSourceDeclaration() = thisType ) or exists(SynchronizedCallable c | c = e.getEnclosingCallable() | diff --git a/java/ql/src/semmle/code/java/ControlFlowGraph.qll b/java/ql/src/semmle/code/java/ControlFlowGraph.qll index 9c7cc976d46..13e6524352a 100644 --- a/java/ql/src/semmle/code/java/ControlFlowGraph.qll +++ b/java/ql/src/semmle/code/java/ControlFlowGraph.qll @@ -289,8 +289,6 @@ private module ControlFlowGraphImpl { logexpr.(UnaryExpr).getExpr() = b and inBooleanContext(logexpr) ) or - exists(ParExpr parexpr | parexpr.getExpr() = b and inBooleanContext(parexpr)) - or exists(ConditionalExpr condexpr | condexpr.getCondition() = b or @@ -574,8 +572,6 @@ private module ControlFlowGraphImpl { or result = first(n.(PostOrderNode).firstChild()) or - result = first(n.(ParExpr).getExpr()) - or result = first(n.(SynchronizedStmt).getExpr()) or result = n and @@ -708,9 +704,6 @@ private module ControlFlowGraphImpl { last(condexpr.getTrueExpr(), last, completion) ) or - // Parentheses are skipped in the CFG. - last(n.(ParExpr).getExpr(), last, completion) - or // The last node of a node executed in post-order is the node itself. n.(PostOrderNode).mayCompleteNormally() and last = n and completion = NormalCompletion() or diff --git a/java/ql/src/semmle/code/java/Conversions.qll b/java/ql/src/semmle/code/java/Conversions.qll index 460b2c24ff2..9d55f1297fc 100644 --- a/java/ql/src/semmle/code/java/Conversions.qll +++ b/java/ql/src/semmle/code/java/Conversions.qll @@ -49,7 +49,7 @@ class AssignmentConversionContext extends ConversionSite { AssignmentConversionContext() { this = v.getAnAssignedValue() or - exists(Assignment a | a.getDest().getProperExpr() = v.getAnAccess() and this = a.getSource()) + exists(Assignment a | a.getDest() = v.getAnAccess() and this = a.getSource()) } override Type getConversionTarget() { result = v.getType() } diff --git a/java/ql/src/semmle/code/java/Expr.qll b/java/ql/src/semmle/code/java/Expr.qll index 51312dd1dc5..08f869c01f2 100755 --- a/java/ql/src/semmle/code/java/Expr.qll +++ b/java/ql/src/semmle/code/java/Expr.qll @@ -46,8 +46,12 @@ class Expr extends ExprParent, @expr { */ int getKind() { exprs(this, result, _, _, _) } - /** Gets this expression with any surrounding parentheses removed. */ - Expr getProperExpr() { + /** + * DEPRECATED: This is no longer necessary. See `Expr.isParenthesized()`. + * + * Gets this expression with any surrounding parentheses removed. + */ + deprecated Expr getProperExpr() { result = this.(ParExpr).getExpr().getProperExpr() or result = this and not this instanceof ParExpr @@ -152,9 +156,6 @@ class CompileTimeConstantExpr extends Expr { e.getFalseExpr().isCompileTimeConstant() ) or - // Parenthesized expressions whose contained expression is a constant expression. - this.(ParExpr).getExpr().isCompileTimeConstant() - or // Access to a final variable initialized by a compile-time constant. exists(Variable v | this = v.getAnAccess() | v.isFinal() and @@ -169,8 +170,6 @@ class CompileTimeConstantExpr extends Expr { string getStringValue() { result = this.(StringLiteral).getRepresentedString() or - result = this.(ParExpr).getExpr().(CompileTimeConstantExpr).getStringValue() - or result = this.(AddExpr).getLeftOperand().(CompileTimeConstantExpr).getStringValue() + this.(AddExpr).getRightOperand().(CompileTimeConstantExpr).getStringValue() @@ -296,9 +295,6 @@ class CompileTimeConstantExpr extends Expr { else result = ce.getFalseExpr().(CompileTimeConstantExpr).getBooleanValue() ) or - // Parenthesized expressions containing a boolean value. - result = this.(ParExpr).getExpr().(CompileTimeConstantExpr).getBooleanValue() - or // Simple or qualified names where the variable is final and the initializer is a constant. exists(Variable v | this = v.getAnAccess() | result = v.getInitializer().(CompileTimeConstantExpr).getBooleanValue() @@ -385,8 +381,6 @@ class CompileTimeConstantExpr extends Expr { else result = ce.getFalseExpr().(CompileTimeConstantExpr).getIntValue() ) or - result = this.(ParExpr).getExpr().(CompileTimeConstantExpr).getIntValue() - or // If a `Variable` is a `CompileTimeConstantExpr`, its value is its initializer. exists(Variable v | this = v.getAnAccess() | result = v.getInitializer().(CompileTimeConstantExpr).getIntValue() @@ -640,12 +634,8 @@ class BinaryExpr extends Expr, @binaryexpr { /** Gets the operand on the right-hand side of this binary expression. */ Expr getRightOperand() { result.isNthChildOf(this, 1) } - /** Gets an operand (left or right), with any parentheses removed. */ - Expr getAnOperand() { - exists(Expr r | r = this.getLeftOperand() or r = this.getRightOperand() | - result = r.getProperExpr() - ) - } + /** Gets an operand (left or right). */ + Expr getAnOperand() { result = this.getLeftOperand() or result = this.getRightOperand() } /** The operands of this binary expression are `e` and `f`, in either order. */ predicate hasOperands(Expr e, Expr f) { @@ -761,17 +751,14 @@ class NEExpr extends BinaryExpr, @neexpr { * A bitwise expression. * * This includes expressions involving the operators - * `&`, `|`, `^`, or `~`, - * possibly parenthesized. + * `&`, `|`, `^`, or `~`. */ class BitwiseExpr extends Expr { BitwiseExpr() { - exists(Expr proper | proper = this.getProperExpr() | - proper instanceof AndBitwiseExpr or - proper instanceof OrBitwiseExpr or - proper instanceof XorBitwiseExpr or - proper instanceof BitNotExpr - ) + this instanceof AndBitwiseExpr or + this instanceof OrBitwiseExpr or + this instanceof XorBitwiseExpr or + this instanceof BitNotExpr } } @@ -1128,10 +1115,14 @@ class SwitchExpr extends Expr, @switchexpr { override string toString() { result = "switch (...)" } } -/** A parenthesised expression. */ -class ParExpr extends Expr, @parexpr { +/** + * DEPRECATED: Use `Expr.isParenthesized()` instead. + * + * A parenthesised expression. + */ +deprecated class ParExpr extends Expr, @parexpr { /** Gets the expression inside the parentheses. */ - Expr getExpr() { result.getParent() = this } + deprecated Expr getExpr() { result.getParent() = this } /** Gets a printable representation of this expression. */ override string toString() { result = "(...)" } diff --git a/java/ql/src/semmle/code/java/JDK.qll b/java/ql/src/semmle/code/java/JDK.qll index 37f8f359c80..d9a1a15e5d3 100644 --- a/java/ql/src/semmle/code/java/JDK.qll +++ b/java/ql/src/semmle/code/java/JDK.qll @@ -101,6 +101,16 @@ class TypeMath extends Class { TypeMath() { this.hasQualifiedName("java.lang", "Math") } } +/** The class `java.lang.Number`. */ +class TypeNumber extends RefType { + TypeNumber() { this.hasQualifiedName("java.lang", "Number") } +} + +/** A (reflexive, transitive) subtype of `java.lang.Number`. */ +class NumberType extends RefType { + NumberType() { exists(TypeNumber number | hasSubtype*(number, this)) } +} + /** A numeric type, including both primitive and boxed types. */ class NumericType extends Type { NumericType() { diff --git a/java/ql/src/semmle/code/java/Reflection.qll b/java/ql/src/semmle/code/java/Reflection.qll index 0c6432e2409..3b0189e17ae 100644 --- a/java/ql/src/semmle/code/java/Reflection.qll +++ b/java/ql/src/semmle/code/java/Reflection.qll @@ -284,7 +284,7 @@ class NewInstance extends MethodAccess { * cast. */ private Type getCastInferredConstructedTypes() { - exists(CastExpr cast | cast.getExpr() = this or cast.getExpr().(ParExpr).getExpr() = this | + exists(CastExpr cast | cast.getExpr() = this | result = cast.getType() or // If we cast the result of this method, then this is either the type specified, or a diff --git a/java/ql/src/semmle/code/java/StringFormat.qll b/java/ql/src/semmle/code/java/StringFormat.qll index 075471d73ca..410d83818cc 100644 --- a/java/ql/src/semmle/code/java/StringFormat.qll +++ b/java/ql/src/semmle/code/java/StringFormat.qll @@ -246,8 +246,7 @@ private predicate formatStringFragment(Expr fmt) { e.(AddExpr).getLeftOperand() = fmt or e.(AddExpr).getRightOperand() = fmt or e.(ConditionalExpr).getTrueExpr() = fmt or - e.(ConditionalExpr).getFalseExpr() = fmt or - e.(ParExpr).getExpr() = fmt + e.(ConditionalExpr).getFalseExpr() = fmt ) } @@ -268,8 +267,6 @@ private predicate formatStringValue(Expr e, string fmtvalue) { or e.getType() instanceof EnumType and fmtvalue = "x" // dummy value or - formatStringValue(e.(ParExpr).getExpr(), fmtvalue) - or exists(Variable v | e = v.getAnAccess() and v.isFinal() and diff --git a/java/ql/src/semmle/code/java/Variable.qll b/java/ql/src/semmle/code/java/Variable.qll index 9a281786ac4..44c7e617d3f 100755 --- a/java/ql/src/semmle/code/java/Variable.qll +++ b/java/ql/src/semmle/code/java/Variable.qll @@ -16,9 +16,7 @@ class Variable extends @variable, Annotatable, Element, Modifiable { Expr getAnAssignedValue() { exists(LocalVariableDeclExpr e | e.getVariable() = this and result = e.getInit()) or - exists(AssignExpr e | - e.getDest().getProperExpr() = this.getAnAccess() and result = e.getSource() - ) + exists(AssignExpr e | e.getDest() = this.getAnAccess() and result = e.getSource()) } /** Gets the initializer expression of this variable. */ diff --git a/java/ql/src/semmle/code/java/comparison/Comparison.qll b/java/ql/src/semmle/code/java/comparison/Comparison.qll index 8a869d8a2e4..27ed9271e99 100644 --- a/java/ql/src/semmle/code/java/comparison/Comparison.qll +++ b/java/ql/src/semmle/code/java/comparison/Comparison.qll @@ -6,10 +6,6 @@ import java * Used as basis for the transitive closure in `exprImplies`. */ private predicate exprImpliesStep(Expr e1, boolean b1, Expr e2, boolean b2) { - e1.(ParExpr).getProperExpr() = e2 and - b2 = b1 and - (b1 = true or b1 = false) - or e1.(LogNotExpr).getExpr() = e2 and b2 = b1.booleanNot() and (b1 = true or b1 = false) diff --git a/java/ql/src/semmle/code/java/controlflow/Guards.qll b/java/ql/src/semmle/code/java/controlflow/Guards.qll index c382e7963cc..82cb1d997ae 100644 --- a/java/ql/src/semmle/code/java/controlflow/Guards.qll +++ b/java/ql/src/semmle/code/java/controlflow/Guards.qll @@ -103,8 +103,8 @@ class Guard extends ExprParent { */ BasicBlock getBasicBlock() { result = this.(Expr).getBasicBlock() or - result = this.(SwitchCase).getSwitch().getExpr().getProperExpr().getBasicBlock() or - result = this.(SwitchCase).getSwitchExpr().getExpr().getProperExpr().getBasicBlock() + result = this.(SwitchCase).getSwitch().getExpr().getBasicBlock() or + result = this.(SwitchCase).getSwitchExpr().getExpr().getBasicBlock() } /** @@ -115,9 +115,9 @@ class Guard extends ExprParent { */ predicate isEquality(Expr e1, Expr e2, boolean polarity) { exists(Expr exp1, Expr exp2 | equalityGuard(this, exp1, exp2, polarity) | - e1 = exp1.getProperExpr() and e2 = exp2.getProperExpr() + e1 = exp1 and e2 = exp2 or - e2 = exp1.getProperExpr() and e1 = exp2.getProperExpr() + e2 = exp1 and e1 = exp2 ) } @@ -265,7 +265,7 @@ private predicate equalityGuard(Guard g, Expr e1, Expr e2, boolean polarity) { exists(ConstCase cc | cc = g and polarity = true and - cc.getSelectorExpr().getProperExpr() = e1 and + cc.getSelectorExpr() = e1 and cc.getValue() = e2 and strictcount(cc.getValue(_)) = 1 ) diff --git a/java/ql/src/semmle/code/java/controlflow/UnreachableBlocks.qll b/java/ql/src/semmle/code/java/controlflow/UnreachableBlocks.qll index c7be6d2494d..5ce27fda434 100644 --- a/java/ql/src/semmle/code/java/controlflow/UnreachableBlocks.qll +++ b/java/ql/src/semmle/code/java/controlflow/UnreachableBlocks.qll @@ -93,8 +93,6 @@ class ConstantExpr extends Expr { // A binary expression where both sides are constant this.(BinaryExpr).getLeftOperand() instanceof ConstantExpr and this.(BinaryExpr).getRightOperand() instanceof ConstantExpr - or - this.(ParExpr).getExpr() instanceof ConstantExpr ) } @@ -108,8 +106,6 @@ class ConstantExpr extends Expr { or result = this.(FieldRead).getField().(ConstantField).getConstantValue().getBooleanValue() or - result = this.(ParExpr).getExpr().(ConstantExpr).getBooleanValue() - or // Handle binary expressions that have integer operands and a boolean result. exists(BinaryExpr b, int left, int right | b = this and diff --git a/java/ql/src/semmle/code/java/controlflow/internal/GuardsLogic.qll b/java/ql/src/semmle/code/java/controlflow/internal/GuardsLogic.qll index 6fd8d08a658..ba2fd8c01c0 100644 --- a/java/ql/src/semmle/code/java/controlflow/internal/GuardsLogic.qll +++ b/java/ql/src/semmle/code/java/controlflow/internal/GuardsLogic.qll @@ -19,10 +19,6 @@ private import semmle.code.java.dataflow.IntegerGuards * Restricted to BaseSSA-based reasoning. */ predicate implies_v1(Guard g1, boolean b1, Guard g2, boolean b2) { - g1.(ParExpr).getExpr() = g2 and - b1 = b2 and - (b1 = true or b1 = false) - or g1.(AndBitwiseExpr).getAnOperand() = g2 and b1 = true and b2 = true or g1.(OrBitwiseExpr).getAnOperand() = g2 and b1 = false and b2 = false @@ -220,13 +216,11 @@ private predicate hasPossibleUnknownValue(SsaVariable v) { * `ConditionalExpr`s. Parentheses are also removed. */ private Expr possibleValue(Expr e) { - result = possibleValue(e.(ParExpr).getExpr()) - or result = possibleValue(e.(ConditionalExpr).getTrueExpr()) or result = possibleValue(e.(ConditionalExpr).getFalseExpr()) or - result = e and not e instanceof ParExpr and not e instanceof ConditionalExpr + result = e and not e instanceof ConditionalExpr } /** @@ -253,7 +247,7 @@ private predicate possibleValue(SsaVariable v, boolean fromBackEdge, Expr e, Abs not hasPossibleUnknownValue(v) and exists(SsaExplicitUpdate def | def = getADefinition(v, fromBackEdge) and - e = possibleValue(def.getDefiningExpr().(VariableAssign).getSource().getProperExpr()) and + e = possibleValue(def.getDefiningExpr().(VariableAssign).getSource()) and k.getExpr() = e ) } @@ -305,7 +299,7 @@ private predicate guardControlsPhiBranch( SsaExplicitUpdate upd, SsaPhiNode phi, Guard guard, boolean branch, Expr e ) { guard.directlyControls(upd.getBasicBlock(), branch) and - upd.getDefiningExpr().(VariableAssign).getSource().getProperExpr() = e and + upd.getDefiningExpr().(VariableAssign).getSource() = e and upd = phi.getAPhiInput() and guard.getBasicBlock().bbStrictlyDominates(phi.getBasicBlock()) } @@ -319,12 +313,12 @@ private predicate guardControlsPhiBranch( */ private predicate conditionalAssign(SsaVariable v, Guard guard, boolean branch, Expr e) { exists(ConditionalExpr c | - v.(SsaExplicitUpdate).getDefiningExpr().(VariableAssign).getSource().getProperExpr() = c and - guard = c.getCondition().getProperExpr() + v.(SsaExplicitUpdate).getDefiningExpr().(VariableAssign).getSource() = c and + guard = c.getCondition() | - branch = true and e = c.getTrueExpr().getProperExpr() + branch = true and e = c.getTrueExpr() or - branch = false and e = c.getFalseExpr().getProperExpr() + branch = false and e = c.getFalseExpr() ) or exists(SsaExplicitUpdate upd, SsaPhiNode phi | diff --git a/java/ql/src/semmle/code/java/controlflow/internal/Preconditions.qll b/java/ql/src/semmle/code/java/controlflow/internal/Preconditions.qll index 9f7fbc14aa4..0fd367d65b4 100644 --- a/java/ql/src/semmle/code/java/controlflow/internal/Preconditions.qll +++ b/java/ql/src/semmle/code/java/controlflow/internal/Preconditions.qll @@ -24,9 +24,9 @@ predicate conditionCheckMethod(Method m, boolean checkTrue) { not m.isOverridable() and p.getType() instanceof BooleanType and m.getBody().getStmt(0) = ifstmt and - ifstmt.getCondition().getProperExpr() = cond and + ifstmt.getCondition() = cond and ( - cond.(LogNotExpr).getExpr().getProperExpr().(VarAccess).getVariable() = p and checkTrue = true + cond.(LogNotExpr).getExpr().(VarAccess).getVariable() = p and checkTrue = true or cond.(VarAccess).getVariable() = p and checkTrue = false ) and @@ -41,9 +41,9 @@ predicate conditionCheckMethod(Method m, boolean checkTrue) { not m.isOverridable() and m.getBody().getStmt(0).(ExprStmt).getExpr() = ma and conditionCheck(ma, ct) and - ma.getArgument(0).getProperExpr() = arg and + ma.getArgument(0) = arg and ( - arg.(LogNotExpr).getExpr().getProperExpr().(VarAccess).getVariable() = p and + arg.(LogNotExpr).getExpr().(VarAccess).getVariable() = p and checkTrue = ct.booleanNot() or arg.(VarAccess).getVariable() = p and checkTrue = ct diff --git a/java/ql/src/semmle/code/java/dataflow/IntegerGuards.qll b/java/ql/src/semmle/code/java/dataflow/IntegerGuards.qll index 5fb592272fb..29e2b3ec8a1 100644 --- a/java/ql/src/semmle/code/java/dataflow/IntegerGuards.qll +++ b/java/ql/src/semmle/code/java/dataflow/IntegerGuards.qll @@ -10,7 +10,6 @@ private import RangeAnalysis /** Gets an expression that might have the value `i`. */ private Expr exprWithIntValue(int i) { result.(ConstantIntegerExpr).getIntValue() = i or - result.(ParExpr).getExpr() = exprWithIntValue(i) or result.(ConditionalExpr).getTrueExpr() = exprWithIntValue(i) or result.(ConditionalExpr).getFalseExpr() = exprWithIntValue(i) } @@ -142,25 +141,25 @@ Expr intBoundGuard(RValue x, boolean branch_with_lower_bound_k, int k) { x.getVariable().getType() instanceof IntegralType | // c < x - comp.getLesserOperand().getProperExpr() = c and + comp.getLesserOperand() = c and comp.isStrict() and branch_with_lower_bound_k = true and val + 1 = k or // c <= x - comp.getLesserOperand().getProperExpr() = c and + comp.getLesserOperand() = c and not comp.isStrict() and branch_with_lower_bound_k = true and val = k or // x < c - comp.getGreaterOperand().getProperExpr() = c and + comp.getGreaterOperand() = c and comp.isStrict() and branch_with_lower_bound_k = false and val = k or // x <= c - comp.getGreaterOperand().getProperExpr() = c and + comp.getGreaterOperand() = c and not comp.isStrict() and branch_with_lower_bound_k = false and val + 1 = k diff --git a/java/ql/src/semmle/code/java/dataflow/ModulusAnalysis.qll b/java/ql/src/semmle/code/java/dataflow/ModulusAnalysis.qll index 876c746d7c0..1fb3eb201a8 100644 --- a/java/ql/src/semmle/code/java/dataflow/ModulusAnalysis.qll +++ b/java/ql/src/semmle/code/java/dataflow/ModulusAnalysis.qll @@ -76,8 +76,6 @@ private Expr modExpr(Expr arg, int mod) { c.getIntValue() = mod - 1 and result.(AndBitwiseExpr).hasOperands(arg, c) ) - or - result.(ParExpr).getExpr() = modExpr(arg, mod) } /** diff --git a/java/ql/src/semmle/code/java/dataflow/NullGuards.qll b/java/ql/src/semmle/code/java/dataflow/NullGuards.qll index b30c6f70f18..e4e7ec33e2c 100644 --- a/java/ql/src/semmle/code/java/dataflow/NullGuards.qll +++ b/java/ql/src/semmle/code/java/dataflow/NullGuards.qll @@ -11,7 +11,6 @@ private import IntegerGuards /** Gets an expression that is always `null`. */ Expr alwaysNullExpr() { result instanceof NullLiteral or - result.(ParExpr).getExpr() = alwaysNullExpr() or result.(CastExpr).getExpr() = alwaysNullExpr() } @@ -60,8 +59,6 @@ Expr clearlyNotNullExpr(Expr reason) { reason = result ) or - result.(ParExpr).getExpr() = clearlyNotNullExpr(reason) - or result.(CastExpr).getExpr() = clearlyNotNullExpr(reason) or result.(AssignExpr).getSource() = clearlyNotNullExpr(reason) diff --git a/java/ql/src/semmle/code/java/dataflow/Nullness.qll b/java/ql/src/semmle/code/java/dataflow/Nullness.qll index c3faeaf0ff2..644790bea6b 100644 --- a/java/ql/src/semmle/code/java/dataflow/Nullness.qll +++ b/java/ql/src/semmle/code/java/dataflow/Nullness.qll @@ -45,7 +45,6 @@ private import semmle.code.java.frameworks.Assertions /** Gets an expression that may be `null`. */ Expr nullExpr() { result instanceof NullLiteral or - result.(ParExpr).getExpr() = nullExpr() or result.(ConditionalExpr).getTrueExpr() = nullExpr() or result.(ConditionalExpr).getFalseExpr() = nullExpr() or result.(AssignExpr).getSource() = nullExpr() or @@ -131,11 +130,8 @@ predicate dereference(Expr e) { * The `VarAccess` is included for nicer error reporting. */ private ControlFlowNode varDereference(SsaVariable v, VarAccess va) { - exists(Expr e | - dereference(e) and - e = sameValue(v, va) and - result = e.getProperExpr() - ) + dereference(result) and + result = sameValue(v, va) } /** @@ -442,8 +438,8 @@ private predicate nullDerefCandidate(SsaVariable origin, VarAccess va) { /** A variable that is assigned `null` if the given condition takes the given branch. */ private predicate varConditionallyNull(SsaExplicitUpdate v, ConditionBlock cond, boolean branch) { exists(ConditionalExpr condexpr | - v.getDefiningExpr().(VariableAssign).getSource().getProperExpr() = condexpr and - condexpr.getCondition().getProperExpr() = cond.getCondition() + v.getDefiningExpr().(VariableAssign).getSource() = condexpr and + condexpr.getCondition() = cond.getCondition() | condexpr.getTrueExpr() = nullExpr() and branch = true and diff --git a/java/ql/src/semmle/code/java/dataflow/RangeAnalysis.qll b/java/ql/src/semmle/code/java/dataflow/RangeAnalysis.qll index e6b2e22d83c..435b976fde2 100644 --- a/java/ql/src/semmle/code/java/dataflow/RangeAnalysis.qll +++ b/java/ql/src/semmle/code/java/dataflow/RangeAnalysis.qll @@ -131,7 +131,7 @@ private predicate boundCondition( or exists(SubExpr sub, ConstantIntegerExpr c, int d | // (v - d) - e < c - comp.getLesserOperand().getProperExpr() = sub and + comp.getLesserOperand() = sub and comp.getGreaterOperand() = c and sub.getLeftOperand() = ssaRead(v, d) and sub.getRightOperand() = e and @@ -139,7 +139,7 @@ private predicate boundCondition( delta = d + c.getIntValue() or // (v - d) - e > c - comp.getGreaterOperand().getProperExpr() = sub and + comp.getGreaterOperand() = sub and comp.getLesserOperand() = c and sub.getLeftOperand() = ssaRead(v, d) and sub.getRightOperand() = e and @@ -147,7 +147,7 @@ private predicate boundCondition( delta = d + c.getIntValue() or // e - (v - d) < c - comp.getLesserOperand().getProperExpr() = sub and + comp.getLesserOperand() = sub and comp.getGreaterOperand() = c and sub.getLeftOperand() = e and sub.getRightOperand() = ssaRead(v, d) and @@ -155,7 +155,7 @@ private predicate boundCondition( delta = d - c.getIntValue() or // e - (v - d) > c - comp.getGreaterOperand().getProperExpr() = sub and + comp.getGreaterOperand() = sub and comp.getLesserOperand() = c and sub.getLeftOperand() = e and sub.getRightOperand() = ssaRead(v, d) and diff --git a/java/ql/src/semmle/code/java/dataflow/RangeUtils.qll b/java/ql/src/semmle/code/java/dataflow/RangeUtils.qll index 08d46f6bb80..22e363d9e3c 100644 --- a/java/ql/src/semmle/code/java/dataflow/RangeUtils.qll +++ b/java/ql/src/semmle/code/java/dataflow/RangeUtils.qll @@ -26,8 +26,6 @@ private predicate nonNullSsaFwdStep(SsaVariable v, SsaVariable phi) { } private predicate nonNullDefStep(Expr e1, Expr e2) { - e2.(ParExpr).getExpr() = e1 - or exists(ConditionalExpr cond | cond = e2 | cond.getTrueExpr() = e1 and cond.getFalseExpr() instanceof NullLiteral or @@ -104,8 +102,6 @@ class ConstantIntegerExpr extends Expr { Expr ssaRead(SsaVariable v, int delta) { result = v.getAUse() and delta = 0 or - result.(ParExpr).getExpr() = ssaRead(v, delta) - or exists(int d1, ConstantIntegerExpr c | result.(AddExpr).hasOperands(ssaRead(v, d1), c) and delta = d1 - c.getIntValue() @@ -264,8 +260,6 @@ predicate ssaUpdateStep(SsaExplicitUpdate v, Expr e, int delta) { * Holds if `e1 + delta` equals `e2`. */ predicate valueFlowStep(Expr e2, Expr e1, int delta) { - e2.(ParExpr).getExpr() = e1 and delta = 0 - or e2.(AssignExpr).getSource() = e1 and delta = 0 or e2.(PlusExpr).getExpr() = e1 and delta = 0 diff --git a/java/ql/src/semmle/code/java/dataflow/SSA.qll b/java/ql/src/semmle/code/java/dataflow/SSA.qll index ee397dd478f..561fdb35c5e 100644 --- a/java/ql/src/semmle/code/java/dataflow/SSA.qll +++ b/java/ql/src/semmle/code/java/dataflow/SSA.qll @@ -1129,7 +1129,5 @@ Expr sameValue(SsaVariable v, VarAccess va) { or result.(AssignExpr).getSource() = sameValue(v, va) or - result.(ParExpr).getExpr() = sameValue(v, va) - or result.(RefTypeCastExpr).getExpr() = sameValue(v, va) } diff --git a/java/ql/src/semmle/code/java/dataflow/SignAnalysis.qll b/java/ql/src/semmle/code/java/dataflow/SignAnalysis.qll index 7e9a22dbb85..60758069232 100644 --- a/java/ql/src/semmle/code/java/dataflow/SignAnalysis.qll +++ b/java/ql/src/semmle/code/java/dataflow/SignAnalysis.qll @@ -476,8 +476,6 @@ private Sign exprSign(Expr e) { ( unknownSign(e) or - result = exprSign(e.(ParExpr).getExpr()) - or exists(SsaVariable v | v.getAUse() = e | result = ssaSign(v, any(SsaReadPositionBlock bb | bb.getBlock() = e.getBasicBlock())) or diff --git a/java/ql/src/semmle/code/java/dataflow/TypeFlow.qll b/java/ql/src/semmle/code/java/dataflow/TypeFlow.qll index ec3d14b8159..e9f32aadbbe 100644 --- a/java/ql/src/semmle/code/java/dataflow/TypeFlow.qll +++ b/java/ql/src/semmle/code/java/dataflow/TypeFlow.qll @@ -109,8 +109,6 @@ private predicate step(TypeFlowNode n1, TypeFlowNode n2) { or n2.asExpr() = n1.asSsa().getAUse() or - n2.asExpr().(ParExpr).getExpr() = n1.asExpr() - or n2.asExpr().(CastExpr).getExpr() = n1.asExpr() and not n2.asExpr().getType() instanceof PrimitiveType or diff --git a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowPrivate.qll b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowPrivate.qll index bab214b44a9..a5ee883aaa8 100644 --- a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowPrivate.qll +++ b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowPrivate.qll @@ -235,6 +235,8 @@ DataFlowType getErasedRepr(Type t) { then result.(BoxedType).getPrimitiveType().getName() = "boolean" else result = e ) + or + t instanceof NullType and result instanceof TypeObject } /** Gets a string representation of a type returned by `getErasedRepr`. */ diff --git a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowUtil.qll b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowUtil.qll index ffa1a86f688..889e0c0c8a6 100644 --- a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowUtil.qll +++ b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowUtil.qll @@ -390,8 +390,6 @@ predicate simpleLocalFlowStep(Node node1, Node node2) { or ThisFlow::adjacentThisRefs(node1.(PostUpdateNode).getPreUpdateNode(), node2) or - node2.asExpr().(ParExpr).getExpr() = node1.asExpr() - or node2.asExpr().(CastExpr).getExpr() = node1.asExpr() or node2.asExpr().(ConditionalExpr).getTrueExpr() = node1.asExpr() diff --git a/java/ql/src/semmle/code/java/dataflow/internal/TaintTrackingUtil.qll b/java/ql/src/semmle/code/java/dataflow/internal/TaintTrackingUtil.qll index be33fc727c1..cb83df5b759 100644 --- a/java/ql/src/semmle/code/java/dataflow/internal/TaintTrackingUtil.qll +++ b/java/ql/src/semmle/code/java/dataflow/internal/TaintTrackingUtil.qll @@ -256,9 +256,12 @@ private predicate taintPreservingQualifierToArgument(Method m, int arg) { m.hasName("writeTo") and arg = 0 or - m.getDeclaringType().hasQualifiedName("java.io", "InputStream") and - m.hasName("read") and - arg = 0 + exists(Method read | + m.overrides*(read) and + read.getDeclaringType().hasQualifiedName("java.io", "InputStream") and + read.hasName("read") and + arg = 0 + ) or m.getDeclaringType().getASupertype*().hasQualifiedName("java.io", "Reader") and m.hasName("read") and @@ -515,9 +518,12 @@ private predicate argToQualifierStep(Expr tracked, Expr sink) { * `arg` is the index of the argument. */ private predicate taintPreservingArgumentToQualifier(Method method, int arg) { - method.getDeclaringType().hasQualifiedName("java.io", "ByteArrayOutputStream") and - method.hasName("write") and - arg = 0 + exists(Method write | + method.overrides*(write) and + write.getDeclaringType().hasQualifiedName("java.io", "OutputStream") and + write.hasName("write") and + arg = 0 + ) } /** A comparison or equality test with a constant. */ diff --git a/java/ql/src/semmle/code/java/deadcode/DeadEnumConstant.qll b/java/ql/src/semmle/code/java/deadcode/DeadEnumConstant.qll index 1c19a9e20cf..c29b167d31e 100644 --- a/java/ql/src/semmle/code/java/deadcode/DeadEnumConstant.qll +++ b/java/ql/src/semmle/code/java/deadcode/DeadEnumConstant.qll @@ -11,8 +11,6 @@ Expr valueFlow(Expr src) { src = c.getTrueExpr() or src = c.getFalseExpr() ) - or - src = result.(ParExpr).getExpr() } /** diff --git a/java/ql/src/semmle/code/java/dispatch/DispatchFlow.qll b/java/ql/src/semmle/code/java/dispatch/DispatchFlow.qll index 4194a4cd97c..0b2b871e129 100644 --- a/java/ql/src/semmle/code/java/dispatch/DispatchFlow.qll +++ b/java/ql/src/semmle/code/java/dispatch/DispatchFlow.qll @@ -192,8 +192,6 @@ private predicate flowStep(RelevantNode n1, RelevantNode n2) { n2.asExpr().(MethodAccess).getMethod() = getValue ) or - n2.asExpr().(ParExpr).getExpr() = n1.asExpr() - or n2.asExpr().(CastExpr).getExpr() = n1.asExpr() or n2.asExpr().(ConditionalExpr).getTrueExpr() = n1.asExpr() diff --git a/java/ql/src/semmle/code/java/dispatch/ObjFlow.qll b/java/ql/src/semmle/code/java/dispatch/ObjFlow.qll index c0740a43364..4d97bfdc3cc 100644 --- a/java/ql/src/semmle/code/java/dispatch/ObjFlow.qll +++ b/java/ql/src/semmle/code/java/dispatch/ObjFlow.qll @@ -98,8 +98,6 @@ private predicate step(Node n1, Node n2) { n2.asExpr().(FieldRead).getField() = f ) or - n2.asExpr().(ParExpr).getExpr() = n1.asExpr() - or n2.asExpr().(CastExpr).getExpr() = n1.asExpr() or n2.asExpr().(ConditionalExpr).getTrueExpr() = n1.asExpr() diff --git a/java/ql/src/semmle/code/java/frameworks/ApacheLdap.qll b/java/ql/src/semmle/code/java/frameworks/ApacheLdap.qll new file mode 100644 index 00000000000..a1cf6376bdf --- /dev/null +++ b/java/ql/src/semmle/code/java/frameworks/ApacheLdap.qll @@ -0,0 +1,27 @@ +/** + * Provides classes and predicates for working with the Apache LDAP API. + */ + +import java +import semmle.code.java.Type +import semmle.code.java.Member + +/*--- Types ---*/ +/** The interface `org.apache.directory.ldap.client.api.LdapConnection`. */ +class TypeApacheLdapConnection extends Interface { + TypeApacheLdapConnection() { + this.hasQualifiedName("org.apache.directory.ldap.client.api", "LdapConnection") + } +} + +/** The interface `org.apache.directory.api.ldap.model.message.SearchRequest`. */ +class TypeApacheSearchRequest extends Interface { + TypeApacheSearchRequest() { + this.hasQualifiedName("org.apache.directory.api.ldap.model.message", "SearchRequest") + } +} + +/** The class `org.apache.directory.api.ldap.model.name.Dn`. */ +class TypeApacheDn extends Class { + TypeApacheDn() { this.hasQualifiedName("org.apache.directory.api.ldap.model.name", "Dn") } +} diff --git a/java/ql/src/semmle/code/java/frameworks/Assertions.qll b/java/ql/src/semmle/code/java/frameworks/Assertions.qll index eb210d5e0d7..810af5ca41e 100644 --- a/java/ql/src/semmle/code/java/frameworks/Assertions.qll +++ b/java/ql/src/semmle/code/java/frameworks/Assertions.qll @@ -109,6 +109,6 @@ predicate assertFail(BasicBlock bb, ControlFlowNode n) { n = m.getACheck(any(BooleanLiteral b | b.getBooleanValue() = true)) ) or exists(AssertFailMethod m | n = m.getACheck()) or - n.(AssertStmt).getExpr().getProperExpr().(BooleanLiteral).getBooleanValue() = false + n.(AssertStmt).getExpr().(BooleanLiteral).getBooleanValue() = false ) } diff --git a/java/ql/src/semmle/code/java/frameworks/Jndi.qll b/java/ql/src/semmle/code/java/frameworks/Jndi.qll new file mode 100644 index 00000000000..f25f8908033 --- /dev/null +++ b/java/ql/src/semmle/code/java/frameworks/Jndi.qll @@ -0,0 +1,59 @@ +/** + * Provides classes and predicates for working with the Java JDBC API. + */ + +import java +import semmle.code.java.Type +import semmle.code.java.Member + +/*--- Types ---*/ +/** The interface `javax.naming.directory.DirContext`. */ +class TypeDirContext extends Interface { + TypeDirContext() { this.hasQualifiedName("javax.naming.directory", "DirContext") } +} + +/** The class `javax.naming.ldap.LdapName`. */ +class TypeLdapName extends Class { + TypeLdapName() { this.hasQualifiedName("javax.naming.ldap", "LdapName") } +} + +/*--- Methods ---*/ +/** A method with the name `addAll` declared in `javax.naming.ldap.LdapName`. */ +class MethodLdapNameAddAll extends Method { + MethodLdapNameAddAll() { + getDeclaringType() instanceof TypeLdapName and + hasName("addAll") + } +} + +/** A method with the name `clone` declared in `javax.naming.ldap.LdapName`. */ +class MethodLdapNameClone extends Method { + MethodLdapNameClone() { + getDeclaringType() instanceof TypeLdapName and + hasName("clone") + } +} + +/** A method with the name `getAll` declared in `javax.naming.ldap.LdapName`. */ +class MethodLdapNameGetAll extends Method { + MethodLdapNameGetAll() { + getDeclaringType() instanceof TypeLdapName and + hasName("getAll") + } +} + +/** A method with the name `getRdns` declared in `javax.naming.ldap.LdapName`. */ +class MethodLdapNameGetRdns extends Method { + MethodLdapNameGetRdns() { + getDeclaringType() instanceof TypeLdapName and + hasName("getRdns") + } +} + +/** A method with the name `toString` declared in `javax.naming.ldap.LdapName`. */ +class MethodLdapNameToString extends Method { + MethodLdapNameToString() { + getDeclaringType() instanceof TypeLdapName and + hasName("toString") + } +} diff --git a/java/ql/src/semmle/code/java/frameworks/SpringLdap.qll b/java/ql/src/semmle/code/java/frameworks/SpringLdap.qll new file mode 100644 index 00000000000..f9dc9e81c84 --- /dev/null +++ b/java/ql/src/semmle/code/java/frameworks/SpringLdap.qll @@ -0,0 +1,193 @@ +/** + * Provides classes and predicates for working with the Spring LDAP API. + */ + +import java +import semmle.code.java.Type +import semmle.code.java.Member + +/*--- Types ---*/ +/** The class `org.springframework.ldap.core.LdapTemplate`. */ +class TypeSpringLdapTemplate extends Class { + TypeSpringLdapTemplate() { + this.hasQualifiedName("org.springframework.ldap.core", "LdapTemplate") + } +} + +/** The class `org.springframework.ldap.query.LdapQueryBuilder`. */ +class TypeSpringLdapQueryBuilder extends Class { + TypeSpringLdapQueryBuilder() { + this.hasQualifiedName("org.springframework.ldap.query", "LdapQueryBuilder") + } +} + +/** The interface `org.springframework.ldap.query.ConditionCriteria`. */ +class TypeSpringConditionCriteria extends Interface { + TypeSpringConditionCriteria() { + this.hasQualifiedName("org.springframework.ldap.query", "ConditionCriteria") + } +} + +/** The interface `org.springframework.ldap.query.ContainerCriteria`. */ +class TypeSpringContainerCriteria extends Interface { + TypeSpringContainerCriteria() { + this.hasQualifiedName("org.springframework.ldap.query", "ContainerCriteria") + } +} + +/** The class `org.springframework.ldap.filter.HardcodedFilter`. */ +class TypeSpringHardcodedFilter extends Class { + TypeSpringHardcodedFilter() { + this.hasQualifiedName("org.springframework.ldap.filter", "HardcodedFilter") + } +} + +/** The interface `org.springframework.ldap.filter.Filter`. */ +class TypeSpringLdapFilter extends Interface { + TypeSpringLdapFilter() { this.hasQualifiedName("org.springframework.ldap.filter", "Filter") } +} + +/** The class `org.springframework.ldap.support.LdapNameBuilder`. */ +class TypeSpringLdapNameBuilder extends Class { + TypeSpringLdapNameBuilder() { + this.hasQualifiedName("org.springframework.ldap.support", "LdapNameBuilder") + } +} + +/** The class `org.springframework.ldap.support.LdapUtils`. */ +class TypeSpringLdapUtils extends Class { + TypeSpringLdapUtils() { this.hasQualifiedName("org.springframework.ldap.support", "LdapUtils") } +} + +/*--- Methods ---*/ +/** + * A method with the name `authenticate` declared in + * `org.springframework.ldap.core.LdapTemplate`. + */ +class MethodSpringLdapTemplateAuthenticate extends Method { + MethodSpringLdapTemplateAuthenticate() { + getDeclaringType() instanceof TypeSpringLdapTemplate and + hasName("authenticate") + } +} + +/** + * A method with the name `find` declared in + * `org.springframework.ldap.core.LdapTemplate`. + */ +class MethodSpringLdapTemplateFind extends Method { + MethodSpringLdapTemplateFind() { + getDeclaringType() instanceof TypeSpringLdapTemplate and + hasName("find") + } +} + +/** + * A method with the name `findOne` declared in + * `org.springframework.ldap.core.LdapTemplate`. + */ +class MethodSpringLdapTemplateFindOne extends Method { + MethodSpringLdapTemplateFindOne() { + getDeclaringType() instanceof TypeSpringLdapTemplate and + hasName("findOne") + } +} + +/** + * A method with the name `search` declared in + * `org.springframework.ldap.core.LdapTemplate`. + */ +class MethodSpringLdapTemplateSearch extends Method { + MethodSpringLdapTemplateSearch() { + getDeclaringType() instanceof TypeSpringLdapTemplate and + hasName("search") + } +} + +/** + * A method with the name `searchForContext` declared in + * `org.springframework.ldap.core.LdapTemplate`. + */ +class MethodSpringLdapTemplateSearchForContext extends Method { + MethodSpringLdapTemplateSearchForContext() { + getDeclaringType() instanceof TypeSpringLdapTemplate and + hasName("searchForContext") + } +} + +/** + * A method with the name `searchForObject` declared in + * `org.springframework.ldap.core.LdapTemplate`. + */ +class MethodSpringLdapTemplateSearchForObject extends Method { + MethodSpringLdapTemplateSearchForObject() { + getDeclaringType() instanceof TypeSpringLdapTemplate and + hasName("searchForObject") + } +} + +/** + * A method with the name `filter` declared in + * `org.springframework.ldap.query.LdapQueryBuilder`. + */ +class MethodSpringLdapQueryBuilderFilter extends Method { + MethodSpringLdapQueryBuilderFilter() { + getDeclaringType() instanceof TypeSpringLdapQueryBuilder and + hasName("filter") + } +} + +/** + * A method with the name `base` declared in + * `org.springframework.ldap.query.LdapQueryBuilder`. + */ +class MethodSpringLdapQueryBuilderBase extends Method { + MethodSpringLdapQueryBuilderBase() { + getDeclaringType() instanceof TypeSpringLdapQueryBuilder and + hasName("base") + } +} + +/** + * A method with the name `newInstance` declared in + * `org.springframework.ldap.support.LdapNameBuilder`. + */ +class MethodSpringLdapNameBuilderNewInstance extends Method { + MethodSpringLdapNameBuilderNewInstance() { + getDeclaringType() instanceof TypeSpringLdapNameBuilder and + hasName("newInstance") + } +} + +/** + * A method with the name `add` declared in + * `org.springframework.ldap.support.LdapNameBuilder`. + */ +class MethodSpringLdapNameBuilderAdd extends Method { + MethodSpringLdapNameBuilderAdd() { + getDeclaringType() instanceof TypeSpringLdapNameBuilder and + hasName("add") + } +} + +/** + * A method with the name `build` declared in + * `org.springframework.ldap.support.LdapNameBuilder`. + */ +class MethodSpringLdapNameBuilderBuild extends Method { + MethodSpringLdapNameBuilderBuild() { + getDeclaringType() instanceof TypeSpringLdapNameBuilder and + hasName("build") + } +} + +/** + * A method with the name `newLdapName` declared in + * `org.springframework.ldap.support.LdapUtils`. + */ +class MethodSpringLdapUtilsNewLdapName extends Method { + MethodSpringLdapUtilsNewLdapName() { + getDeclaringType() instanceof TypeSpringLdapUtils and + hasName("newLdapName") + } +} diff --git a/java/ql/src/semmle/code/java/frameworks/UnboundId.qll b/java/ql/src/semmle/code/java/frameworks/UnboundId.qll new file mode 100644 index 00000000000..8eee0f14ce5 --- /dev/null +++ b/java/ql/src/semmle/code/java/frameworks/UnboundId.qll @@ -0,0 +1,113 @@ +/** + * Provides classes and predicates for working with the UnboundID API. + */ + +import java +import semmle.code.java.Type +import semmle.code.java.Member + +/*--- Types ---*/ +/** The interface `com.unboundid.ldap.sdk.ReadOnlySearchRequest`. */ +class TypeUnboundIdReadOnlySearchRequest extends Interface { + TypeUnboundIdReadOnlySearchRequest() { + this.hasQualifiedName("com.unboundid.ldap.sdk", "ReadOnlySearchRequest") + } +} + +/** The class `com.unboundid.ldap.sdk.SearchRequest`. */ +class TypeUnboundIdSearchRequest extends Class { + TypeUnboundIdSearchRequest() { this.hasQualifiedName("com.unboundid.ldap.sdk", "SearchRequest") } +} + +/** The class `com.unboundid.ldap.sdk.Filter`. */ +class TypeUnboundIdLdapFilter extends Class { + TypeUnboundIdLdapFilter() { this.hasQualifiedName("com.unboundid.ldap.sdk", "Filter") } +} + +/** The class `com.unboundid.ldap.sdk.LDAPConnection`. */ +class TypeUnboundIdLDAPConnection extends Class { + TypeUnboundIdLDAPConnection() { + this.hasQualifiedName("com.unboundid.ldap.sdk", "LDAPConnection") + } +} + +/*--- Methods ---*/ +/** A method with the name `setBaseDN` declared in `com.unboundid.ldap.sdk.SearchRequest`. */ +class MethodUnboundIdSearchRequestSetBaseDN extends Method { + MethodUnboundIdSearchRequestSetBaseDN() { + getDeclaringType() instanceof TypeUnboundIdSearchRequest and + hasName("setBaseDN") + } +} + +/** A method with the name `setFilter` declared in `com.unboundid.ldap.sdk.SearchRequest`. */ +class MethodUnboundIdSearchRequestSetFilter extends Method { + MethodUnboundIdSearchRequestSetFilter() { + getDeclaringType() instanceof TypeUnboundIdSearchRequest and + hasName("setFilter") + } +} + +/** A method with the name `create` declared in `com.unboundid.ldap.sdk.Filter`. */ +class MethodUnboundIdFilterCreate extends Method { + MethodUnboundIdFilterCreate() { + getDeclaringType() instanceof TypeUnboundIdLdapFilter and + hasName("create") + } +} + +/** A method with the name `createANDFilter` declared in `com.unboundid.ldap.sdk.Filter`. */ +class MethodUnboundIdFilterCreateANDFilter extends Method { + MethodUnboundIdFilterCreateANDFilter() { + getDeclaringType() instanceof TypeUnboundIdLdapFilter and + hasName("createANDFilter") + } +} + +/** A method with the name `createORFilter` declared in `com.unboundid.ldap.sdk.Filter`. */ +class MethodUnboundIdFilterCreateORFilter extends Method { + MethodUnboundIdFilterCreateORFilter() { + getDeclaringType() instanceof TypeUnboundIdLdapFilter and + hasName("createORFilter") + } +} + +/** A method with the name `createNOTFilter` declared in `com.unboundid.ldap.sdk.Filter`. */ +class MethodUnboundIdFilterCreateNOTFilter extends Method { + MethodUnboundIdFilterCreateNOTFilter() { + getDeclaringType() instanceof TypeUnboundIdLdapFilter and + hasName("createNOTFilter") + } +} + +/** A method with the name `simplifyFilter` declared in `com.unboundid.ldap.sdk.Filter`. */ +class MethodUnboundIdFilterSimplifyFilter extends Method { + MethodUnboundIdFilterSimplifyFilter() { + getDeclaringType() instanceof TypeUnboundIdLdapFilter and + hasName("simplifyFilter") + } +} + +/** A method with the name `search` declared in `com.unboundid.ldap.sdk.LDAPConnection`. */ +class MethodUnboundIdLDAPConnectionSearch extends Method { + MethodUnboundIdLDAPConnectionSearch() { + getDeclaringType() instanceof TypeUnboundIdLDAPConnection and + hasName("search") + } +} + +/** A method with the name `asyncSearch` declared in `com.unboundid.ldap.sdk.LDAPConnection`. */ +class MethodUnboundIdLDAPConnectionAsyncSearch extends Method { + MethodUnboundIdLDAPConnectionAsyncSearch() { + getDeclaringType() instanceof TypeUnboundIdLDAPConnection and + hasName("asyncSearch") + } +} + +/** A method with the name `searchForEntry` declared in `com.unboundid.ldap.sdk.LDAPConnection`. */ +class MethodUnboundIdLDAPConnectionSearchForEntry extends Method { + MethodUnboundIdLDAPConnectionSearchForEntry() { + getDeclaringType() instanceof TypeUnboundIdLDAPConnection and + hasName("searchForEntry") + } +} diff --git a/java/ql/src/semmle/code/java/security/Random.qll b/java/ql/src/semmle/code/java/security/Random.qll index 7929309821c..095b619c9c6 100644 --- a/java/ql/src/semmle/code/java/security/Random.qll +++ b/java/ql/src/semmle/code/java/security/Random.qll @@ -36,10 +36,6 @@ private class PredictableSeedFlowConfiguration extends DataFlow::Configuration { } } -private class TypeNumber extends Class { - TypeNumber() { this.getQualifiedName() = "java.lang.Number" } -} - private predicate predictableCalcStep(Expr e1, Expr e2) { e2.(BinaryExpr).hasOperands(e1, any(PredictableSeedExpr p)) or diff --git a/java/ql/test/library-tests/dataflow/null/A.java b/java/ql/test/library-tests/dataflow/null/A.java new file mode 100644 index 00000000000..252358f7996 --- /dev/null +++ b/java/ql/test/library-tests/dataflow/null/A.java @@ -0,0 +1,9 @@ +public class A { + void sink(Object o) { } + + void foo() { + Object src = null; + Object x = src; + sink(x); + } +} diff --git a/java/ql/test/library-tests/dataflow/null/testnullflow.expected b/java/ql/test/library-tests/dataflow/null/testnullflow.expected new file mode 100644 index 00000000000..532d64e81f8 --- /dev/null +++ b/java/ql/test/library-tests/dataflow/null/testnullflow.expected @@ -0,0 +1,4 @@ +| A.java:5:18:5:21 | null | A.java:2:13:2:20 | o | +| A.java:5:18:5:21 | null | A.java:5:18:5:21 | null | +| A.java:5:18:5:21 | null | A.java:6:16:6:18 | src | +| A.java:5:18:5:21 | null | A.java:7:10:7:10 | x | diff --git a/java/ql/test/library-tests/dataflow/null/testnullflow.ql b/java/ql/test/library-tests/dataflow/null/testnullflow.ql new file mode 100644 index 00000000000..d0937e9c0f4 --- /dev/null +++ b/java/ql/test/library-tests/dataflow/null/testnullflow.ql @@ -0,0 +1,14 @@ +import java +import semmle.code.java.dataflow.DataFlow + +class Conf extends DataFlow::Configuration { + Conf() { this = "qqconf" } + + override predicate isSource(DataFlow::Node n) { n.asExpr() instanceof NullLiteral } + + override predicate isSink(DataFlow::Node n) { any() } +} + +from Conf conf, DataFlow::Node src, DataFlow::Node sink +where conf.hasFlow(src, sink) +select src, sink diff --git a/java/ql/test/library-tests/dataflow/taint/A.java b/java/ql/test/library-tests/dataflow/taint/A.java new file mode 100644 index 00000000000..d6509f15936 --- /dev/null +++ b/java/ql/test/library-tests/dataflow/taint/A.java @@ -0,0 +1,27 @@ +import java.io.*; + +public class A { + byte[] taint() { return new byte[2]; } + + void sink(Object o) { } + + void test1() { + ByteArrayOutputStream bOutput = new ByteArrayOutputStream(); + bOutput.write(taint(), 0, 1); + byte[] b = bOutput.toByteArray(); + ByteArrayInputStream bInput = new ByteArrayInputStream(b); + byte[] b2 = new byte[10]; + bInput.read(b2, 0, 1); + sink(b2); + } + + void test2() { + ByteArrayOutputStream bOutput = new ByteArrayOutputStream(); + bOutput.write(taint()); + byte[] b = bOutput.toByteArray(); + ByteArrayInputStream bInput = new ByteArrayInputStream(b); + byte[] b2 = new byte[10]; + bInput.read(b2); + sink(b2); + } +} diff --git a/java/ql/test/library-tests/dataflow/taint/test.expected b/java/ql/test/library-tests/dataflow/taint/test.expected new file mode 100644 index 00000000000..fdab0e9be24 --- /dev/null +++ b/java/ql/test/library-tests/dataflow/taint/test.expected @@ -0,0 +1,2 @@ +| A.java:10:19:10:25 | taint(...) | A.java:15:10:15:11 | b2 | +| A.java:20:19:20:25 | taint(...) | A.java:25:10:25:11 | b2 | diff --git a/java/ql/test/library-tests/dataflow/taint/test.ql b/java/ql/test/library-tests/dataflow/taint/test.ql new file mode 100644 index 00000000000..65b15fbaa4e --- /dev/null +++ b/java/ql/test/library-tests/dataflow/taint/test.ql @@ -0,0 +1,18 @@ +import java +import semmle.code.java.dataflow.TaintTracking + +class Conf extends TaintTracking::Configuration { + Conf() { this = "qqconf" } + + override predicate isSource(DataFlow::Node n) { + n.asExpr().(MethodAccess).getMethod().hasName("taint") + } + + override predicate isSink(DataFlow::Node n) { + n.asExpr().(Argument).getCall().getCallee().hasName("sink") + } +} + +from DataFlow::Node src, DataFlow::Node sink, Conf conf +where conf.hasFlow(src, sink) +select src, sink diff --git a/java/ql/test/library-tests/guards/guardslogic.ql b/java/ql/test/library-tests/guards/guardslogic.ql index 516a8670473..afbb313d664 100644 --- a/java/ql/test/library-tests/guards/guardslogic.ql +++ b/java/ql/test/library-tests/guards/guardslogic.ql @@ -4,6 +4,5 @@ import semmle.code.java.controlflow.Guards from Guard g, BasicBlock bb, boolean branch where g.controls(bb, branch) and - not g instanceof ParExpr and g.getEnclosingCallable().getDeclaringType().hasName("Logic") select g, branch, bb diff --git a/java/ql/test/query-tests/security/CWE-090/LdapInjection.expected b/java/ql/test/query-tests/security/CWE-090/LdapInjection.expected new file mode 100644 index 00000000000..c275cda2e58 --- /dev/null +++ b/java/ql/test/query-tests/security/CWE-090/LdapInjection.expected @@ -0,0 +1,231 @@ +edges +| LdapInjection.java:41:28:41:52 | jBad : String | LdapInjection.java:43:38:43:57 | ... + ... | +| LdapInjection.java:41:55:41:81 | jBadDN : String | LdapInjection.java:43:16:43:35 | ... + ... | +| LdapInjection.java:46:28:46:52 | jBad : String | LdapInjection.java:48:56:48:75 | ... + ... | +| LdapInjection.java:46:55:46:85 | jBadDNName : String | LdapInjection.java:48:16:48:53 | new LdapName(...) | +| LdapInjection.java:51:28:51:52 | jBad : String | LdapInjection.java:53:63:53:82 | ... + ... | +| LdapInjection.java:56:28:56:59 | jBadInitial : String | LdapInjection.java:58:29:58:55 | ... + ... | +| LdapInjection.java:61:28:61:52 | jBad : String | LdapInjection.java:63:84:63:103 | ... + ... | +| LdapInjection.java:61:55:61:88 | jBadDNNameAdd : String | LdapInjection.java:63:16:63:81 | addAll(...) | +| LdapInjection.java:66:28:66:52 | jBad : String | LdapInjection.java:70:47:70:66 | ... + ... | +| LdapInjection.java:66:55:66:89 | jBadDNNameAdd2 : String | LdapInjection.java:70:16:70:44 | addAll(...) | +| LdapInjection.java:73:28:73:52 | jBad : String | LdapInjection.java:75:75:75:94 | ... + ... | +| LdapInjection.java:73:55:73:93 | jBadDNNameToString : String | LdapInjection.java:75:16:75:72 | toString(...) | +| LdapInjection.java:78:28:78:52 | jBad : String | LdapInjection.java:80:76:80:95 | ... + ... | +| LdapInjection.java:78:55:78:90 | jBadDNNameClone : String | LdapInjection.java:80:16:80:73 | (...)... | +| LdapInjection.java:92:31:92:55 | uBad : String | LdapInjection.java:94:67:94:86 | ... + ... | +| LdapInjection.java:92:58:92:84 | uBadDN : String | LdapInjection.java:94:20:94:39 | ... + ... | +| LdapInjection.java:97:31:97:67 | uBadFilterCreate : String | LdapInjection.java:98:58:98:88 | create(...) | +| LdapInjection.java:101:31:101:70 | uBadROSearchRequest : String | LdapInjection.java:105:14:105:14 | s | +| LdapInjection.java:101:73:101:103 | uBadROSRDN : String | LdapInjection.java:105:14:105:14 | s | +| LdapInjection.java:108:31:108:68 | uBadSearchRequest : String | LdapInjection.java:112:14:112:14 | s | +| LdapInjection.java:108:71:108:99 | uBadSRDN : String | LdapInjection.java:112:14:112:14 | s | +| LdapInjection.java:115:31:115:55 | uBad : String | LdapInjection.java:117:69:117:88 | ... + ... | +| LdapInjection.java:115:58:115:87 | uBadDNSFR : String | LdapInjection.java:117:22:117:44 | ... + ... | +| LdapInjection.java:120:31:120:75 | uBadROSearchRequestAsync : String | LdapInjection.java:124:19:124:19 | s | +| LdapInjection.java:120:78:120:113 | uBadROSRDNAsync : String | LdapInjection.java:124:19:124:19 | s | +| LdapInjection.java:127:31:127:73 | uBadSearchRequestAsync : String | LdapInjection.java:131:19:131:19 | s | +| LdapInjection.java:127:76:127:109 | uBadSRDNAsync : String | LdapInjection.java:131:19:131:19 | s | +| LdapInjection.java:134:31:134:70 | uBadFilterCreateNOT : String | LdapInjection.java:135:58:135:115 | createNOTFilter(...) | +| LdapInjection.java:138:31:138:75 | uBadFilterCreateToString : String | LdapInjection.java:139:58:139:107 | toString(...) | +| LdapInjection.java:142:32:142:82 | uBadFilterCreateToStringBuffer : String | LdapInjection.java:145:58:145:69 | toString(...) | +| LdapInjection.java:148:32:148:78 | uBadSearchRequestDuplicate : String | LdapInjection.java:152:14:152:26 | duplicate(...) | +| LdapInjection.java:155:32:155:80 | uBadROSearchRequestDuplicate : String | LdapInjection.java:159:14:159:26 | duplicate(...) | +| LdapInjection.java:162:32:162:74 | uBadSearchRequestSetDN : String | LdapInjection.java:166:14:166:14 | s | +| LdapInjection.java:169:32:169:78 | uBadSearchRequestSetFilter : String | LdapInjection.java:173:14:173:14 | s | +| LdapInjection.java:197:30:197:54 | sBad : String | LdapInjection.java:198:36:198:55 | ... + ... | +| LdapInjection.java:197:57:197:83 | sBadDN : String | LdapInjection.java:198:14:198:33 | ... + ... | +| LdapInjection.java:201:30:201:54 | sBad : String | LdapInjection.java:202:88:202:107 | ... + ... | +| LdapInjection.java:201:57:201:92 | sBadDNLNBuilder : String | LdapInjection.java:202:20:202:85 | build(...) | +| LdapInjection.java:205:30:205:54 | sBad : String | LdapInjection.java:206:100:206:119 | ... + ... | +| LdapInjection.java:205:57:205:95 | sBadDNLNBuilderAdd : String | LdapInjection.java:206:23:206:97 | build(...) | +| LdapInjection.java:209:30:209:63 | sBadLdapQuery : String | LdapInjection.java:210:15:210:76 | filter(...) | +| LdapInjection.java:213:30:213:60 | sBadFilter : String | LdapInjection.java:214:66:214:112 | new HardcodedFilter(...) | +| LdapInjection.java:213:63:213:98 | sBadDNLdapUtils : String | LdapInjection.java:214:12:214:63 | newLdapName(...) | +| LdapInjection.java:217:30:217:63 | sBadLdapQuery : String | LdapInjection.java:218:24:218:85 | filter(...) | +| LdapInjection.java:221:30:221:64 | sBadLdapQuery2 : String | LdapInjection.java:223:24:223:24 | q | +| LdapInjection.java:226:30:226:73 | sBadLdapQueryWithFilter : String | LdapInjection.java:227:24:227:116 | filter(...) | +| LdapInjection.java:230:30:230:74 | sBadLdapQueryWithFilter2 : String | LdapInjection.java:232:24:232:57 | filter(...) | +| LdapInjection.java:235:31:235:68 | sBadLdapQueryBase : String | LdapInjection.java:236:12:236:66 | base(...) | +| LdapInjection.java:239:31:239:71 | sBadLdapQueryComplex : String | LdapInjection.java:240:24:240:98 | is(...) | +| LdapInjection.java:243:31:243:69 | sBadFilterToString : String | LdapInjection.java:244:18:244:83 | toString(...) | +| LdapInjection.java:247:31:247:67 | sBadFilterEncode : String | LdapInjection.java:250:18:250:29 | toString(...) | +| LdapInjection.java:266:30:266:54 | aBad : String | LdapInjection.java:268:36:268:55 | ... + ... | +| LdapInjection.java:266:57:266:83 | aBadDN : String | LdapInjection.java:268:14:268:33 | ... + ... | +| LdapInjection.java:271:30:271:54 | aBad : String | LdapInjection.java:273:65:273:84 | ... + ... | +| LdapInjection.java:271:57:271:94 | aBadDNObjToString : String | LdapInjection.java:273:14:273:62 | getName(...) | +| LdapInjection.java:276:30:276:67 | aBadSearchRequest : String | LdapInjection.java:280:14:280:14 | s | +| LdapInjection.java:283:74:283:103 | aBadDNObj : String | LdapInjection.java:287:14:287:14 | s | +| LdapInjection.java:290:30:290:72 | aBadDNSearchRequestGet : String | LdapInjection.java:294:14:294:24 | getBase(...) | +nodes +| LdapInjection.java:41:28:41:52 | jBad : String | semmle.label | jBad : String | +| LdapInjection.java:41:55:41:81 | jBadDN : String | semmle.label | jBadDN : String | +| LdapInjection.java:43:16:43:35 | ... + ... | semmle.label | ... + ... | +| LdapInjection.java:43:38:43:57 | ... + ... | semmle.label | ... + ... | +| LdapInjection.java:46:28:46:52 | jBad : String | semmle.label | jBad : String | +| LdapInjection.java:46:55:46:85 | jBadDNName : String | semmle.label | jBadDNName : String | +| LdapInjection.java:48:16:48:53 | new LdapName(...) | semmle.label | new LdapName(...) | +| LdapInjection.java:48:56:48:75 | ... + ... | semmle.label | ... + ... | +| LdapInjection.java:51:28:51:52 | jBad : String | semmle.label | jBad : String | +| LdapInjection.java:53:63:53:82 | ... + ... | semmle.label | ... + ... | +| LdapInjection.java:56:28:56:59 | jBadInitial : String | semmle.label | jBadInitial : String | +| LdapInjection.java:58:29:58:55 | ... + ... | semmle.label | ... + ... | +| LdapInjection.java:61:28:61:52 | jBad : String | semmle.label | jBad : String | +| LdapInjection.java:61:55:61:88 | jBadDNNameAdd : String | semmle.label | jBadDNNameAdd : String | +| LdapInjection.java:63:16:63:81 | addAll(...) | semmle.label | addAll(...) | +| LdapInjection.java:63:84:63:103 | ... + ... | semmle.label | ... + ... | +| LdapInjection.java:66:28:66:52 | jBad : String | semmle.label | jBad : String | +| LdapInjection.java:66:55:66:89 | jBadDNNameAdd2 : String | semmle.label | jBadDNNameAdd2 : String | +| LdapInjection.java:70:16:70:44 | addAll(...) | semmle.label | addAll(...) | +| LdapInjection.java:70:47:70:66 | ... + ... | semmle.label | ... + ... | +| LdapInjection.java:73:28:73:52 | jBad : String | semmle.label | jBad : String | +| LdapInjection.java:73:55:73:93 | jBadDNNameToString : String | semmle.label | jBadDNNameToString : String | +| LdapInjection.java:75:16:75:72 | toString(...) | semmle.label | toString(...) | +| LdapInjection.java:75:75:75:94 | ... + ... | semmle.label | ... + ... | +| LdapInjection.java:78:28:78:52 | jBad : String | semmle.label | jBad : String | +| LdapInjection.java:78:55:78:90 | jBadDNNameClone : String | semmle.label | jBadDNNameClone : String | +| LdapInjection.java:80:16:80:73 | (...)... | semmle.label | (...)... | +| LdapInjection.java:80:76:80:95 | ... + ... | semmle.label | ... + ... | +| LdapInjection.java:92:31:92:55 | uBad : String | semmle.label | uBad : String | +| LdapInjection.java:92:58:92:84 | uBadDN : String | semmle.label | uBadDN : String | +| LdapInjection.java:94:20:94:39 | ... + ... | semmle.label | ... + ... | +| LdapInjection.java:94:67:94:86 | ... + ... | semmle.label | ... + ... | +| LdapInjection.java:97:31:97:67 | uBadFilterCreate : String | semmle.label | uBadFilterCreate : String | +| LdapInjection.java:98:58:98:88 | create(...) | semmle.label | create(...) | +| LdapInjection.java:101:31:101:70 | uBadROSearchRequest : String | semmle.label | uBadROSearchRequest : String | +| LdapInjection.java:101:73:101:103 | uBadROSRDN : String | semmle.label | uBadROSRDN : String | +| LdapInjection.java:105:14:105:14 | s | semmle.label | s | +| LdapInjection.java:108:31:108:68 | uBadSearchRequest : String | semmle.label | uBadSearchRequest : String | +| LdapInjection.java:108:71:108:99 | uBadSRDN : String | semmle.label | uBadSRDN : String | +| LdapInjection.java:112:14:112:14 | s | semmle.label | s | +| LdapInjection.java:115:31:115:55 | uBad : String | semmle.label | uBad : String | +| LdapInjection.java:115:58:115:87 | uBadDNSFR : String | semmle.label | uBadDNSFR : String | +| LdapInjection.java:117:22:117:44 | ... + ... | semmle.label | ... + ... | +| LdapInjection.java:117:69:117:88 | ... + ... | semmle.label | ... + ... | +| LdapInjection.java:120:31:120:75 | uBadROSearchRequestAsync : String | semmle.label | uBadROSearchRequestAsync : String | +| LdapInjection.java:120:78:120:113 | uBadROSRDNAsync : String | semmle.label | uBadROSRDNAsync : String | +| LdapInjection.java:124:19:124:19 | s | semmle.label | s | +| LdapInjection.java:127:31:127:73 | uBadSearchRequestAsync : String | semmle.label | uBadSearchRequestAsync : String | +| LdapInjection.java:127:76:127:109 | uBadSRDNAsync : String | semmle.label | uBadSRDNAsync : String | +| LdapInjection.java:131:19:131:19 | s | semmle.label | s | +| LdapInjection.java:134:31:134:70 | uBadFilterCreateNOT : String | semmle.label | uBadFilterCreateNOT : String | +| LdapInjection.java:135:58:135:115 | createNOTFilter(...) | semmle.label | createNOTFilter(...) | +| LdapInjection.java:138:31:138:75 | uBadFilterCreateToString : String | semmle.label | uBadFilterCreateToString : String | +| LdapInjection.java:139:58:139:107 | toString(...) | semmle.label | toString(...) | +| LdapInjection.java:142:32:142:82 | uBadFilterCreateToStringBuffer : String | semmle.label | uBadFilterCreateToStringBuffer : String | +| LdapInjection.java:145:58:145:69 | toString(...) | semmle.label | toString(...) | +| LdapInjection.java:148:32:148:78 | uBadSearchRequestDuplicate : String | semmle.label | uBadSearchRequestDuplicate : String | +| LdapInjection.java:152:14:152:26 | duplicate(...) | semmle.label | duplicate(...) | +| LdapInjection.java:155:32:155:80 | uBadROSearchRequestDuplicate : String | semmle.label | uBadROSearchRequestDuplicate : String | +| LdapInjection.java:159:14:159:26 | duplicate(...) | semmle.label | duplicate(...) | +| LdapInjection.java:162:32:162:74 | uBadSearchRequestSetDN : String | semmle.label | uBadSearchRequestSetDN : String | +| LdapInjection.java:166:14:166:14 | s | semmle.label | s | +| LdapInjection.java:169:32:169:78 | uBadSearchRequestSetFilter : String | semmle.label | uBadSearchRequestSetFilter : String | +| LdapInjection.java:173:14:173:14 | s | semmle.label | s | +| LdapInjection.java:197:30:197:54 | sBad : String | semmle.label | sBad : String | +| LdapInjection.java:197:57:197:83 | sBadDN : String | semmle.label | sBadDN : String | +| LdapInjection.java:198:14:198:33 | ... + ... | semmle.label | ... + ... | +| LdapInjection.java:198:36:198:55 | ... + ... | semmle.label | ... + ... | +| LdapInjection.java:201:30:201:54 | sBad : String | semmle.label | sBad : String | +| LdapInjection.java:201:57:201:92 | sBadDNLNBuilder : String | semmle.label | sBadDNLNBuilder : String | +| LdapInjection.java:202:20:202:85 | build(...) | semmle.label | build(...) | +| LdapInjection.java:202:88:202:107 | ... + ... | semmle.label | ... + ... | +| LdapInjection.java:205:30:205:54 | sBad : String | semmle.label | sBad : String | +| LdapInjection.java:205:57:205:95 | sBadDNLNBuilderAdd : String | semmle.label | sBadDNLNBuilderAdd : String | +| LdapInjection.java:206:23:206:97 | build(...) | semmle.label | build(...) | +| LdapInjection.java:206:100:206:119 | ... + ... | semmle.label | ... + ... | +| LdapInjection.java:209:30:209:63 | sBadLdapQuery : String | semmle.label | sBadLdapQuery : String | +| LdapInjection.java:210:15:210:76 | filter(...) | semmle.label | filter(...) | +| LdapInjection.java:213:30:213:60 | sBadFilter : String | semmle.label | sBadFilter : String | +| LdapInjection.java:213:63:213:98 | sBadDNLdapUtils : String | semmle.label | sBadDNLdapUtils : String | +| LdapInjection.java:214:12:214:63 | newLdapName(...) | semmle.label | newLdapName(...) | +| LdapInjection.java:214:66:214:112 | new HardcodedFilter(...) | semmle.label | new HardcodedFilter(...) | +| LdapInjection.java:217:30:217:63 | sBadLdapQuery : String | semmle.label | sBadLdapQuery : String | +| LdapInjection.java:218:24:218:85 | filter(...) | semmle.label | filter(...) | +| LdapInjection.java:221:30:221:64 | sBadLdapQuery2 : String | semmle.label | sBadLdapQuery2 : String | +| LdapInjection.java:223:24:223:24 | q | semmle.label | q | +| LdapInjection.java:226:30:226:73 | sBadLdapQueryWithFilter : String | semmle.label | sBadLdapQueryWithFilter : String | +| LdapInjection.java:227:24:227:116 | filter(...) | semmle.label | filter(...) | +| LdapInjection.java:230:30:230:74 | sBadLdapQueryWithFilter2 : String | semmle.label | sBadLdapQueryWithFilter2 : String | +| LdapInjection.java:232:24:232:57 | filter(...) | semmle.label | filter(...) | +| LdapInjection.java:235:31:235:68 | sBadLdapQueryBase : String | semmle.label | sBadLdapQueryBase : String | +| LdapInjection.java:236:12:236:66 | base(...) | semmle.label | base(...) | +| LdapInjection.java:239:31:239:71 | sBadLdapQueryComplex : String | semmle.label | sBadLdapQueryComplex : String | +| LdapInjection.java:240:24:240:98 | is(...) | semmle.label | is(...) | +| LdapInjection.java:243:31:243:69 | sBadFilterToString : String | semmle.label | sBadFilterToString : String | +| LdapInjection.java:244:18:244:83 | toString(...) | semmle.label | toString(...) | +| LdapInjection.java:247:31:247:67 | sBadFilterEncode : String | semmle.label | sBadFilterEncode : String | +| LdapInjection.java:250:18:250:29 | toString(...) | semmle.label | toString(...) | +| LdapInjection.java:266:30:266:54 | aBad : String | semmle.label | aBad : String | +| LdapInjection.java:266:57:266:83 | aBadDN : String | semmle.label | aBadDN : String | +| LdapInjection.java:268:14:268:33 | ... + ... | semmle.label | ... + ... | +| LdapInjection.java:268:36:268:55 | ... + ... | semmle.label | ... + ... | +| LdapInjection.java:271:30:271:54 | aBad : String | semmle.label | aBad : String | +| LdapInjection.java:271:57:271:94 | aBadDNObjToString : String | semmle.label | aBadDNObjToString : String | +| LdapInjection.java:273:14:273:62 | getName(...) | semmle.label | getName(...) | +| LdapInjection.java:273:65:273:84 | ... + ... | semmle.label | ... + ... | +| LdapInjection.java:276:30:276:67 | aBadSearchRequest : String | semmle.label | aBadSearchRequest : String | +| LdapInjection.java:280:14:280:14 | s | semmle.label | s | +| LdapInjection.java:283:74:283:103 | aBadDNObj : String | semmle.label | aBadDNObj : String | +| LdapInjection.java:287:14:287:14 | s | semmle.label | s | +| LdapInjection.java:290:30:290:72 | aBadDNSearchRequestGet : String | semmle.label | aBadDNSearchRequestGet : String | +| LdapInjection.java:294:14:294:24 | getBase(...) | semmle.label | getBase(...) | +#select +| LdapInjection.java:43:16:43:35 | ... + ... | LdapInjection.java:41:55:41:81 | jBadDN : String | LdapInjection.java:43:16:43:35 | ... + ... | LDAP query might include code from $@. | LdapInjection.java:41:55:41:81 | jBadDN | this user input | +| LdapInjection.java:43:38:43:57 | ... + ... | LdapInjection.java:41:28:41:52 | jBad : String | LdapInjection.java:43:38:43:57 | ... + ... | LDAP query might include code from $@. | LdapInjection.java:41:28:41:52 | jBad | this user input | +| LdapInjection.java:48:16:48:53 | new LdapName(...) | LdapInjection.java:46:55:46:85 | jBadDNName : String | LdapInjection.java:48:16:48:53 | new LdapName(...) | LDAP query might include code from $@. | LdapInjection.java:46:55:46:85 | jBadDNName | this user input | +| LdapInjection.java:48:56:48:75 | ... + ... | LdapInjection.java:46:28:46:52 | jBad : String | LdapInjection.java:48:56:48:75 | ... + ... | LDAP query might include code from $@. | LdapInjection.java:46:28:46:52 | jBad | this user input | +| LdapInjection.java:53:63:53:82 | ... + ... | LdapInjection.java:51:28:51:52 | jBad : String | LdapInjection.java:53:63:53:82 | ... + ... | LDAP query might include code from $@. | LdapInjection.java:51:28:51:52 | jBad | this user input | +| LdapInjection.java:58:29:58:55 | ... + ... | LdapInjection.java:56:28:56:59 | jBadInitial : String | LdapInjection.java:58:29:58:55 | ... + ... | LDAP query might include code from $@. | LdapInjection.java:56:28:56:59 | jBadInitial | this user input | +| LdapInjection.java:63:16:63:81 | addAll(...) | LdapInjection.java:61:55:61:88 | jBadDNNameAdd : String | LdapInjection.java:63:16:63:81 | addAll(...) | LDAP query might include code from $@. | LdapInjection.java:61:55:61:88 | jBadDNNameAdd | this user input | +| LdapInjection.java:63:84:63:103 | ... + ... | LdapInjection.java:61:28:61:52 | jBad : String | LdapInjection.java:63:84:63:103 | ... + ... | LDAP query might include code from $@. | LdapInjection.java:61:28:61:52 | jBad | this user input | +| LdapInjection.java:70:16:70:44 | addAll(...) | LdapInjection.java:66:55:66:89 | jBadDNNameAdd2 : String | LdapInjection.java:70:16:70:44 | addAll(...) | LDAP query might include code from $@. | LdapInjection.java:66:55:66:89 | jBadDNNameAdd2 | this user input | +| LdapInjection.java:70:47:70:66 | ... + ... | LdapInjection.java:66:28:66:52 | jBad : String | LdapInjection.java:70:47:70:66 | ... + ... | LDAP query might include code from $@. | LdapInjection.java:66:28:66:52 | jBad | this user input | +| LdapInjection.java:75:16:75:72 | toString(...) | LdapInjection.java:73:55:73:93 | jBadDNNameToString : String | LdapInjection.java:75:16:75:72 | toString(...) | LDAP query might include code from $@. | LdapInjection.java:73:55:73:93 | jBadDNNameToString | this user input | +| LdapInjection.java:75:75:75:94 | ... + ... | LdapInjection.java:73:28:73:52 | jBad : String | LdapInjection.java:75:75:75:94 | ... + ... | LDAP query might include code from $@. | LdapInjection.java:73:28:73:52 | jBad | this user input | +| LdapInjection.java:80:16:80:73 | (...)... | LdapInjection.java:78:55:78:90 | jBadDNNameClone : String | LdapInjection.java:80:16:80:73 | (...)... | LDAP query might include code from $@. | LdapInjection.java:78:55:78:90 | jBadDNNameClone | this user input | +| LdapInjection.java:80:76:80:95 | ... + ... | LdapInjection.java:78:28:78:52 | jBad : String | LdapInjection.java:80:76:80:95 | ... + ... | LDAP query might include code from $@. | LdapInjection.java:78:28:78:52 | jBad | this user input | +| LdapInjection.java:94:20:94:39 | ... + ... | LdapInjection.java:92:58:92:84 | uBadDN : String | LdapInjection.java:94:20:94:39 | ... + ... | LDAP query might include code from $@. | LdapInjection.java:92:58:92:84 | uBadDN | this user input | +| LdapInjection.java:94:67:94:86 | ... + ... | LdapInjection.java:92:31:92:55 | uBad : String | LdapInjection.java:94:67:94:86 | ... + ... | LDAP query might include code from $@. | LdapInjection.java:92:31:92:55 | uBad | this user input | +| LdapInjection.java:98:58:98:88 | create(...) | LdapInjection.java:97:31:97:67 | uBadFilterCreate : String | LdapInjection.java:98:58:98:88 | create(...) | LDAP query might include code from $@. | LdapInjection.java:97:31:97:67 | uBadFilterCreate | this user input | +| LdapInjection.java:105:14:105:14 | s | LdapInjection.java:101:31:101:70 | uBadROSearchRequest : String | LdapInjection.java:105:14:105:14 | s | LDAP query might include code from $@. | LdapInjection.java:101:31:101:70 | uBadROSearchRequest | this user input | +| LdapInjection.java:105:14:105:14 | s | LdapInjection.java:101:73:101:103 | uBadROSRDN : String | LdapInjection.java:105:14:105:14 | s | LDAP query might include code from $@. | LdapInjection.java:101:73:101:103 | uBadROSRDN | this user input | +| LdapInjection.java:112:14:112:14 | s | LdapInjection.java:108:31:108:68 | uBadSearchRequest : String | LdapInjection.java:112:14:112:14 | s | LDAP query might include code from $@. | LdapInjection.java:108:31:108:68 | uBadSearchRequest | this user input | +| LdapInjection.java:112:14:112:14 | s | LdapInjection.java:108:71:108:99 | uBadSRDN : String | LdapInjection.java:112:14:112:14 | s | LDAP query might include code from $@. | LdapInjection.java:108:71:108:99 | uBadSRDN | this user input | +| LdapInjection.java:117:22:117:44 | ... + ... | LdapInjection.java:115:58:115:87 | uBadDNSFR : String | LdapInjection.java:117:22:117:44 | ... + ... | LDAP query might include code from $@. | LdapInjection.java:115:58:115:87 | uBadDNSFR | this user input | +| LdapInjection.java:117:69:117:88 | ... + ... | LdapInjection.java:115:31:115:55 | uBad : String | LdapInjection.java:117:69:117:88 | ... + ... | LDAP query might include code from $@. | LdapInjection.java:115:31:115:55 | uBad | this user input | +| LdapInjection.java:124:19:124:19 | s | LdapInjection.java:120:31:120:75 | uBadROSearchRequestAsync : String | LdapInjection.java:124:19:124:19 | s | LDAP query might include code from $@. | LdapInjection.java:120:31:120:75 | uBadROSearchRequestAsync | this user input | +| LdapInjection.java:124:19:124:19 | s | LdapInjection.java:120:78:120:113 | uBadROSRDNAsync : String | LdapInjection.java:124:19:124:19 | s | LDAP query might include code from $@. | LdapInjection.java:120:78:120:113 | uBadROSRDNAsync | this user input | +| LdapInjection.java:131:19:131:19 | s | LdapInjection.java:127:31:127:73 | uBadSearchRequestAsync : String | LdapInjection.java:131:19:131:19 | s | LDAP query might include code from $@. | LdapInjection.java:127:31:127:73 | uBadSearchRequestAsync | this user input | +| LdapInjection.java:131:19:131:19 | s | LdapInjection.java:127:76:127:109 | uBadSRDNAsync : String | LdapInjection.java:131:19:131:19 | s | LDAP query might include code from $@. | LdapInjection.java:127:76:127:109 | uBadSRDNAsync | this user input | +| LdapInjection.java:135:58:135:115 | createNOTFilter(...) | LdapInjection.java:134:31:134:70 | uBadFilterCreateNOT : String | LdapInjection.java:135:58:135:115 | createNOTFilter(...) | LDAP query might include code from $@. | LdapInjection.java:134:31:134:70 | uBadFilterCreateNOT | this user input | +| LdapInjection.java:139:58:139:107 | toString(...) | LdapInjection.java:138:31:138:75 | uBadFilterCreateToString : String | LdapInjection.java:139:58:139:107 | toString(...) | LDAP query might include code from $@. | LdapInjection.java:138:31:138:75 | uBadFilterCreateToString | this user input | +| LdapInjection.java:145:58:145:69 | toString(...) | LdapInjection.java:142:32:142:82 | uBadFilterCreateToStringBuffer : String | LdapInjection.java:145:58:145:69 | toString(...) | LDAP query might include code from $@. | LdapInjection.java:142:32:142:82 | uBadFilterCreateToStringBuffer | this user input | +| LdapInjection.java:152:14:152:26 | duplicate(...) | LdapInjection.java:148:32:148:78 | uBadSearchRequestDuplicate : String | LdapInjection.java:152:14:152:26 | duplicate(...) | LDAP query might include code from $@. | LdapInjection.java:148:32:148:78 | uBadSearchRequestDuplicate | this user input | +| LdapInjection.java:159:14:159:26 | duplicate(...) | LdapInjection.java:155:32:155:80 | uBadROSearchRequestDuplicate : String | LdapInjection.java:159:14:159:26 | duplicate(...) | LDAP query might include code from $@. | LdapInjection.java:155:32:155:80 | uBadROSearchRequestDuplicate | this user input | +| LdapInjection.java:166:14:166:14 | s | LdapInjection.java:162:32:162:74 | uBadSearchRequestSetDN : String | LdapInjection.java:166:14:166:14 | s | LDAP query might include code from $@. | LdapInjection.java:162:32:162:74 | uBadSearchRequestSetDN | this user input | +| LdapInjection.java:173:14:173:14 | s | LdapInjection.java:169:32:169:78 | uBadSearchRequestSetFilter : String | LdapInjection.java:173:14:173:14 | s | LDAP query might include code from $@. | LdapInjection.java:169:32:169:78 | uBadSearchRequestSetFilter | this user input | +| LdapInjection.java:198:14:198:33 | ... + ... | LdapInjection.java:197:57:197:83 | sBadDN : String | LdapInjection.java:198:14:198:33 | ... + ... | LDAP query might include code from $@. | LdapInjection.java:197:57:197:83 | sBadDN | this user input | +| LdapInjection.java:198:36:198:55 | ... + ... | LdapInjection.java:197:30:197:54 | sBad : String | LdapInjection.java:198:36:198:55 | ... + ... | LDAP query might include code from $@. | LdapInjection.java:197:30:197:54 | sBad | this user input | +| LdapInjection.java:202:20:202:85 | build(...) | LdapInjection.java:201:57:201:92 | sBadDNLNBuilder : String | LdapInjection.java:202:20:202:85 | build(...) | LDAP query might include code from $@. | LdapInjection.java:201:57:201:92 | sBadDNLNBuilder | this user input | +| LdapInjection.java:202:88:202:107 | ... + ... | LdapInjection.java:201:30:201:54 | sBad : String | LdapInjection.java:202:88:202:107 | ... + ... | LDAP query might include code from $@. | LdapInjection.java:201:30:201:54 | sBad | this user input | +| LdapInjection.java:206:23:206:97 | build(...) | LdapInjection.java:205:57:205:95 | sBadDNLNBuilderAdd : String | LdapInjection.java:206:23:206:97 | build(...) | LDAP query might include code from $@. | LdapInjection.java:205:57:205:95 | sBadDNLNBuilderAdd | this user input | +| LdapInjection.java:206:100:206:119 | ... + ... | LdapInjection.java:205:30:205:54 | sBad : String | LdapInjection.java:206:100:206:119 | ... + ... | LDAP query might include code from $@. | LdapInjection.java:205:30:205:54 | sBad | this user input | +| LdapInjection.java:210:15:210:76 | filter(...) | LdapInjection.java:209:30:209:63 | sBadLdapQuery : String | LdapInjection.java:210:15:210:76 | filter(...) | LDAP query might include code from $@. | LdapInjection.java:209:30:209:63 | sBadLdapQuery | this user input | +| LdapInjection.java:214:12:214:63 | newLdapName(...) | LdapInjection.java:213:63:213:98 | sBadDNLdapUtils : String | LdapInjection.java:214:12:214:63 | newLdapName(...) | LDAP query might include code from $@. | LdapInjection.java:213:63:213:98 | sBadDNLdapUtils | this user input | +| LdapInjection.java:214:66:214:112 | new HardcodedFilter(...) | LdapInjection.java:213:30:213:60 | sBadFilter : String | LdapInjection.java:214:66:214:112 | new HardcodedFilter(...) | LDAP query might include code from $@. | LdapInjection.java:213:30:213:60 | sBadFilter | this user input | +| LdapInjection.java:218:24:218:85 | filter(...) | LdapInjection.java:217:30:217:63 | sBadLdapQuery : String | LdapInjection.java:218:24:218:85 | filter(...) | LDAP query might include code from $@. | LdapInjection.java:217:30:217:63 | sBadLdapQuery | this user input | +| LdapInjection.java:223:24:223:24 | q | LdapInjection.java:221:30:221:64 | sBadLdapQuery2 : String | LdapInjection.java:223:24:223:24 | q | LDAP query might include code from $@. | LdapInjection.java:221:30:221:64 | sBadLdapQuery2 | this user input | +| LdapInjection.java:227:24:227:116 | filter(...) | LdapInjection.java:226:30:226:73 | sBadLdapQueryWithFilter : String | LdapInjection.java:227:24:227:116 | filter(...) | LDAP query might include code from $@. | LdapInjection.java:226:30:226:73 | sBadLdapQueryWithFilter | this user input | +| LdapInjection.java:232:24:232:57 | filter(...) | LdapInjection.java:230:30:230:74 | sBadLdapQueryWithFilter2 : String | LdapInjection.java:232:24:232:57 | filter(...) | LDAP query might include code from $@. | LdapInjection.java:230:30:230:74 | sBadLdapQueryWithFilter2 | this user input | +| LdapInjection.java:236:12:236:66 | base(...) | LdapInjection.java:235:31:235:68 | sBadLdapQueryBase : String | LdapInjection.java:236:12:236:66 | base(...) | LDAP query might include code from $@. | LdapInjection.java:235:31:235:68 | sBadLdapQueryBase | this user input | +| LdapInjection.java:240:24:240:98 | is(...) | LdapInjection.java:239:31:239:71 | sBadLdapQueryComplex : String | LdapInjection.java:240:24:240:98 | is(...) | LDAP query might include code from $@. | LdapInjection.java:239:31:239:71 | sBadLdapQueryComplex | this user input | +| LdapInjection.java:244:18:244:83 | toString(...) | LdapInjection.java:243:31:243:69 | sBadFilterToString : String | LdapInjection.java:244:18:244:83 | toString(...) | LDAP query might include code from $@. | LdapInjection.java:243:31:243:69 | sBadFilterToString | this user input | +| LdapInjection.java:250:18:250:29 | toString(...) | LdapInjection.java:247:31:247:67 | sBadFilterEncode : String | LdapInjection.java:250:18:250:29 | toString(...) | LDAP query might include code from $@. | LdapInjection.java:247:31:247:67 | sBadFilterEncode | this user input | +| LdapInjection.java:268:14:268:33 | ... + ... | LdapInjection.java:266:57:266:83 | aBadDN : String | LdapInjection.java:268:14:268:33 | ... + ... | LDAP query might include code from $@. | LdapInjection.java:266:57:266:83 | aBadDN | this user input | +| LdapInjection.java:268:36:268:55 | ... + ... | LdapInjection.java:266:30:266:54 | aBad : String | LdapInjection.java:268:36:268:55 | ... + ... | LDAP query might include code from $@. | LdapInjection.java:266:30:266:54 | aBad | this user input | +| LdapInjection.java:273:14:273:62 | getName(...) | LdapInjection.java:271:57:271:94 | aBadDNObjToString : String | LdapInjection.java:273:14:273:62 | getName(...) | LDAP query might include code from $@. | LdapInjection.java:271:57:271:94 | aBadDNObjToString | this user input | +| LdapInjection.java:273:65:273:84 | ... + ... | LdapInjection.java:271:30:271:54 | aBad : String | LdapInjection.java:273:65:273:84 | ... + ... | LDAP query might include code from $@. | LdapInjection.java:271:30:271:54 | aBad | this user input | +| LdapInjection.java:280:14:280:14 | s | LdapInjection.java:276:30:276:67 | aBadSearchRequest : String | LdapInjection.java:280:14:280:14 | s | LDAP query might include code from $@. | LdapInjection.java:276:30:276:67 | aBadSearchRequest | this user input | +| LdapInjection.java:287:14:287:14 | s | LdapInjection.java:283:74:283:103 | aBadDNObj : String | LdapInjection.java:287:14:287:14 | s | LDAP query might include code from $@. | LdapInjection.java:283:74:283:103 | aBadDNObj | this user input | +| LdapInjection.java:294:14:294:24 | getBase(...) | LdapInjection.java:290:30:290:72 | aBadDNSearchRequestGet : String | LdapInjection.java:294:14:294:24 | getBase(...) | LDAP query might include code from $@. | LdapInjection.java:290:30:290:72 | aBadDNSearchRequestGet | this user input | diff --git a/java/ql/test/query-tests/security/CWE-090/LdapInjection.java b/java/ql/test/query-tests/security/CWE-090/LdapInjection.java new file mode 100644 index 00000000000..5a5f7b238b4 --- /dev/null +++ b/java/ql/test/query-tests/security/CWE-090/LdapInjection.java @@ -0,0 +1,326 @@ +import java.util.List; + +import javax.naming.Name; +import javax.naming.NamingException; +import javax.naming.directory.BasicAttributes; +import javax.naming.directory.DirContext; +import javax.naming.directory.InitialDirContext; +import javax.naming.directory.SearchControls; +import javax.naming.ldap.InitialLdapContext; +import javax.naming.ldap.LdapContext; +import javax.naming.ldap.LdapName; +import javax.naming.ldap.Rdn; + +import com.unboundid.ldap.sdk.Filter; +import com.unboundid.ldap.sdk.LDAPConnection; +import com.unboundid.ldap.sdk.LDAPException; +import com.unboundid.ldap.sdk.LDAPSearchException; +import com.unboundid.ldap.sdk.ReadOnlySearchRequest; +import com.unboundid.ldap.sdk.SearchRequest; + +import org.apache.directory.api.ldap.model.exception.LdapException; +import org.apache.directory.api.ldap.model.filter.EqualityNode; +import org.apache.directory.api.ldap.model.message.SearchRequestImpl; +import org.apache.directory.api.ldap.model.name.Dn; +import org.apache.directory.ldap.client.api.LdapConnection; +import org.apache.directory.ldap.client.api.LdapNetworkConnection; +import org.owasp.esapi.Encoder; +import org.owasp.esapi.reference.DefaultEncoder; +import org.springframework.ldap.core.LdapTemplate; +import org.springframework.ldap.filter.EqualsFilter; +import org.springframework.ldap.filter.HardcodedFilter; +import org.springframework.ldap.query.LdapQuery; +import org.springframework.ldap.query.LdapQueryBuilder; +import org.springframework.ldap.support.LdapEncoder; +import org.springframework.ldap.support.LdapNameBuilder; +import org.springframework.ldap.support.LdapUtils; +import org.springframework.web.bind.annotation.RequestParam; + +public class LdapInjection { + // JNDI + public void testJndiBad1(@RequestParam String jBad, @RequestParam String jBadDN, DirContext ctx) + throws NamingException { + ctx.search("ou=system" + jBadDN, "(uid=" + jBad + ")", new SearchControls()); + } + + public void testJndiBad2(@RequestParam String jBad, @RequestParam String jBadDNName, InitialDirContext ctx) + throws NamingException { + ctx.search(new LdapName("ou=system" + jBadDNName), "(uid=" + jBad + ")", new SearchControls()); + } + + public void testJndiBad3(@RequestParam String jBad, @RequestParam String jOkDN, LdapContext ctx) + throws NamingException { + ctx.search(new LdapName(List.of(new Rdn("ou=" + jOkDN))), "(uid=" + jBad + ")", new SearchControls()); + } + + public void testJndiBad4(@RequestParam String jBadInitial, InitialLdapContext ctx) + throws NamingException { + ctx.search("ou=system", "(uid=" + jBadInitial + ")", new SearchControls()); + } + + public void testJndiBad5(@RequestParam String jBad, @RequestParam String jBadDNNameAdd, InitialDirContext ctx) + throws NamingException { + ctx.search(new LdapName("").addAll(new LdapName("ou=system" + jBadDNNameAdd)), "(uid=" + jBad + ")", new SearchControls()); + } + + public void testJndiBad6(@RequestParam String jBad, @RequestParam String jBadDNNameAdd2, InitialDirContext ctx) + throws NamingException { + LdapName name = new LdapName(""); + name.addAll(new LdapName("ou=system" + jBadDNNameAdd2).getRdns()); + ctx.search(new LdapName("").addAll(name), "(uid=" + jBad + ")", new SearchControls()); + } + + public void testJndiBad7(@RequestParam String jBad, @RequestParam String jBadDNNameToString, InitialDirContext ctx) + throws NamingException { + ctx.search(new LdapName("ou=system" + jBadDNNameToString).toString(), "(uid=" + jBad + ")", new SearchControls()); + } + + public void testJndiBad8(@RequestParam String jBad, @RequestParam String jBadDNNameClone, InitialDirContext ctx) + throws NamingException { + ctx.search((Name) new LdapName("ou=system" + jBadDNNameClone).clone(), "(uid=" + jBad + ")", new SearchControls()); + } + + public void testJndiOk1(@RequestParam String jOkFilterExpr, DirContext ctx) throws NamingException { + ctx.search("ou=system", "(uid={0})", new String[] { jOkFilterExpr }, new SearchControls()); + } + + public void testJndiOk2(@RequestParam String jOkAttribute, DirContext ctx) throws NamingException { + ctx.search("ou=system", new BasicAttributes(jOkAttribute, jOkAttribute)); + } + + // UnboundID + public void testUnboundBad1(@RequestParam String uBad, @RequestParam String uBadDN, LDAPConnection c) + throws LDAPSearchException { + c.search(null, "ou=system" + uBadDN, null, null, 1, 1, false, "(uid=" + uBad + ")"); + } + + public void testUnboundBad2(@RequestParam String uBadFilterCreate, LDAPConnection c) throws LDAPException { + c.search(null, "ou=system", null, null, 1, 1, false, Filter.create(uBadFilterCreate)); + } + + public void testUnboundBad3(@RequestParam String uBadROSearchRequest, @RequestParam String uBadROSRDN, + LDAPConnection c) throws LDAPException { + ReadOnlySearchRequest s = new SearchRequest(null, "ou=system" + uBadROSRDN, null, null, 1, 1, false, + "(uid=" + uBadROSearchRequest + ")"); + c.search(s); + } + + public void testUnboundBad4(@RequestParam String uBadSearchRequest, @RequestParam String uBadSRDN, LDAPConnection c) + throws LDAPException { + SearchRequest s = new SearchRequest(null, "ou=system" + uBadSRDN, null, null, 1, 1, false, + "(uid=" + uBadSearchRequest + ")"); + c.search(s); + } + + public void testUnboundBad5(@RequestParam String uBad, @RequestParam String uBadDNSFR, LDAPConnection c) + throws LDAPSearchException { + c.searchForEntry("ou=system" + uBadDNSFR, null, null, 1, false, "(uid=" + uBad + ")"); + } + + public void testUnboundBad6(@RequestParam String uBadROSearchRequestAsync, @RequestParam String uBadROSRDNAsync, + LDAPConnection c) throws LDAPException { + ReadOnlySearchRequest s = new SearchRequest(null, "ou=system" + uBadROSRDNAsync, null, null, 1, 1, false, + "(uid=" + uBadROSearchRequestAsync + ")"); + c.asyncSearch(s); + } + + public void testUnboundBad7(@RequestParam String uBadSearchRequestAsync, @RequestParam String uBadSRDNAsync, LDAPConnection c) + throws LDAPException { + SearchRequest s = new SearchRequest(null, "ou=system" + uBadSRDNAsync, null, null, 1, 1, false, + "(uid=" + uBadSearchRequestAsync + ")"); + c.asyncSearch(s); + } + + public void testUnboundBad8(@RequestParam String uBadFilterCreateNOT, LDAPConnection c) throws LDAPException { + c.search(null, "ou=system", null, null, 1, 1, false, Filter.createNOTFilter(Filter.create(uBadFilterCreateNOT))); + } + + public void testUnboundBad9(@RequestParam String uBadFilterCreateToString, LDAPConnection c) throws LDAPException { + c.search(null, "ou=system", null, null, 1, 1, false, Filter.create(uBadFilterCreateToString).toString()); + } + + public void testUnboundBad10(@RequestParam String uBadFilterCreateToStringBuffer, LDAPConnection c) throws LDAPException { + StringBuilder b = new StringBuilder(); + Filter.create(uBadFilterCreateToStringBuffer).toNormalizedString(b); + c.search(null, "ou=system", null, null, 1, 1, false, b.toString()); + } + + public void testUnboundBad11(@RequestParam String uBadSearchRequestDuplicate, LDAPConnection c) + throws LDAPException { + SearchRequest s = new SearchRequest(null, "ou=system", null, null, 1, 1, false, + "(uid=" + uBadSearchRequestDuplicate + ")"); + c.search(s.duplicate()); + } + + public void testUnboundBad12(@RequestParam String uBadROSearchRequestDuplicate, LDAPConnection c) + throws LDAPException { + ReadOnlySearchRequest s = new SearchRequest(null, "ou=system", null, null, 1, 1, false, + "(uid=" + uBadROSearchRequestDuplicate + ")"); + c.search(s.duplicate()); + } + + public void testUnboundBad13(@RequestParam String uBadSearchRequestSetDN, LDAPConnection c) + throws LDAPException { + SearchRequest s = new SearchRequest(null, "", null, null, 1, 1, false, ""); + s.setBaseDN(uBadSearchRequestSetDN); + c.search(s); + } + + public void testUnboundBad14(@RequestParam String uBadSearchRequestSetFilter, LDAPConnection c) + throws LDAPException { + SearchRequest s = new SearchRequest(null, "ou=system", null, null, 1, 1, false, ""); + s.setFilter(uBadSearchRequestSetFilter); + c.search(s); + } + + public void testUnboundOk1(@RequestParam String uOkEqualityFilter, LDAPConnection c) throws LDAPSearchException { + c.search(null, "ou=system", null, null, 1, 1, false, Filter.createEqualityFilter("uid", uOkEqualityFilter)); + } + + public void testUnboundOk2(@RequestParam String uOkVaragsAttr, LDAPConnection c) throws LDAPSearchException { + c.search("ou=system", null, null, 1, 1, false, "(uid=fixed)", "a" + uOkVaragsAttr); + } + + public void testUnboundOk3(@RequestParam String uOkFilterSearchRequest, LDAPConnection c) throws LDAPException { + SearchRequest s = new SearchRequest(null, "ou=system", null, null, 1, 1, false, + Filter.createEqualityFilter("uid", uOkFilterSearchRequest)); + c.search(s); + } + + public void testUnboundOk4(@RequestParam String uOkSearchRequestVarargs, LDAPConnection c) throws LDAPException { + SearchRequest s = new SearchRequest("ou=system", null, "(uid=fixed)", "va1", "va2", "va3", + "a" + uOkSearchRequestVarargs); + c.search(s); + } + + // Spring LDAP + public void testSpringBad1(@RequestParam String sBad, @RequestParam String sBadDN, LdapTemplate c) { + c.search("ou=system" + sBadDN, "(uid=" + sBad + ")", 1, false, null); + } + + public void testSpringBad2(@RequestParam String sBad, @RequestParam String sBadDNLNBuilder, LdapTemplate c) { + c.authenticate(LdapNameBuilder.newInstance("ou=system" + sBadDNLNBuilder).build(), "(uid=" + sBad + ")", "pass"); + } + + public void testSpringBad3(@RequestParam String sBad, @RequestParam String sBadDNLNBuilderAdd, LdapTemplate c) { + c.searchForObject(LdapNameBuilder.newInstance().add("ou=system" + sBadDNLNBuilderAdd).build(), "(uid=" + sBad + ")", null); + } + + public void testSpringBad4(@RequestParam String sBadLdapQuery, LdapTemplate c) { + c.findOne(LdapQueryBuilder.query().filter("(uid=" + sBadLdapQuery + ")"), null); + } + + public void testSpringBad5(@RequestParam String sBadFilter, @RequestParam String sBadDNLdapUtils, LdapTemplate c) { + c.find(LdapUtils.newLdapName("ou=system" + sBadDNLdapUtils), new HardcodedFilter("(uid=" + sBadFilter + ")"), null, null); + } + + public void testSpringBad6(@RequestParam String sBadLdapQuery, LdapTemplate c) { + c.searchForContext(LdapQueryBuilder.query().filter("(uid=" + sBadLdapQuery + ")")); + } + + public void testSpringBad7(@RequestParam String sBadLdapQuery2, LdapTemplate c) { + LdapQuery q = LdapQueryBuilder.query().filter("(uid=" + sBadLdapQuery2 + ")"); + c.searchForContext(q); + } + + public void testSpringBad8(@RequestParam String sBadLdapQueryWithFilter, LdapTemplate c) { + c.searchForContext(LdapQueryBuilder.query().filter(new HardcodedFilter("(uid=" + sBadLdapQueryWithFilter + ")"))); + } + + public void testSpringBad9(@RequestParam String sBadLdapQueryWithFilter2, LdapTemplate c) { + org.springframework.ldap.filter.Filter f = new HardcodedFilter("(uid=" + sBadLdapQueryWithFilter2 + ")"); + c.searchForContext(LdapQueryBuilder.query().filter(f)); + } + + public void testSpringBad10(@RequestParam String sBadLdapQueryBase, LdapTemplate c) { + c.find(LdapQueryBuilder.query().base(sBadLdapQueryBase).base(), null, null, null); + } + + public void testSpringBad11(@RequestParam String sBadLdapQueryComplex, LdapTemplate c) { + c.searchForContext(LdapQueryBuilder.query().base(sBadLdapQueryComplex).where("uid").is("test")); + } + + public void testSpringBad12(@RequestParam String sBadFilterToString, LdapTemplate c) { + c.search("", new HardcodedFilter("(uid=" + sBadFilterToString + ")").toString(), 1, false, null); + } + + public void testSpringBad13(@RequestParam String sBadFilterEncode, LdapTemplate c) { + StringBuffer s = new StringBuffer(); + new HardcodedFilter("(uid=" + sBadFilterEncode + ")").encode(s); + c.search("", s.toString(), 1, false, null); + } + + public void testSpringOk1(@RequestParam String sOkLdapQuery, LdapTemplate c) { + c.find(LdapQueryBuilder.query().filter("(uid={0})", sOkLdapQuery), null); + } + + public void testSpringOk2(@RequestParam String sOkFilter, @RequestParam String sOkDN, LdapTemplate c) { + c.find(LdapNameBuilder.newInstance().add("ou", sOkDN).build(), new EqualsFilter("uid", sOkFilter), null, null); + } + + public void testSpringOk3(@RequestParam String sOkLdapQuery, @RequestParam String sOkPassword, LdapTemplate c) { + c.authenticate(LdapQueryBuilder.query().filter("(uid={0})", sOkLdapQuery), sOkPassword); + } + + // Apache LDAP API + public void testApacheBad1(@RequestParam String aBad, @RequestParam String aBadDN, LdapConnection c) + throws LdapException { + c.search("ou=system" + aBadDN, "(uid=" + aBad + ")", null); + } + + public void testApacheBad2(@RequestParam String aBad, @RequestParam String aBadDNObjToString, LdapNetworkConnection c) + throws LdapException { + c.search(new Dn("ou=system" + aBadDNObjToString).getName(), "(uid=" + aBad + ")", null); + } + + public void testApacheBad3(@RequestParam String aBadSearchRequest, LdapConnection c) + throws LdapException { + org.apache.directory.api.ldap.model.message.SearchRequest s = new SearchRequestImpl(); + s.setFilter("(uid=" + aBadSearchRequest + ")"); + c.search(s); + } + + public void testApacheBad4(@RequestParam String aBadSearchRequestImpl, @RequestParam String aBadDNObj, LdapConnection c) + throws LdapException { + SearchRequestImpl s = new SearchRequestImpl(); + s.setBase(new Dn("ou=system" + aBadDNObj)); + c.search(s); + } + + public void testApacheBad5(@RequestParam String aBadDNSearchRequestGet, LdapConnection c) + throws LdapException { + org.apache.directory.api.ldap.model.message.SearchRequest s = new SearchRequestImpl(); + s.setBase(new Dn("ou=system" + aBadDNSearchRequestGet)); + c.search(s.getBase(), "(uid=test", null); + } + + public void testApacheOk1(@RequestParam String aOk, LdapConnection c) + throws LdapException { + org.apache.directory.api.ldap.model.message.SearchRequest s = new SearchRequestImpl(); + s.setFilter(new EqualityNode("uid", aOk)); + c.search(s); + } + + public void testApacheOk2(@RequestParam String aOk, LdapConnection c) + throws LdapException { + SearchRequestImpl s = new SearchRequestImpl(); + s.setFilter(new EqualityNode("uid", aOk)); + c.search(s); + } + + // ESAPI encoder sanitizer + public void testOk3(@RequestParam String okEncodeForLDAP, DirContext ctx) throws NamingException { + Encoder encoder = DefaultEncoder.getInstance(); + ctx.search("ou=system", "(uid=" + encoder.encodeForLDAP(okEncodeForLDAP) + ")", new SearchControls()); + } + + // Spring LdapEncoder sanitizer + public void testOk4(@RequestParam String okFilterEncode, DirContext ctx) throws NamingException { + ctx.search("ou=system", "(uid=" + LdapEncoder.filterEncode(okFilterEncode) + ")", new SearchControls()); + } + + // UnboundID Filter.encodeValue sanitizer + public void testOk5(@RequestParam String okUnboundEncodeValue, DirContext ctx) throws NamingException { + ctx.search("ou=system", "(uid=" + Filter.encodeValue(okUnboundEncodeValue) + ")", new SearchControls()); + } +} diff --git a/java/ql/test/query-tests/security/CWE-090/LdapInjection.qlref b/java/ql/test/query-tests/security/CWE-090/LdapInjection.qlref new file mode 100644 index 00000000000..1af3794de3a --- /dev/null +++ b/java/ql/test/query-tests/security/CWE-090/LdapInjection.qlref @@ -0,0 +1 @@ +Security/CWE/CWE-090/LdapInjection.ql diff --git a/java/ql/test/query-tests/security/CWE-090/options b/java/ql/test/query-tests/security/CWE-090/options new file mode 100644 index 00000000000..08c38d92ad8 --- /dev/null +++ b/java/ql/test/query-tests/security/CWE-090/options @@ -0,0 +1 @@ +//semmle-extractor-options: --javac-args -cp ${testdir}/../../../stubs/springframework-5.2.3:${testdir}/../../../stubs/spring-ldap-2.3.2:${testdir}/../../../stubs/unboundid-ldap-4.0.14:${testdir}/../../../stubs/esapi-2.0.1:${testdir}/../../../stubs/apache-ldap-1.0.2 diff --git a/java/ql/test/stubs/apache-ldap-1.0.2/org/apache/directory/api/ldap/model/cursor/EntryCursor.java b/java/ql/test/stubs/apache-ldap-1.0.2/org/apache/directory/api/ldap/model/cursor/EntryCursor.java new file mode 100644 index 00000000000..fc2eecd4e50 --- /dev/null +++ b/java/ql/test/stubs/apache-ldap-1.0.2/org/apache/directory/api/ldap/model/cursor/EntryCursor.java @@ -0,0 +1,4 @@ +package org.apache.directory.api.ldap.model.cursor; + +public interface EntryCursor { +} diff --git a/java/ql/test/stubs/apache-ldap-1.0.2/org/apache/directory/api/ldap/model/cursor/SearchCursor.java b/java/ql/test/stubs/apache-ldap-1.0.2/org/apache/directory/api/ldap/model/cursor/SearchCursor.java new file mode 100644 index 00000000000..e27defdfde5 --- /dev/null +++ b/java/ql/test/stubs/apache-ldap-1.0.2/org/apache/directory/api/ldap/model/cursor/SearchCursor.java @@ -0,0 +1,4 @@ +package org.apache.directory.api.ldap.model.cursor; + +public interface SearchCursor { +} diff --git a/java/ql/test/stubs/apache-ldap-1.0.2/org/apache/directory/api/ldap/model/entry/Value.java b/java/ql/test/stubs/apache-ldap-1.0.2/org/apache/directory/api/ldap/model/entry/Value.java new file mode 100644 index 00000000000..b10f86cc626 --- /dev/null +++ b/java/ql/test/stubs/apache-ldap-1.0.2/org/apache/directory/api/ldap/model/entry/Value.java @@ -0,0 +1,4 @@ +package org.apache.directory.api.ldap.model.entry; + +public interface Value { +} diff --git a/java/ql/test/stubs/apache-ldap-1.0.2/org/apache/directory/api/ldap/model/exception/LdapException.java b/java/ql/test/stubs/apache-ldap-1.0.2/org/apache/directory/api/ldap/model/exception/LdapException.java new file mode 100644 index 00000000000..955a2446832 --- /dev/null +++ b/java/ql/test/stubs/apache-ldap-1.0.2/org/apache/directory/api/ldap/model/exception/LdapException.java @@ -0,0 +1,4 @@ +package org.apache.directory.api.ldap.model.exception; + +public class LdapException extends Exception { +} diff --git a/java/ql/test/stubs/apache-ldap-1.0.2/org/apache/directory/api/ldap/model/exception/LdapInvalidDnException.java b/java/ql/test/stubs/apache-ldap-1.0.2/org/apache/directory/api/ldap/model/exception/LdapInvalidDnException.java new file mode 100644 index 00000000000..8d44950a9b9 --- /dev/null +++ b/java/ql/test/stubs/apache-ldap-1.0.2/org/apache/directory/api/ldap/model/exception/LdapInvalidDnException.java @@ -0,0 +1,4 @@ +package org.apache.directory.api.ldap.model.exception; + +public class LdapInvalidDnException extends LdapException { +} diff --git a/java/ql/test/stubs/apache-ldap-1.0.2/org/apache/directory/api/ldap/model/filter/EqualityNode.java b/java/ql/test/stubs/apache-ldap-1.0.2/org/apache/directory/api/ldap/model/filter/EqualityNode.java new file mode 100644 index 00000000000..58c9d1981e9 --- /dev/null +++ b/java/ql/test/stubs/apache-ldap-1.0.2/org/apache/directory/api/ldap/model/filter/EqualityNode.java @@ -0,0 +1,8 @@ +package org.apache.directory.api.ldap.model.filter; + +import org.apache.directory.api.ldap.model.entry.Value; + +public class EqualityNode implements ExprNode { + public EqualityNode(String attribute, Value value) { } + public EqualityNode(String attribute, String value) { } +} diff --git a/java/ql/test/stubs/apache-ldap-1.0.2/org/apache/directory/api/ldap/model/filter/ExprNode.java b/java/ql/test/stubs/apache-ldap-1.0.2/org/apache/directory/api/ldap/model/filter/ExprNode.java new file mode 100644 index 00000000000..8bb2e041159 --- /dev/null +++ b/java/ql/test/stubs/apache-ldap-1.0.2/org/apache/directory/api/ldap/model/filter/ExprNode.java @@ -0,0 +1,4 @@ +package org.apache.directory.api.ldap.model.filter; + +public interface ExprNode { +} diff --git a/java/ql/test/stubs/apache-ldap-1.0.2/org/apache/directory/api/ldap/model/message/SearchRequest.java b/java/ql/test/stubs/apache-ldap-1.0.2/org/apache/directory/api/ldap/model/message/SearchRequest.java new file mode 100644 index 00000000000..509785f8afc --- /dev/null +++ b/java/ql/test/stubs/apache-ldap-1.0.2/org/apache/directory/api/ldap/model/message/SearchRequest.java @@ -0,0 +1,12 @@ +package org.apache.directory.api.ldap.model.message; + +import org.apache.directory.api.ldap.model.exception.LdapException; +import org.apache.directory.api.ldap.model.name.Dn; +import org.apache.directory.api.ldap.model.filter.ExprNode; + +public interface SearchRequest { + Dn getBase(); + SearchRequest setBase(Dn baseDn); + SearchRequest setFilter(ExprNode filter); + SearchRequest setFilter(String filter) throws LdapException; +} diff --git a/java/ql/test/stubs/apache-ldap-1.0.2/org/apache/directory/api/ldap/model/message/SearchRequestImpl.java b/java/ql/test/stubs/apache-ldap-1.0.2/org/apache/directory/api/ldap/model/message/SearchRequestImpl.java new file mode 100644 index 00000000000..ed0ffb4a607 --- /dev/null +++ b/java/ql/test/stubs/apache-ldap-1.0.2/org/apache/directory/api/ldap/model/message/SearchRequestImpl.java @@ -0,0 +1,12 @@ +package org.apache.directory.api.ldap.model.message; + +import org.apache.directory.api.ldap.model.exception.LdapException; +import org.apache.directory.api.ldap.model.name.Dn; +import org.apache.directory.api.ldap.model.filter.ExprNode; + +public class SearchRequestImpl implements SearchRequest { + public Dn getBase() { return null; } + public SearchRequest setBase(Dn baseDn) { return null; } + public SearchRequest setFilter(ExprNode filter) { return null; } + public SearchRequest setFilter(String filter) throws LdapException { return null; } +} diff --git a/java/ql/test/stubs/apache-ldap-1.0.2/org/apache/directory/api/ldap/model/message/SearchScope.java b/java/ql/test/stubs/apache-ldap-1.0.2/org/apache/directory/api/ldap/model/message/SearchScope.java new file mode 100644 index 00000000000..05e3138d7d3 --- /dev/null +++ b/java/ql/test/stubs/apache-ldap-1.0.2/org/apache/directory/api/ldap/model/message/SearchScope.java @@ -0,0 +1,4 @@ +package org.apache.directory.api.ldap.model.message; + +public enum SearchScope { +} diff --git a/java/ql/test/stubs/apache-ldap-1.0.2/org/apache/directory/api/ldap/model/name/Dn.java b/java/ql/test/stubs/apache-ldap-1.0.2/org/apache/directory/api/ldap/model/name/Dn.java new file mode 100644 index 00000000000..7f168474ef7 --- /dev/null +++ b/java/ql/test/stubs/apache-ldap-1.0.2/org/apache/directory/api/ldap/model/name/Dn.java @@ -0,0 +1,8 @@ +package org.apache.directory.api.ldap.model.name; + +import org.apache.directory.api.ldap.model.exception.LdapInvalidDnException; + +public class Dn { + public Dn(String... upRdns) throws LdapInvalidDnException { } + public String getName() { return null; } +} diff --git a/java/ql/test/stubs/apache-ldap-1.0.2/org/apache/directory/ldap/client/api/LdapConnection.java b/java/ql/test/stubs/apache-ldap-1.0.2/org/apache/directory/ldap/client/api/LdapConnection.java new file mode 100644 index 00000000000..7891c621100 --- /dev/null +++ b/java/ql/test/stubs/apache-ldap-1.0.2/org/apache/directory/ldap/client/api/LdapConnection.java @@ -0,0 +1,17 @@ +package org.apache.directory.ldap.client.api; + +import org.apache.directory.api.ldap.model.exception.LdapException; +import org.apache.directory.api.ldap.model.cursor.EntryCursor; +import org.apache.directory.api.ldap.model.cursor.SearchCursor; +import org.apache.directory.api.ldap.model.message.SearchRequest; +import org.apache.directory.api.ldap.model.message.SearchScope; +import org.apache.directory.api.ldap.model.name.Dn; + +public interface LdapConnection { + SearchCursor search(SearchRequest searchRequest) throws LdapException; + + EntryCursor search(String baseDn, String filter, SearchScope scope, String... attributes) throws LdapException; + + EntryCursor search(Dn baseDn, String filter, SearchScope scope, String... attributes) throws LdapException; + +} diff --git a/java/ql/test/stubs/apache-ldap-1.0.2/org/apache/directory/ldap/client/api/LdapNetworkConnection.java b/java/ql/test/stubs/apache-ldap-1.0.2/org/apache/directory/ldap/client/api/LdapNetworkConnection.java new file mode 100644 index 00000000000..4de173ac1a7 --- /dev/null +++ b/java/ql/test/stubs/apache-ldap-1.0.2/org/apache/directory/ldap/client/api/LdapNetworkConnection.java @@ -0,0 +1,16 @@ +package org.apache.directory.ldap.client.api; + +import org.apache.directory.api.ldap.model.exception.LdapException; +import org.apache.directory.api.ldap.model.cursor.EntryCursor; +import org.apache.directory.api.ldap.model.cursor.SearchCursor; +import org.apache.directory.api.ldap.model.message.SearchRequest; +import org.apache.directory.api.ldap.model.message.SearchScope; +import org.apache.directory.api.ldap.model.name.Dn; + +public class LdapNetworkConnection implements LdapConnection { + public SearchCursor search(SearchRequest searchRequest) throws LdapException { return null; } + + public EntryCursor search(String baseDn, String filter, SearchScope scope, String... attributes) throws LdapException { return null; } + + public EntryCursor search(Dn baseDn, String filter, SearchScope scope, String... attributes) throws LdapException { return null; } +} diff --git a/java/ql/test/stubs/esapi-2.0.1/org/owasp/esapi/Encoder.java b/java/ql/test/stubs/esapi-2.0.1/org/owasp/esapi/Encoder.java new file mode 100644 index 00000000000..e0200207e76 --- /dev/null +++ b/java/ql/test/stubs/esapi-2.0.1/org/owasp/esapi/Encoder.java @@ -0,0 +1,5 @@ +package org.owasp.esapi; + +public interface Encoder { + String encodeForLDAP(String input); +} diff --git a/java/ql/test/stubs/esapi-2.0.1/org/owasp/esapi/reference/DefaultEncoder.java b/java/ql/test/stubs/esapi-2.0.1/org/owasp/esapi/reference/DefaultEncoder.java new file mode 100644 index 00000000000..8a1169f073a --- /dev/null +++ b/java/ql/test/stubs/esapi-2.0.1/org/owasp/esapi/reference/DefaultEncoder.java @@ -0,0 +1,8 @@ +package org.owasp.esapi.reference; + +import org.owasp.esapi.Encoder; + +public class DefaultEncoder implements Encoder { + public static Encoder getInstance() { return null; } + public String encodeForLDAP(String input) { return null; } +} diff --git a/java/ql/test/stubs/spring-ldap-2.3.2/org/springframework/ldap/core/ContextMapper.java b/java/ql/test/stubs/spring-ldap-2.3.2/org/springframework/ldap/core/ContextMapper.java new file mode 100644 index 00000000000..951015b637e --- /dev/null +++ b/java/ql/test/stubs/spring-ldap-2.3.2/org/springframework/ldap/core/ContextMapper.java @@ -0,0 +1,4 @@ +package org.springframework.ldap.core; + +public interface ContextMapper { +} diff --git a/java/ql/test/stubs/spring-ldap-2.3.2/org/springframework/ldap/core/DirContextOperations.java b/java/ql/test/stubs/spring-ldap-2.3.2/org/springframework/ldap/core/DirContextOperations.java new file mode 100644 index 00000000000..682de892a42 --- /dev/null +++ b/java/ql/test/stubs/spring-ldap-2.3.2/org/springframework/ldap/core/DirContextOperations.java @@ -0,0 +1,4 @@ +package org.springframework.ldap.core; + +public interface DirContextOperations { +} diff --git a/java/ql/test/stubs/spring-ldap-2.3.2/org/springframework/ldap/core/LdapTemplate.java b/java/ql/test/stubs/spring-ldap-2.3.2/org/springframework/ldap/core/LdapTemplate.java new file mode 100644 index 00000000000..2c26a3b3b50 --- /dev/null +++ b/java/ql/test/stubs/spring-ldap-2.3.2/org/springframework/ldap/core/LdapTemplate.java @@ -0,0 +1,28 @@ +package org.springframework.ldap.core; + +import java.util.*; + +import javax.naming.Name; +import javax.naming.directory.SearchControls; + +import org.springframework.ldap.filter.Filter; + +import org.springframework.ldap.query.LdapQuery; + +public class LdapTemplate { + public void authenticate(LdapQuery query, String password) { } + + public boolean authenticate(Name base, String filter, String password) { return true; } + + public List find(Name base, Filter filter, SearchControls searchControls, final Class clazz) { return null; } + + public List find(LdapQuery query, Class clazz) { return null; } + + public T findOne(LdapQuery query, Class clazz) { return null; } + + public void search(String base, String filter, int searchScope, boolean returningObjFlag, NameClassPairCallbackHandler handler) { } + + public DirContextOperations searchForContext(LdapQuery query) { return null; } + + public T searchForObject(Name base, String filter, ContextMapper mapper) { return null; } +} diff --git a/java/ql/test/stubs/spring-ldap-2.3.2/org/springframework/ldap/core/NameClassPairCallbackHandler.java b/java/ql/test/stubs/spring-ldap-2.3.2/org/springframework/ldap/core/NameClassPairCallbackHandler.java new file mode 100644 index 00000000000..250e6da0237 --- /dev/null +++ b/java/ql/test/stubs/spring-ldap-2.3.2/org/springframework/ldap/core/NameClassPairCallbackHandler.java @@ -0,0 +1,3 @@ +package org.springframework.ldap.core; + +public interface NameClassPairCallbackHandler { } diff --git a/java/ql/test/stubs/spring-ldap-2.3.2/org/springframework/ldap/filter/EqualsFilter.java b/java/ql/test/stubs/spring-ldap-2.3.2/org/springframework/ldap/filter/EqualsFilter.java new file mode 100644 index 00000000000..a5cbbd2a674 --- /dev/null +++ b/java/ql/test/stubs/spring-ldap-2.3.2/org/springframework/ldap/filter/EqualsFilter.java @@ -0,0 +1,5 @@ +package org.springframework.ldap.filter; + +public class EqualsFilter implements Filter { + public EqualsFilter(String attribute, String value) { } +} diff --git a/java/ql/test/stubs/spring-ldap-2.3.2/org/springframework/ldap/filter/Filter.java b/java/ql/test/stubs/spring-ldap-2.3.2/org/springframework/ldap/filter/Filter.java new file mode 100644 index 00000000000..b24091e6de0 --- /dev/null +++ b/java/ql/test/stubs/spring-ldap-2.3.2/org/springframework/ldap/filter/Filter.java @@ -0,0 +1,4 @@ +package org.springframework.ldap.filter; + +public interface Filter { +} diff --git a/java/ql/test/stubs/spring-ldap-2.3.2/org/springframework/ldap/filter/HardcodedFilter.java b/java/ql/test/stubs/spring-ldap-2.3.2/org/springframework/ldap/filter/HardcodedFilter.java new file mode 100644 index 00000000000..bc43dddc6f8 --- /dev/null +++ b/java/ql/test/stubs/spring-ldap-2.3.2/org/springframework/ldap/filter/HardcodedFilter.java @@ -0,0 +1,7 @@ +package org.springframework.ldap.filter; + +public class HardcodedFilter implements Filter { + public HardcodedFilter(String filter) { } + public StringBuffer encode(StringBuffer buff) { return buff; } + public String toString() { return ""; } +} diff --git a/java/ql/test/stubs/spring-ldap-2.3.2/org/springframework/ldap/query/ConditionCriteria.java b/java/ql/test/stubs/spring-ldap-2.3.2/org/springframework/ldap/query/ConditionCriteria.java new file mode 100644 index 00000000000..80cf59b6040 --- /dev/null +++ b/java/ql/test/stubs/spring-ldap-2.3.2/org/springframework/ldap/query/ConditionCriteria.java @@ -0,0 +1,5 @@ +package org.springframework.ldap.query; + +public interface ConditionCriteria { + ContainerCriteria is(String value); +} diff --git a/java/ql/test/stubs/spring-ldap-2.3.2/org/springframework/ldap/query/ContainerCriteria.java b/java/ql/test/stubs/spring-ldap-2.3.2/org/springframework/ldap/query/ContainerCriteria.java new file mode 100644 index 00000000000..7a68b9fbab7 --- /dev/null +++ b/java/ql/test/stubs/spring-ldap-2.3.2/org/springframework/ldap/query/ContainerCriteria.java @@ -0,0 +1,4 @@ +package org.springframework.ldap.query; + +public interface ContainerCriteria extends LdapQuery { +} diff --git a/java/ql/test/stubs/spring-ldap-2.3.2/org/springframework/ldap/query/LdapQuery.java b/java/ql/test/stubs/spring-ldap-2.3.2/org/springframework/ldap/query/LdapQuery.java new file mode 100644 index 00000000000..c94bb75c20c --- /dev/null +++ b/java/ql/test/stubs/spring-ldap-2.3.2/org/springframework/ldap/query/LdapQuery.java @@ -0,0 +1,4 @@ +package org.springframework.ldap.query; + +public interface LdapQuery { +} diff --git a/java/ql/test/stubs/spring-ldap-2.3.2/org/springframework/ldap/query/LdapQueryBuilder.java b/java/ql/test/stubs/spring-ldap-2.3.2/org/springframework/ldap/query/LdapQueryBuilder.java new file mode 100644 index 00000000000..2e6c76ccc55 --- /dev/null +++ b/java/ql/test/stubs/spring-ldap-2.3.2/org/springframework/ldap/query/LdapQueryBuilder.java @@ -0,0 +1,14 @@ +package org.springframework.ldap.query; + +import javax.naming.Name; +import org.springframework.ldap.filter.Filter; + +public class LdapQueryBuilder { + public static LdapQueryBuilder query() { return null; } + public LdapQuery filter(String hardcodedFilter) { return null; } + public LdapQuery filter(Filter filter) { return null; } + public LdapQuery filter(String filterFormat, Object... params) { return null; } + public LdapQueryBuilder base(String baseDn) { return this; } + public Name base() { return null; } + public ConditionCriteria where(String attribute) { return null; } +} diff --git a/java/ql/test/stubs/spring-ldap-2.3.2/org/springframework/ldap/support/LdapEncoder.java b/java/ql/test/stubs/spring-ldap-2.3.2/org/springframework/ldap/support/LdapEncoder.java new file mode 100644 index 00000000000..a85d74192b3 --- /dev/null +++ b/java/ql/test/stubs/spring-ldap-2.3.2/org/springframework/ldap/support/LdapEncoder.java @@ -0,0 +1,5 @@ +package org.springframework.ldap.support; + +public class LdapEncoder { + public static String filterEncode(String value) { return null; } +} diff --git a/java/ql/test/stubs/spring-ldap-2.3.2/org/springframework/ldap/support/LdapNameBuilder.java b/java/ql/test/stubs/spring-ldap-2.3.2/org/springframework/ldap/support/LdapNameBuilder.java new file mode 100644 index 00000000000..74333407853 --- /dev/null +++ b/java/ql/test/stubs/spring-ldap-2.3.2/org/springframework/ldap/support/LdapNameBuilder.java @@ -0,0 +1,12 @@ +package org.springframework.ldap.support; + +import javax.naming.ldap.LdapName; + +public class LdapNameBuilder { + public static LdapNameBuilder newInstance() { return null; } + public static LdapNameBuilder newInstance(String name) { return null; } + + public LdapNameBuilder add(String name) { return null; } + public LdapNameBuilder add(String key, Object value) { return null; } + public LdapName build() { return null; } +} diff --git a/java/ql/test/stubs/spring-ldap-2.3.2/org/springframework/ldap/support/LdapUtils.java b/java/ql/test/stubs/spring-ldap-2.3.2/org/springframework/ldap/support/LdapUtils.java new file mode 100644 index 00000000000..13fee96e004 --- /dev/null +++ b/java/ql/test/stubs/spring-ldap-2.3.2/org/springframework/ldap/support/LdapUtils.java @@ -0,0 +1,7 @@ +package org.springframework.ldap.support; + +import javax.naming.ldap.LdapName; + +public class LdapUtils { + public static LdapName newLdapName(String distinguishedName) { return null; } +} diff --git a/java/ql/test/stubs/springframework-5.2.3/org/springframework/web/bind/annotation/RequestParam.java b/java/ql/test/stubs/springframework-5.2.3/org/springframework/web/bind/annotation/RequestParam.java new file mode 100644 index 00000000000..5ae52ad123f --- /dev/null +++ b/java/ql/test/stubs/springframework-5.2.3/org/springframework/web/bind/annotation/RequestParam.java @@ -0,0 +1,8 @@ +package org.springframework.web.bind.annotation; + +import java.lang.annotation.*; + +@Target(value=ElementType.PARAMETER) +@Retention(value=RetentionPolicy.RUNTIME) +@Documented +public @interface RequestParam { } diff --git a/java/ql/test/stubs/unboundid-ldap-4.0.14/com/unboundid/ldap/sdk/AsyncRequestID.java b/java/ql/test/stubs/unboundid-ldap-4.0.14/com/unboundid/ldap/sdk/AsyncRequestID.java new file mode 100644 index 00000000000..09e9240e97e --- /dev/null +++ b/java/ql/test/stubs/unboundid-ldap-4.0.14/com/unboundid/ldap/sdk/AsyncRequestID.java @@ -0,0 +1,3 @@ +package com.unboundid.ldap.sdk; + +public class AsyncRequestID { } diff --git a/java/ql/test/stubs/unboundid-ldap-4.0.14/com/unboundid/ldap/sdk/DereferencePolicy.java b/java/ql/test/stubs/unboundid-ldap-4.0.14/com/unboundid/ldap/sdk/DereferencePolicy.java new file mode 100644 index 00000000000..76f554cf6da --- /dev/null +++ b/java/ql/test/stubs/unboundid-ldap-4.0.14/com/unboundid/ldap/sdk/DereferencePolicy.java @@ -0,0 +1,3 @@ +package com.unboundid.ldap.sdk; + +public class DereferencePolicy { } diff --git a/java/ql/test/stubs/unboundid-ldap-4.0.14/com/unboundid/ldap/sdk/Filter.java b/java/ql/test/stubs/unboundid-ldap-4.0.14/com/unboundid/ldap/sdk/Filter.java new file mode 100644 index 00000000000..1dc7c823759 --- /dev/null +++ b/java/ql/test/stubs/unboundid-ldap-4.0.14/com/unboundid/ldap/sdk/Filter.java @@ -0,0 +1,15 @@ +package com.unboundid.ldap.sdk; + +public class Filter { + public static Filter create(java.lang.String filterString) throws LDAPException { return null; } + + public static Filter createNOTFilter(Filter notComponent) { return null; } + + public static Filter createEqualityFilter(java.lang.String attributeName, java.lang.String assertionValue) { return null; } + + public static java.lang.String encodeValue(java.lang.String value) { return null; } + + public void toNormalizedString(java.lang.StringBuilder buffer) { } + + public String toString() { return ""; } +} diff --git a/java/ql/test/stubs/unboundid-ldap-4.0.14/com/unboundid/ldap/sdk/LDAPConnection.java b/java/ql/test/stubs/unboundid-ldap-4.0.14/com/unboundid/ldap/sdk/LDAPConnection.java new file mode 100644 index 00000000000..36ce082de57 --- /dev/null +++ b/java/ql/test/stubs/unboundid-ldap-4.0.14/com/unboundid/ldap/sdk/LDAPConnection.java @@ -0,0 +1,21 @@ +package com.unboundid.ldap.sdk; + +public class LDAPConnection { + public AsyncRequestID asyncSearch(ReadOnlySearchRequest searchRequest) throws LDAPException { return null; } + public AsyncRequestID asyncSearch(SearchRequest searchRequest) throws LDAPException { return null; } + + public SearchResult search(ReadOnlySearchRequest searchRequest) throws LDAPSearchException { return null; } + public SearchResult search(SearchRequest searchRequest) throws LDAPSearchException { return null; } + + public SearchResult search(SearchResultListener searchResultListener, String baseDN, SearchScope scope, DereferencePolicy derefPolicy, + int sizeLimit, int timeLimit, boolean typesOnly, Filter filter, String... attributes) throws LDAPSearchException { return null; } + + public SearchResult search(SearchResultListener searchResultListener, String baseDN, SearchScope scope, DereferencePolicy derefPolicy, + int sizeLimit, int timeLimit, boolean typesOnly, String filter, String... attributes) throws LDAPSearchException { return null; } + + public SearchResult search(String baseDN, SearchScope scope, DereferencePolicy derefPolicy, int sizeLimit, int timeLimit, + boolean typesOnly, String filter, String... attributes) throws LDAPSearchException { return null; } + + public SearchResultEntry searchForEntry(String baseDN, SearchScope scope, DereferencePolicy derefPolicy, int timeLimit, + boolean typesOnly, String filter, String... attributes) throws LDAPSearchException { return null; } +} diff --git a/java/ql/test/stubs/unboundid-ldap-4.0.14/com/unboundid/ldap/sdk/LDAPException.java b/java/ql/test/stubs/unboundid-ldap-4.0.14/com/unboundid/ldap/sdk/LDAPException.java new file mode 100644 index 00000000000..b32b7e94b84 --- /dev/null +++ b/java/ql/test/stubs/unboundid-ldap-4.0.14/com/unboundid/ldap/sdk/LDAPException.java @@ -0,0 +1,3 @@ +package com.unboundid.ldap.sdk; + +public class LDAPException extends Exception { } diff --git a/java/ql/test/stubs/unboundid-ldap-4.0.14/com/unboundid/ldap/sdk/LDAPSearchException.java b/java/ql/test/stubs/unboundid-ldap-4.0.14/com/unboundid/ldap/sdk/LDAPSearchException.java new file mode 100644 index 00000000000..29679929b31 --- /dev/null +++ b/java/ql/test/stubs/unboundid-ldap-4.0.14/com/unboundid/ldap/sdk/LDAPSearchException.java @@ -0,0 +1,3 @@ +package com.unboundid.ldap.sdk; + +public class LDAPSearchException extends LDAPException { } diff --git a/java/ql/test/stubs/unboundid-ldap-4.0.14/com/unboundid/ldap/sdk/ReadOnlySearchRequest.java b/java/ql/test/stubs/unboundid-ldap-4.0.14/com/unboundid/ldap/sdk/ReadOnlySearchRequest.java new file mode 100644 index 00000000000..26c06ebfce6 --- /dev/null +++ b/java/ql/test/stubs/unboundid-ldap-4.0.14/com/unboundid/ldap/sdk/ReadOnlySearchRequest.java @@ -0,0 +1,5 @@ +package com.unboundid.ldap.sdk; + +public interface ReadOnlySearchRequest { + SearchRequest duplicate(); +} diff --git a/java/ql/test/stubs/unboundid-ldap-4.0.14/com/unboundid/ldap/sdk/SearchRequest.java b/java/ql/test/stubs/unboundid-ldap-4.0.14/com/unboundid/ldap/sdk/SearchRequest.java new file mode 100644 index 00000000000..6c7156096b0 --- /dev/null +++ b/java/ql/test/stubs/unboundid-ldap-4.0.14/com/unboundid/ldap/sdk/SearchRequest.java @@ -0,0 +1,17 @@ +package com.unboundid.ldap.sdk; + +public class SearchRequest implements ReadOnlySearchRequest { + public SearchRequest(String baseDN, SearchScope scope, String filter, String... attributes) throws LDAPException { } + + public SearchRequest(SearchResultListener searchResultListener, String baseDN, SearchScope scope, DereferencePolicy derefPolicy, + int sizeLimit, int timeLimit, boolean typesOnly, Filter filter, String... attributes) { } + + public SearchRequest(SearchResultListener searchResultListener, String baseDN, SearchScope scope, DereferencePolicy derefPolicy, + int sizeLimit, int timeLimit, boolean typesOnly, String filter, String... attributes) throws LDAPException { } + + public SearchRequest duplicate() { return null; } + + public void setBaseDN(String baseDN) { } + + public void setFilter(String filter) throws LDAPException { } +} diff --git a/java/ql/test/stubs/unboundid-ldap-4.0.14/com/unboundid/ldap/sdk/SearchResult.java b/java/ql/test/stubs/unboundid-ldap-4.0.14/com/unboundid/ldap/sdk/SearchResult.java new file mode 100644 index 00000000000..98b7b07c4aa --- /dev/null +++ b/java/ql/test/stubs/unboundid-ldap-4.0.14/com/unboundid/ldap/sdk/SearchResult.java @@ -0,0 +1,3 @@ +package com.unboundid.ldap.sdk; + +public class SearchResult { } diff --git a/java/ql/test/stubs/unboundid-ldap-4.0.14/com/unboundid/ldap/sdk/SearchResultEntry.java b/java/ql/test/stubs/unboundid-ldap-4.0.14/com/unboundid/ldap/sdk/SearchResultEntry.java new file mode 100644 index 00000000000..637683f50af --- /dev/null +++ b/java/ql/test/stubs/unboundid-ldap-4.0.14/com/unboundid/ldap/sdk/SearchResultEntry.java @@ -0,0 +1,3 @@ +package com.unboundid.ldap.sdk; + +public class SearchResultEntry { } diff --git a/java/ql/test/stubs/unboundid-ldap-4.0.14/com/unboundid/ldap/sdk/SearchResultListener.java b/java/ql/test/stubs/unboundid-ldap-4.0.14/com/unboundid/ldap/sdk/SearchResultListener.java new file mode 100644 index 00000000000..856793a91bf --- /dev/null +++ b/java/ql/test/stubs/unboundid-ldap-4.0.14/com/unboundid/ldap/sdk/SearchResultListener.java @@ -0,0 +1,3 @@ +package com.unboundid.ldap.sdk; + +public interface SearchResultListener { } diff --git a/java/ql/test/stubs/unboundid-ldap-4.0.14/com/unboundid/ldap/sdk/SearchScope.java b/java/ql/test/stubs/unboundid-ldap-4.0.14/com/unboundid/ldap/sdk/SearchScope.java new file mode 100644 index 00000000000..18dda6314a8 --- /dev/null +++ b/java/ql/test/stubs/unboundid-ldap-4.0.14/com/unboundid/ldap/sdk/SearchScope.java @@ -0,0 +1,3 @@ +package com.unboundid.ldap.sdk; + +public class SearchScope { } diff --git a/java/upgrades/qlpack.yml b/java/upgrades/qlpack.yml new file mode 100644 index 00000000000..aee4ed51c3e --- /dev/null +++ b/java/upgrades/qlpack.yml @@ -0,0 +1,2 @@ +name: codeql-java-upgrades +upgrades: . diff --git a/javascript/extractor/lib/typescript/src/ast_extractor.ts b/javascript/extractor/lib/typescript/src/ast_extractor.ts index 4ef306db0ca..c1415dbd294 100644 --- a/javascript/extractor/lib/typescript/src/ast_extractor.ts +++ b/javascript/extractor/lib/typescript/src/ast_extractor.ts @@ -251,8 +251,13 @@ export function augmentAst(ast: AugmentedSourceFile, code: string, project: Proj } } } - if (isNamedNodeWithSymbol(node)) { - let symbol = typeChecker.getSymbolAtLocation(node.name); + let symbolNode = + isNamedNodeWithSymbol(node) ? node.name : + ts.isImportDeclaration(node) ? node.moduleSpecifier : + ts.isExternalModuleReference(node) ? node.expression : + null; + if (symbolNode != null) { + let symbol = typeChecker.getSymbolAtLocation(symbolNode); if (symbol != null) { node.$symbol = typeTable.getSymbolId(symbol); } diff --git a/javascript/extractor/lib/typescript/src/common.ts b/javascript/extractor/lib/typescript/src/common.ts index cad531ca622..1dbe54b4632 100644 --- a/javascript/extractor/lib/typescript/src/common.ts +++ b/javascript/extractor/lib/typescript/src/common.ts @@ -1,10 +1,44 @@ import * as ts from "./typescript"; import { TypeTable } from "./type_table"; +import * as pathlib from "path"; +import { VirtualSourceRoot } from "./virtual_source_root"; + +/** + * Extracts the package name from the prefix of an import string. + */ +const packageNameRex = /^(?:@[\w.-]+[/\\]+)?\w[\w.-]*(?=[/\\]|$)/; +const extensions = ['.ts', '.tsx', '.d.ts', '.js', '.jsx']; + +function getPackageName(importString: string) { + let packageNameMatch = packageNameRex.exec(importString); + if (packageNameMatch == null) return null; + let packageName = packageNameMatch[0]; + if (packageName.charAt(0) === '@') { + packageName = packageName.replace(/[/\\]+/g, '/'); // Normalize slash after the scope. + } + return packageName; +} export class Project { public program: ts.Program = null; + private host: ts.CompilerHost; + private resolutionCache: ts.ModuleResolutionCache; - constructor(public tsConfig: string, public config: ts.ParsedCommandLine, public typeTable: TypeTable) {} + constructor( + public tsConfig: string, + public config: ts.ParsedCommandLine, + public typeTable: TypeTable, + public packageEntryPoints: Map, + public virtualSourceRoot: VirtualSourceRoot) { + + this.resolveModuleNames = this.resolveModuleNames.bind(this); + + this.resolutionCache = ts.createModuleResolutionCache(pathlib.dirname(tsConfig), ts.sys.realpath, config.options); + let host = ts.createCompilerHost(config.options, true); + host.resolveModuleNames = this.resolveModuleNames; + host.trace = undefined; // Disable tracing which would otherwise go to standard out + this.host = host; + } public unload(): void { this.typeTable.releaseProgram(); @@ -12,9 +46,8 @@ export class Project { } public load(): void { - let host = ts.createCompilerHost(this.config.options, true); - host.trace = undefined; // Disable tracing which would otherwise go to standard out - this.program = ts.createProgram(this.config.fileNames, this.config.options, host); + const { config, host } = this; + this.program = ts.createProgram(config.fileNames, config.options, host); this.typeTable.setProgram(this.program); } @@ -27,4 +60,73 @@ export class Project { this.unload(); this.load(); } + + /** + * Override for module resolution in the TypeScript compiler host. + */ + private resolveModuleNames( + moduleNames: string[], + containingFile: string, + reusedNames: string[], + redirectedReference: ts.ResolvedProjectReference, + options: ts.CompilerOptions) { + + const { host, resolutionCache } = this; + return moduleNames.map((moduleName) => { + let redirected = this.redirectModuleName(moduleName, containingFile, options); + if (redirected != null) return redirected; + return ts.resolveModuleName(moduleName, containingFile, options, host, resolutionCache).resolvedModule; + }); + } + + /** + * Returns the path that the given import string should be redirected to, or null if it should + * fall back to standard module resolution. + */ + private redirectModuleName(moduleName: string, containingFile: string, options: ts.CompilerOptions): ts.ResolvedModule { + // Get a package name from the leading part of the module name, e.g. '@scope/foo' from '@scope/foo/bar'. + let packageName = getPackageName(moduleName); + if (packageName == null) return null; + + // Get the overridden location of this package, if one exists. + let packageEntryPoint = this.packageEntryPoints.get(packageName); + if (packageEntryPoint == null) { + // The package is not overridden, but we have established that it begins with a valid package name. + // Do a lookup in the virtual source root (where dependencies are installed) by changing the 'containing file'. + let virtualContainingFile = this.virtualSourceRoot.toVirtualPath(containingFile); + if (virtualContainingFile != null) { + return ts.resolveModuleName(moduleName, virtualContainingFile, options, this.host, this.resolutionCache).resolvedModule; + } + return null; + } + + // If the requested module name is exactly the overridden package name, + // return the entry point file (it is not necessarily called `index.ts`). + if (moduleName === packageName) { + return { resolvedFileName: packageEntryPoint, isExternalLibraryImport: true }; + } + + // Get the suffix after the package name, e.g. the '/bar' in '@scope/foo/bar'. + let suffix = moduleName.substring(packageName.length); + + // Resolve the suffix relative to the package directory. + let packageDir = pathlib.dirname(packageEntryPoint); + let joinedPath = pathlib.join(packageDir, suffix); + + // Add implicit '/index' + if (ts.sys.directoryExists(joinedPath)) { + joinedPath = pathlib.join(joinedPath, 'index'); + } + + // Try each recognized extension. We must not return a file whose extension is not + // recognized by TypeScript. + for (let ext of extensions) { + let candidate = joinedPath.endsWith(ext) ? joinedPath : (joinedPath + ext); + if (ts.sys.fileExists(candidate)) { + return { resolvedFileName: candidate, isExternalLibraryImport: true }; + } + } + + return null; + } } diff --git a/javascript/extractor/lib/typescript/src/main.ts b/javascript/extractor/lib/typescript/src/main.ts index c260bb321d9..188544a3792 100644 --- a/javascript/extractor/lib/typescript/src/main.ts +++ b/javascript/extractor/lib/typescript/src/main.ts @@ -39,6 +39,7 @@ import * as ast_extractor from "./ast_extractor"; import { Project } from "./common"; import { TypeTable } from "./type_table"; +import { VirtualSourceRoot } from "./virtual_source_root"; interface ParseCommand { command: "parse"; @@ -47,6 +48,9 @@ interface ParseCommand { interface OpenProjectCommand { command: "open-project"; tsConfig: string; + virtualSourceRoot: string | null; + packageEntryPoints: [string, string][]; + packageJsonFiles: [string, string][]; } interface CloseProjectCommand { command: "close-project"; @@ -242,26 +246,93 @@ function parseSingleFile(filename: string): {ast: ts.SourceFile, code: string} { return {ast, code}; } +/** + * Matches a path segment referencing a package in a node_modules folder, and extracts + * two capture groups: the package name, and the relative path in the package. + * + * For example `lib/node_modules/@foo/bar/src/index.js` extracts the capture groups [`@foo/bar`, `src/index.js`]. + */ +const nodeModulesRex = /[/\\]node_modules[/\\]((?:@[\w.-]+[/\\])?\w[\w.-]*)[/\\](.*)/; + function handleOpenProjectCommand(command: OpenProjectCommand) { Error.stackTraceLimit = Infinity; let tsConfigFilename = String(command.tsConfig); let tsConfig = ts.readConfigFile(tsConfigFilename, ts.sys.readFile); let basePath = pathlib.dirname(tsConfigFilename); + let packageEntryPoints = new Map(command.packageEntryPoints); + let packageJsonFiles = new Map(command.packageJsonFiles); + let virtualSourceRoot = new VirtualSourceRoot(process.cwd(), command.virtualSourceRoot); + + /** + * Rewrites path segments of form `node_modules/PACK/suffix` to be relative to + * the location of package PACK in the source tree, if it exists. + */ + function redirectNodeModulesPath(path: string) { + let nodeModulesMatch = nodeModulesRex.exec(path); + if (nodeModulesMatch == null) return null; + let packageName = nodeModulesMatch[1]; + let packageJsonFile = packageJsonFiles.get(packageName); + if (packageJsonFile == null) return null; + let packageDir = pathlib.dirname(packageJsonFile); + let suffix = nodeModulesMatch[2]; + let finalPath = pathlib.join(packageDir, suffix); + if (!ts.sys.fileExists(finalPath)) return null; + return finalPath; + } + + /** + * Create the host passed to the tsconfig.json parser. + * + * We override its file system access in case there is an "extends" + * clause pointing into "./node_modules", which must be redirected to + * the location of an installed package or a checked-in package. + */ let parseConfigHost: ts.ParseConfigHost = { useCaseSensitiveFileNames: true, - readDirectory: ts.sys.readDirectory, - fileExists: (path: string) => fs.existsSync(path), - readFile: ts.sys.readFile, + readDirectory: ts.sys.readDirectory, // No need to override traversal/glob matching + fileExists: (path: string) => { + return ts.sys.fileExists(path) + || virtualSourceRoot.toVirtualPathIfFileExists(path) != null + || redirectNodeModulesPath(path) != null; + }, + readFile: (path: string) => { + if (!ts.sys.fileExists(path)) { + let virtualPath = virtualSourceRoot.toVirtualPathIfFileExists(path); + if (virtualPath != null) return ts.sys.readFile(virtualPath); + virtualPath = redirectNodeModulesPath(path); + if (virtualPath != null) return ts.sys.readFile(virtualPath); + } + return ts.sys.readFile(path); + } }; let config = ts.parseJsonConfigFileContent(tsConfig.config, parseConfigHost, basePath); - let project = new Project(tsConfigFilename, config, state.typeTable); + let project = new Project(tsConfigFilename, config, state.typeTable, packageEntryPoints, virtualSourceRoot); project.load(); state.project = project; let program = project.program; let typeChecker = program.getTypeChecker(); + let diagnostics = program.getSemanticDiagnostics() + .filter(d => d.category === ts.DiagnosticCategory.Error); + if (diagnostics.length > 0) { + console.warn('TypeScript: reported ' + diagnostics.length + ' semantic errors.'); + } + for (let diagnostic of diagnostics) { + let text = diagnostic.messageText; + if (text && typeof text !== 'string') { + text = text.messageText; + } + let locationStr = ''; + let { file } = diagnostic; + if (file != null) { + let { line, character } = file.getLineAndCharacterOfPosition(diagnostic.start); + locationStr = `${file.fileName}:${line}:${character}`; + } + console.warn(`TypeScript: ${locationStr} ${text}`); + } + // Associate external module names with the corresponding file symbols. // We need these mappings to identify which module a given external type comes from. // The TypeScript API lets us resolve a module name to a source file, but there is no @@ -512,6 +583,9 @@ if (process.argv.length > 2) { handleOpenProjectCommand({ command: "open-project", tsConfig: argument, + packageEntryPoints: [], + packageJsonFiles: [], + virtualSourceRoot: null, }); for (let sf of state.project.program.getSourceFiles()) { if (pathlib.basename(sf.fileName) === "lib.d.ts") continue; diff --git a/javascript/extractor/lib/typescript/src/virtual_source_root.ts b/javascript/extractor/lib/typescript/src/virtual_source_root.ts new file mode 100644 index 00000000000..5f1bbf2b95e --- /dev/null +++ b/javascript/extractor/lib/typescript/src/virtual_source_root.ts @@ -0,0 +1,39 @@ +import * as pathlib from "path"; +import * as ts from "./typescript"; + +/** + * Mapping from the real source root to the virtual source root, + * a directory whose folder structure mirrors the real source root, but with `node_modules` installed. + */ +export class VirtualSourceRoot { + constructor( + private sourceRoot: string, + + /** + * Directory whose folder structure mirrors the real source root, but with `node_modules` installed, + * or undefined if no virtual source root exists. + */ + private virtualSourceRoot: string, + ) {} + + /** + * Maps a path under the real source root to the corresponding path in the virtual source root. + */ + public toVirtualPath(path: string) { + if (!this.virtualSourceRoot) return null; + let relative = pathlib.relative(this.sourceRoot, path); + if (relative.startsWith('..') || pathlib.isAbsolute(relative)) return null; + return pathlib.join(this.virtualSourceRoot, relative); + } + + /** + * Maps a path under the real source root to the corresponding path in the virtual source root. + */ + public toVirtualPathIfFileExists(path: string) { + let virtualPath = this.toVirtualPath(path); + if (virtualPath != null && ts.sys.fileExists(virtualPath)) { + return virtualPath; + } + return null; + } +} diff --git a/javascript/extractor/src/com/semmle/jcorn/Parser.java b/javascript/extractor/src/com/semmle/jcorn/Parser.java index f0e3c0d004f..eee2e172a6d 100644 --- a/javascript/extractor/src/com/semmle/jcorn/Parser.java +++ b/javascript/extractor/src/com/semmle/jcorn/Parser.java @@ -5,6 +5,7 @@ import static com.semmle.jcorn.Whitespace.lineBreak; import com.semmle.jcorn.Identifiers.Dialect; import com.semmle.jcorn.Options.AllowReserved; +import com.semmle.jcorn.TokenType.Properties; import com.semmle.js.ast.ArrayExpression; import com.semmle.js.ast.ArrayPattern; import com.semmle.js.ast.ArrowFunctionExpression; @@ -44,6 +45,7 @@ import com.semmle.js.ast.INode; import com.semmle.js.ast.IPattern; import com.semmle.js.ast.Identifier; import com.semmle.js.ast.IfStatement; +import com.semmle.js.ast.FieldDefinition; import com.semmle.js.ast.ImportDeclaration; import com.semmle.js.ast.ImportDefaultSpecifier; import com.semmle.js.ast.ImportNamespaceSpecifier; @@ -124,6 +126,7 @@ public class Parser { private boolean inModule; protected boolean inFunction; protected boolean inGenerator; + protected boolean inClass; protected boolean inAsync; protected boolean inTemplateElement; protected int pos; @@ -240,8 +243,8 @@ public class Parser { // Used to signify the start of a potential arrow function this.potentialArrowAt = -1; - // Flags to track whether we are in a function, a generator, an async function. - this.inFunction = this.inGenerator = this.inAsync = false; + // Flags to track whether we are in a function, a generator, an async function, a class. + this.inFunction = this.inGenerator = this.inAsync = this.inClass = false; // Positions to delayed-check that yield/await does not exist in default parameters. this.yieldPos = this.awaitPos = 0; // Labels in scope. @@ -651,6 +654,9 @@ public class Parser { case 58: ++this.pos; return this.finishToken(TokenType.colon); + case 35: + ++this.pos; + return this.finishToken(TokenType.pound); case 63: return this.readToken_question(); @@ -2191,6 +2197,7 @@ public class Parser { // identifiers. protected Identifier parseIdent(boolean liberal) { Position startLoc = this.startLoc; + boolean isPrivateField = liberal && this.eat(TokenType.pound); if (liberal && this.options.allowReserved() == AllowReserved.NEVER) liberal = false; String name = null; if (this.type == TokenType.name) { @@ -2199,9 +2206,9 @@ public class Parser { && (this.options.ecmaVersion() >= 6 || inputSubstring(this.start, this.end).indexOf("\\") == -1)) this.raiseRecoverable(this.start, "The keyword '" + this.value + "' is reserved"); - if (this.inGenerator && this.value.equals("yield")) + if (!isPrivateField && this.inGenerator && this.value.equals("yield")) this.raiseRecoverable(this.start, "Can not use 'yield' as identifier inside a generator"); - if (this.inAsync && this.value.equals("await")) + if (!isPrivateField && this.inAsync && this.value.equals("await")) this.raiseRecoverable( this.start, "Can not use 'await' as identifier inside an async function"); name = String.valueOf(this.value); @@ -2213,6 +2220,12 @@ public class Parser { this.unexpected(); } this.next(); + if (isPrivateField) { + if (!this.inClass) { + this.raiseRecoverable(this.start, "Cannot use private fields outside a class"); + } + name = "#" + name; + } Identifier node = new Identifier(new SourceLocation(startLoc), name); return this.finishNode(node); } @@ -3127,6 +3140,8 @@ public class Parser { // Parse a class declaration or literal (depending on the // `isStatement` parameter). protected Node parseClass(Position startLoc, boolean isStatement) { + boolean oldInClass = this.inClass; + this.inClass = true; SourceLocation loc = new SourceLocation(startLoc); this.next(); Identifier id = this.parseClassId(isStatement); @@ -3145,6 +3160,8 @@ public class Parser { Node node; if (isStatement) node = new ClassDeclaration(loc, id, superClass, classBody); else node = new ClassExpression(loc, id, superClass, classBody); + + this.inClass = oldInClass; return this.finishNode(node); } @@ -3221,6 +3238,9 @@ public class Parser { if (pi.kind.equals("set") && node.getValue().hasRest()) this.raiseRecoverable(params.get(params.size() - 1), "Setter cannot use rest params"); } + if (pi.key instanceof Identifier && ((Identifier)pi.key).getName().startsWith("#")) { + raiseRecoverable(pi.key, "Only fields, not methods, can be declared private."); + } return node; } diff --git a/javascript/extractor/src/com/semmle/jcorn/TokenType.java b/javascript/extractor/src/com/semmle/jcorn/TokenType.java index 25d6ee9cb53..e3c005c1b6a 100644 --- a/javascript/extractor/src/com/semmle/jcorn/TokenType.java +++ b/javascript/extractor/src/com/semmle/jcorn/TokenType.java @@ -85,6 +85,7 @@ public class TokenType { dot = new TokenType(new Properties(".")), questiondot = new TokenType(new Properties("?.")), question = new TokenType(new Properties("?").beforeExpr()), + pound = new TokenType(kw("#")), arrow = new TokenType(new Properties("=>").beforeExpr()), template = new TokenType(new Properties("template")), invalidTemplate = new TokenType(new Properties("invalidTemplate")), diff --git a/javascript/extractor/src/com/semmle/js/ast/ImportDeclaration.java b/javascript/extractor/src/com/semmle/js/ast/ImportDeclaration.java index a63b5fdbba1..1e538c1bb74 100644 --- a/javascript/extractor/src/com/semmle/js/ast/ImportDeclaration.java +++ b/javascript/extractor/src/com/semmle/js/ast/ImportDeclaration.java @@ -1,5 +1,6 @@ package com.semmle.js.ast; +import com.semmle.ts.ast.INodeWithSymbol; import java.util.List; /** @@ -14,13 +15,15 @@ import java.util.List; * import "m"; * */ -public class ImportDeclaration extends Statement { +public class ImportDeclaration extends Statement implements INodeWithSymbol { /** List of import specifiers detailing how declarations are imported; may be empty. */ private final List specifiers; /** The module from which declarations are imported. */ private final Literal source; + private int symbol = -1; + public ImportDeclaration(SourceLocation loc, List specifiers, Literal source) { super("ImportDeclaration", loc); this.specifiers = specifiers; @@ -39,4 +42,14 @@ public class ImportDeclaration extends Statement { public R accept(Visitor v, C c) { return v.visit(this, c); } + + @Override + public int getSymbol() { + return this.symbol; + } + + @Override + public void setSymbol(int symbol) { + this.symbol = symbol; + } } diff --git a/javascript/extractor/src/com/semmle/js/extractor/ASTExtractor.java b/javascript/extractor/src/com/semmle/js/extractor/ASTExtractor.java index 4bc7d0fbdd3..6af90d562d8 100644 --- a/javascript/extractor/src/com/semmle/js/extractor/ASTExtractor.java +++ b/javascript/extractor/src/com/semmle/js/extractor/ASTExtractor.java @@ -1555,6 +1555,7 @@ public class ASTExtractor { Label lbl = super.visit(nd, c); visit(nd.getSource(), lbl, -1); visitAll(nd.getSpecifiers(), lbl); + emitNodeSymbol(nd, lbl); return lbl; } @@ -1705,6 +1706,7 @@ public class ASTExtractor { public Label visit(ExternalModuleReference nd, Context c) { Label key = super.visit(nd, c); visit(nd.getExpression(), key, 0); + emitNodeSymbol(nd, key); return key; } @@ -2061,12 +2063,14 @@ public class ASTExtractor { @Override public Label visit(AssignmentPattern nd, Context c) { - additionalErrors.add(new ParseError("Unexpected assignment pattern.", nd.getLoc().getStart())); + additionalErrors.add( + new ParseError("Unexpected assignment pattern.", nd.getLoc().getStart())); return super.visit(nd, c); } } - public List extract(Node root, Platform platform, SourceType sourceType, int toplevelKind) { + public List extract( + Node root, Platform platform, SourceType sourceType, int toplevelKind) { lexicalExtractor.getMetrics().startPhase(ExtractionPhase.ASTExtractor_extract); trapwriter.addTuple("toplevels", toplevelLabel, toplevelKind); locationManager.emitNodeLocation(root, toplevelLabel); diff --git a/javascript/extractor/src/com/semmle/js/extractor/AutoBuild.java b/javascript/extractor/src/com/semmle/js/extractor/AutoBuild.java index 9f52be05cb6..35295ae0678 100644 --- a/javascript/extractor/src/com/semmle/js/extractor/AutoBuild.java +++ b/javascript/extractor/src/com/semmle/js/extractor/AutoBuild.java @@ -1,31 +1,11 @@ package com.semmle.js.extractor; -import com.semmle.js.extractor.ExtractorConfig.SourceType; -import com.semmle.js.extractor.FileExtractor.FileType; -import com.semmle.js.extractor.trapcache.DefaultTrapCache; -import com.semmle.js.extractor.trapcache.DummyTrapCache; -import com.semmle.js.extractor.trapcache.ITrapCache; -import com.semmle.js.parser.ParsedProject; -import com.semmle.js.parser.TypeScriptParser; -import com.semmle.ts.extractor.TypeExtractor; -import com.semmle.ts.extractor.TypeTable; -import com.semmle.util.data.StringUtil; -import com.semmle.util.exception.CatastrophicError; -import com.semmle.util.exception.Exceptions; -import com.semmle.util.exception.ResourceError; -import com.semmle.util.exception.UserError; -import com.semmle.util.extraction.ExtractorOutputConfig; -import com.semmle.util.files.FileUtil; -import com.semmle.util.io.csv.CSVReader; -import com.semmle.util.language.LegacyLanguage; -import com.semmle.util.process.Env; -import com.semmle.util.projectstructure.ProjectLayout; -import com.semmle.util.trap.TrapWriter; import java.io.BufferedReader; import java.io.File; import java.io.IOException; import java.io.InputStreamReader; import java.io.Reader; +import java.io.Writer; import java.lang.ProcessBuilder.Redirect; import java.net.URI; import java.net.URISyntaxException; @@ -50,6 +30,35 @@ import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.stream.Stream; +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; +import com.google.gson.JsonParser; +import com.google.gson.JsonPrimitive; +import com.semmle.js.extractor.ExtractorConfig.SourceType; +import com.semmle.js.extractor.FileExtractor.FileType; +import com.semmle.js.extractor.trapcache.DefaultTrapCache; +import com.semmle.js.extractor.trapcache.DummyTrapCache; +import com.semmle.js.extractor.trapcache.ITrapCache; +import com.semmle.js.parser.ParsedProject; +import com.semmle.js.parser.TypeScriptParser; +import com.semmle.ts.extractor.TypeExtractor; +import com.semmle.ts.extractor.TypeTable; +import com.semmle.util.data.StringUtil; +import com.semmle.util.exception.CatastrophicError; +import com.semmle.util.exception.Exceptions; +import com.semmle.util.exception.ResourceError; +import com.semmle.util.exception.UserError; +import com.semmle.util.extraction.ExtractorOutputConfig; +import com.semmle.util.files.FileUtil; +import com.semmle.util.io.WholeIO; +import com.semmle.util.io.csv.CSVReader; +import com.semmle.util.language.LegacyLanguage; +import com.semmle.util.process.Env; +import com.semmle.util.projectstructure.ProjectLayout; +import com.semmle.util.trap.TrapWriter; + /** * An alternative entry point to the JavaScript extractor. * @@ -388,9 +397,10 @@ public class AutoBuild { for (FileType filetype : defaultExtract) for (String extension : filetype.getExtensions()) patterns.add("**/*" + extension); - // include .eslintrc files and package.json files + // include .eslintrc files, package.json files, and tsconfig.json files patterns.add("**/.eslintrc*"); patterns.add("**/package.json"); + patterns.add("**/tsconfig.json"); // include any explicitly specified extensions for (String extension : fileTypes.keySet()) patterns.add("**/*" + extension); @@ -545,12 +555,15 @@ public class AutoBuild { List tsconfigFiles = new ArrayList<>(); findFilesToExtract(defaultExtractor, filesToExtract, tsconfigFiles); + DependencyInstallationResult dependencyInstallationResult = DependencyInstallationResult.empty; if (!tsconfigFiles.isEmpty() && this.installDependencies) { - this.installDependencies(filesToExtract); + dependencyInstallationResult = this.installDependencies(filesToExtract); } // extract TypeScript projects and files - Set extractedFiles = extractTypeScript(defaultExtractor, filesToExtract, tsconfigFiles); + Set extractedFiles = + extractTypeScript( + defaultExtractor, filesToExtract, tsconfigFiles, dependencyInstallationResult); // extract remaining files for (Path f : filesToExtract) { @@ -587,36 +600,255 @@ public class AutoBuild { } } - protected void installDependencies(Set filesToExtract) { - if (!verifyYarnInstallation()) { - return; + /** + * Returns an existing file named dir/stem.ext where .ext is any + * of the given extensions, or null if no such file exists. + */ + private static Path tryResolveWithExtensions(Path dir, String stem, Iterable extensions) { + for (String ext : extensions) { + Path path = dir.resolve(stem + ext); + if (Files.exists(dir.resolve(path))) { + return path; + } } + return null; + } + + /** + * Returns an existing file named dir/stem.ext where ext is any TypeScript or JavaScript extension, + * or null if no such file exists. + */ + private static Path tryResolveTypeScriptOrJavaScriptFile(Path dir, String stem) { + Path resolved = tryResolveWithExtensions(dir, stem, FileType.TYPESCRIPT.getExtensions()); + if (resolved != null) return resolved; + return tryResolveWithExtensions(dir, stem, FileType.JS.getExtensions()); + } + + /** + * Gets a child of a JSON object as a string, or null. + */ + private String getChildAsString(JsonObject obj, String name) { + JsonElement child = obj.get(name); + if (child instanceof JsonPrimitive && ((JsonPrimitive)child).isString()) { + return child.getAsString(); + } + return null; + } + + /** + * Installs dependencies for use by the TypeScript type checker. + *

    + * Some packages must be downloaded while others exist within the same repo ("monorepos") + * but are not in a location where TypeScript would look for it. + *

    + * Downloaded packages are intalled under SCRATCH_DIR, in a mirrored directory hierarchy + * we call the "virtual source root". + * Each package.json file is rewritten and copied to the virtual source root, + * where yarn install is invoked. + *

    + * Packages that exists within the repo are stripped from the dependencies + * before installation, so they are not downloaded. Since they are part of the main source tree, + * these packages are not mirrored under the virtual source root. + * Instead, an explicit package location mapping is passed to the TypeScript parser wrapper. + *

    + * The TypeScript parser wrapper then overrides module resolution so packages can be found + * under the virtual source root and via that package location mapping. + */ + protected DependencyInstallationResult installDependencies(Set filesToExtract) { + if (!verifyYarnInstallation()) { + return DependencyInstallationResult.empty; + } + + final Path sourceRoot = Paths.get(".").toAbsolutePath(); + final Path virtualSourceRoot = Paths.get(EnvironmentVariables.getScratchDir()).toAbsolutePath(); + + // Read all package.json files and index them by name. + Map packageJsonFiles = new LinkedHashMap<>(); + Map packagesInRepo = new LinkedHashMap<>(); + Map packageMainFile = new LinkedHashMap<>(); for (Path file : filesToExtract) { if (file.getFileName().toString().equals("package.json")) { - System.out.println("Installing dependencies from " + file); - ProcessBuilder pb = - new ProcessBuilder( - Arrays.asList( - "yarn", - "install", - "--non-interactive", - "--ignore-scripts", - "--ignore-platform", - "--ignore-engines", - "--ignore-optional", - "--no-default-rc", - "--no-bin-links", - "--pure-lockfile")); - pb.directory(file.getParent().toFile()); - pb.redirectOutput(Redirect.INHERIT); - pb.redirectError(Redirect.INHERIT); try { - pb.start().waitFor(this.installDependenciesTimeout, TimeUnit.MILLISECONDS); - } catch (IOException | InterruptedException ex) { - throw new ResourceError("Could not install dependencies from " + file, ex); + String text = new WholeIO().read(file); + JsonElement json = new JsonParser().parse(text); + if (!(json instanceof JsonObject)) continue; + JsonObject jsonObject = (JsonObject) json; + file = file.toAbsolutePath(); + packageJsonFiles.put(file, jsonObject); + + String name = getChildAsString(jsonObject, "name"); + if (name != null) { + packagesInRepo.put(name, file); + } + } catch (JsonParseException e) { + System.err.println("Could not parse JSON file: " + file); + System.err.println(e); + // Continue without the malformed package.json file } } } + + // Process all package.json files now that we know the names of all local packages. + // - remove dependencies on local packages + // - guess the main file for each package + // Note that we ignore optional dependencies during installation, so "optionalDependencies" + // is ignored here as well. + final List dependencyFields = + Arrays.asList("dependencies", "devDependencies", "peerDependencies"); + packageJsonFiles.forEach( + (path, packageJson) -> { + Path relativePath = sourceRoot.relativize(path); + for (String dependencyField : dependencyFields) { + JsonElement dependencyElm = packageJson.get(dependencyField); + if (!(dependencyElm instanceof JsonObject)) continue; + JsonObject dependencyObj = (JsonObject) dependencyElm; + List propsToRemove = new ArrayList<>(); + for (String packageName : dependencyObj.keySet()) { + if (packagesInRepo.containsKey(packageName)) { + // Remove dependency on local package + propsToRemove.add(packageName); + } else { + // Remove file dependency on a package that doesn't exist in the checkout. + String dependency = getChildAsString(dependencyObj, packageName); + if (dependency != null && (dependency.startsWith("file:") || dependency.startsWith("./") || dependency.startsWith("../"))) { + if (dependency.startsWith("file:")) { + dependency = dependency.substring("file:".length()); + } + Path resolvedPackage = path.getParent().resolve(dependency + "/package.json"); + if (!Files.exists(resolvedPackage)) { + propsToRemove.add(packageName); + } + } + } + } + for (String prop : propsToRemove) { + dependencyObj.remove(prop); + } + } + // For named packages, find the main file. + String name = getChildAsString(packageJson, "name"); + if (name != null) { + Path entryPoint = guessPackageMainFile(path, packageJson, FileType.TYPESCRIPT.getExtensions()); + if (entryPoint == null) { + // Try a TypeScript-recognized JS extension instead + entryPoint = guessPackageMainFile(path, packageJson, Arrays.asList(".js", ".jsx")); + } + if (entryPoint != null) { + System.out.println(relativePath + ": Main file set to " + sourceRoot.relativize(entryPoint)); + packageMainFile.put(name, entryPoint); + } else { + System.out.println(relativePath + ": Main file not found"); + } + } + }); + + // Write the new package.json files to disk + for (Path file : packageJsonFiles.keySet()) { + Path relativePath = sourceRoot.relativize(file); + Path virtualFile = virtualSourceRoot.resolve(relativePath); + + try { + Files.createDirectories(virtualFile.getParent()); + try (Writer writer = Files.newBufferedWriter(virtualFile)) { + new Gson().toJson(packageJsonFiles.get(file), writer); + } + } catch (IOException e) { + throw new ResourceError("Could not rewrite package.json file: " + virtualFile, e); + } + } + + // Install dependencies + for (Path file : packageJsonFiles.keySet()) { + Path virtualFile = virtualSourceRoot.resolve(sourceRoot.relativize(file)); + System.out.println("Installing dependencies from " + virtualFile); + ProcessBuilder pb = + new ProcessBuilder( + Arrays.asList( + "yarn", + "install", + "--non-interactive", + "--ignore-scripts", + "--ignore-platform", + "--ignore-engines", + "--ignore-optional", + "--no-default-rc", + "--no-bin-links", + "--pure-lockfile")); + pb.directory(virtualFile.getParent().toFile()); + pb.redirectOutput(Redirect.INHERIT); + pb.redirectError(Redirect.INHERIT); + try { + pb.start().waitFor(this.installDependenciesTimeout, TimeUnit.MILLISECONDS); + } catch (IOException | InterruptedException ex) { + throw new ResourceError("Could not install dependencies from " + file, ex); + } + } + + return new DependencyInstallationResult(virtualSourceRoot, packageMainFile, packagesInRepo); + } + + /** + * Attempts to find a TypeScript file that acts as the main entry point to the + * given package - that is, the file you get when importing the package by name + * without any path suffix. + */ + private Path guessPackageMainFile(Path packageJsonFile, JsonObject packageJson, Iterable extensions) { + Path packageDir = packageJsonFile.getParent(); + + // Try /index.ts. + Path resolved = tryResolveWithExtensions(packageDir, "index", extensions); + if (resolved != null) { + return resolved; + } + + // Get the "main" property from the package.json + // This usually refers to the compiled output, such as `./out/foo.js` but may hint as to + // the name of main file ("foo" in this case). + String mainStr = getChildAsString(packageJson, "main"); + + // Look for source files `./src` if it exists + Path sourceDir = packageDir.resolve("src"); + if (Files.isDirectory(sourceDir)) { + // Try `src/index.ts` + resolved = tryResolveTypeScriptOrJavaScriptFile(sourceDir, "index"); + if (resolved != null) { + return resolved; + } + + // If "main" was defined, try to map it to a file in `src`. + // For example `out/dist/foo.bundle.js` might be mapped back to `src/foo.ts`. + if (mainStr != null) { + Path candidatePath = Paths.get(mainStr); + + // Strip off prefix directories that don't exist under `src/`, such as `out` and `dist`. + while (candidatePath.getNameCount() > 1 && !Files.isDirectory(sourceDir.resolve(candidatePath.getParent()))) { + candidatePath = candidatePath.subpath(1, candidatePath.getNameCount()); + } + + // Strip off extensions until a file can be found + while (true) { + resolved = tryResolveWithExtensions(sourceDir, candidatePath.toString(), extensions); + if (resolved != null) { + return resolved; + } + Path withoutExt = candidatePath.resolveSibling(FileUtil.stripExtension(candidatePath.getFileName().toString())); + if (withoutExt.equals(candidatePath)) break; // No more extensions to strip + candidatePath = withoutExt; + } + } + } + + // Try to resolve main as a sibling of the package.json file, such as "./main.js" -> "./main.ts". + if (mainStr != null) { + Path mainPath = Paths.get(mainStr); + String withoutExt = FileUtil.stripExtension(mainPath.getFileName().toString()); + resolved = tryResolveWithExtensions(packageDir, withoutExt, extensions); + if (resolved != null) { + return resolved; + } + } + + return null; } private ExtractorConfig mkExtractorConfig() { @@ -628,7 +860,10 @@ public class AutoBuild { } private Set extractTypeScript( - FileExtractor extractor, Set files, List tsconfig) { + FileExtractor extractor, + Set files, + List tsconfig, + DependencyInstallationResult deps) { Set extractedFiles = new LinkedHashSet<>(); if (hasTypeScriptFiles(files) || !tsconfig.isEmpty()) { @@ -640,7 +875,7 @@ public class AutoBuild { for (Path projectPath : tsconfig) { File projectFile = projectPath.toFile(); long start = logBeginProcess("Opening project " + projectFile); - ParsedProject project = tsParser.openProject(projectFile); + ParsedProject project = tsParser.openProject(projectFile, deps); logEndProcess(start, "Done opening project " + projectFile); // Extract all files belonging to this project which are also matched // by our include/exclude filters. @@ -729,7 +964,8 @@ public class AutoBuild { // extract TypeScript projects from 'tsconfig.json' if (typeScriptMode == TypeScriptMode.FULL && file.getFileName().endsWith("tsconfig.json") - && !excludes.contains(file)) { + && !excludes.contains(file) + && isFileIncluded(file)) { tsconfigFiles.add(file); } diff --git a/javascript/extractor/src/com/semmle/js/extractor/DependencyInstallationResult.java b/javascript/extractor/src/com/semmle/js/extractor/DependencyInstallationResult.java new file mode 100644 index 00000000000..460a6573f6b --- /dev/null +++ b/javascript/extractor/src/com/semmle/js/extractor/DependencyInstallationResult.java @@ -0,0 +1,48 @@ +package com.semmle.js.extractor; + +import java.nio.file.Path; +import java.util.Collections; +import java.util.Map; + +/** Contains the results of installing dependencies. */ +public class DependencyInstallationResult { + private Path virtualSourceRoot; + private Map packageEntryPoints; + private Map packageJsonFiles; + + public static final DependencyInstallationResult empty = + new DependencyInstallationResult(null, Collections.emptyMap(), Collections.emptyMap()); + + public DependencyInstallationResult( + Path virtualSourceRoot, + Map packageEntryPoints, + Map packageJsonFiles) { + this.packageEntryPoints = packageEntryPoints; + this.packageJsonFiles = packageJsonFiles; + } + + /** + * Returns the virtual source root or null if no virtual source root exists. + * + * The virtual source root is a directory hierarchy that mirrors the real source + * root, where dependencies are installed. + */ + public Path getVirtualSourceRoot() { + return virtualSourceRoot; + } + + /** + * Returns the mapping from package names to the TypeScript file that should + * act as its main entry point. + */ + public Map getPackageEntryPoints() { + return packageEntryPoints; + } + + /** + * Returns the mapping from package name to corresponding package.json. + */ + public Map getPackageJsonFiles() { + return packageJsonFiles; + } +} diff --git a/javascript/extractor/src/com/semmle/js/extractor/EnvironmentVariables.java b/javascript/extractor/src/com/semmle/js/extractor/EnvironmentVariables.java index 4f991c42fa7..6f8e7124b3c 100644 --- a/javascript/extractor/src/com/semmle/js/extractor/EnvironmentVariables.java +++ b/javascript/extractor/src/com/semmle/js/extractor/EnvironmentVariables.java @@ -7,16 +7,22 @@ import com.semmle.util.process.Env.Var; public class EnvironmentVariables { public static final String CODEQL_EXTRACTOR_JAVASCRIPT_ROOT_ENV_VAR = "CODEQL_EXTRACTOR_JAVASCRIPT_ROOT"; + + public static final String CODEQL_EXTRACTOR_JAVASCRIPT_SCRATCH_DIR_ENV_VAR = + "CODEQL_EXTRACTOR_JAVASCRIPT_SCRATCH_DIR"; + + public static final String LGTM_WORKSPACE_ENV_VAR = + "LGTM_WORKSPACE"; /** * Gets the extractor root based on the CODEQL_EXTRACTOR_JAVASCRIPT_ROOT or * SEMMLE_DIST or environment variable, or null if neither is set. */ public static String tryGetExtractorRoot() { - String env = Env.systemEnv().get(CODEQL_EXTRACTOR_JAVASCRIPT_ROOT_ENV_VAR); - if (env != null && !env.isEmpty()) return env; - env = Env.systemEnv().get(Var.SEMMLE_DIST); - if (env != null && !env.isEmpty()) return env; + String env = Env.systemEnv().getNonEmpty(CODEQL_EXTRACTOR_JAVASCRIPT_ROOT_ENV_VAR); + if (env != null) return env; + env = Env.systemEnv().getNonEmpty(Var.SEMMLE_DIST); + if (env != null) return env; return null; } @@ -31,4 +37,16 @@ public class EnvironmentVariables { } return env; } + + /** + * Gets the scratch directory from the appropriate environment variable. + */ + public static String getScratchDir() { + String env = Env.systemEnv().getNonEmpty(CODEQL_EXTRACTOR_JAVASCRIPT_SCRATCH_DIR_ENV_VAR); + if (env != null) return env; + env = Env.systemEnv().getNonEmpty(LGTM_WORKSPACE_ENV_VAR); + if (env != null) return env; + + throw new UserError(CODEQL_EXTRACTOR_JAVASCRIPT_SCRATCH_DIR_ENV_VAR + " or " + LGTM_WORKSPACE_ENV_VAR + " must be set"); + } } diff --git a/javascript/extractor/src/com/semmle/js/extractor/Main.java b/javascript/extractor/src/com/semmle/js/extractor/Main.java index 4f522513873..062d44de30c 100644 --- a/javascript/extractor/src/com/semmle/js/extractor/Main.java +++ b/javascript/extractor/src/com/semmle/js/extractor/Main.java @@ -140,7 +140,7 @@ public class Main { for (File projectFile : projectFiles) { long start = verboseLogStartTimer(ap, "Opening project " + projectFile); - ParsedProject project = tsParser.openProject(projectFile); + ParsedProject project = tsParser.openProject(projectFile, DependencyInstallationResult.empty); verboseLogEndTimer(ap, start); // Extract all files belonging to this project which are also matched // by our include/exclude filters. diff --git a/javascript/extractor/src/com/semmle/js/extractor/test/AutoBuildTests.java b/javascript/extractor/src/com/semmle/js/extractor/test/AutoBuildTests.java index 3e39cfc6706..aefe68f7370 100644 --- a/javascript/extractor/src/com/semmle/js/extractor/test/AutoBuildTests.java +++ b/javascript/extractor/src/com/semmle/js/extractor/test/AutoBuildTests.java @@ -1,14 +1,5 @@ package com.semmle.js.extractor.test; -import com.semmle.js.extractor.AutoBuild; -import com.semmle.js.extractor.ExtractorState; -import com.semmle.js.extractor.FileExtractor; -import com.semmle.js.extractor.FileExtractor.FileType; -import com.semmle.util.data.StringUtil; -import com.semmle.util.exception.UserError; -import com.semmle.util.files.FileUtil; -import com.semmle.util.files.FileUtil8; -import com.semmle.util.process.Env; import java.io.File; import java.io.IOException; import java.nio.charset.StandardCharsets; @@ -25,12 +16,24 @@ import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; + import org.junit.After; import org.junit.Assert; import org.junit.Assume; import org.junit.Before; import org.junit.Test; +import com.semmle.js.extractor.AutoBuild; +import com.semmle.js.extractor.DependencyInstallationResult; +import com.semmle.js.extractor.ExtractorState; +import com.semmle.js.extractor.FileExtractor; +import com.semmle.js.extractor.FileExtractor.FileType; +import com.semmle.util.data.StringUtil; +import com.semmle.util.exception.UserError; +import com.semmle.util.files.FileUtil; +import com.semmle.util.files.FileUtil8; +import com.semmle.util.process.Env; + public class AutoBuildTests { private Path SEMMLE_DIST, LGTM_SRC; private Set expected; @@ -129,8 +132,9 @@ public class AutoBuildTests { } @Override - protected void installDependencies(Set filesToExtract) { + protected DependencyInstallationResult installDependencies(Set filesToExtract) { // never install dependencies during testing + return DependencyInstallationResult.empty; } @Override diff --git a/javascript/extractor/src/com/semmle/js/parser/TypeScriptASTConverter.java b/javascript/extractor/src/com/semmle/js/parser/TypeScriptASTConverter.java index 733eba38df1..1b0e5421497 100644 --- a/javascript/extractor/src/com/semmle/js/parser/TypeScriptASTConverter.java +++ b/javascript/extractor/src/com/semmle/js/parser/TypeScriptASTConverter.java @@ -1202,7 +1202,9 @@ public class TypeScriptASTConverter { private Node convertExternalModuleReference(JsonObject node, SourceLocation loc) throws ParseError { - return new ExternalModuleReference(loc, convertChild(node, "expression")); + ExternalModuleReference moduleRef = new ExternalModuleReference(loc, convertChild(node, "expression")); + attachSymbolInformation(moduleRef, node); + return moduleRef; } private Node convertFalseKeyword(SourceLocation loc) { @@ -1366,7 +1368,9 @@ public class TypeScriptASTConverter { } } } - return new ImportDeclaration(loc, specifiers, src); + ImportDeclaration importDecl = new ImportDeclaration(loc, specifiers, src); + attachSymbolInformation(importDecl, node); + return importDecl; } private Node convertImportEqualsDeclaration(JsonObject node, SourceLocation loc) diff --git a/javascript/extractor/src/com/semmle/js/parser/TypeScriptParser.java b/javascript/extractor/src/com/semmle/js/parser/TypeScriptParser.java index 93a241b1e3d..0f43a2da0b4 100644 --- a/javascript/extractor/src/com/semmle/js/parser/TypeScriptParser.java +++ b/javascript/extractor/src/com/semmle/js/parser/TypeScriptParser.java @@ -1,12 +1,31 @@ package com.semmle.js.parser; -import ch.qos.logback.classic.Level; +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.ByteArrayOutputStream; +import java.io.Closeable; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.lang.ProcessBuilder.Redirect; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Map; + import com.google.gson.JsonArray; import com.google.gson.JsonElement; +import com.google.gson.JsonNull; import com.google.gson.JsonObject; import com.google.gson.JsonParseException; import com.google.gson.JsonParser; import com.google.gson.JsonPrimitive; +import com.semmle.js.extractor.DependencyInstallationResult; import com.semmle.js.extractor.EnvironmentVariables; import com.semmle.js.extractor.ExtractionMetrics; import com.semmle.js.parser.JSParser.Result; @@ -23,21 +42,8 @@ import com.semmle.util.logging.LogbackUtils; import com.semmle.util.process.AbstractProcessBuilder; import com.semmle.util.process.Builder; import com.semmle.util.process.Env; -import java.io.BufferedReader; -import java.io.BufferedWriter; -import java.io.ByteArrayOutputStream; -import java.io.Closeable; -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.OutputStream; -import java.io.OutputStreamWriter; -import java.lang.ProcessBuilder.Redirect; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; + +import ch.qos.logback.classic.Level; /** * The Java half of our wrapper for invoking the TypeScript parser. @@ -401,6 +407,21 @@ public class TypeScriptParser { checkResponseType(response, "ok"); } + /** + * Converts a map to an array of [key, value] pairs. + */ + private JsonArray mapToArray(Map map) { + JsonArray result = new JsonArray(); + map.forEach( + (key, path) -> { + JsonArray entry = new JsonArray(); + entry.add(key); + entry.add(path.toString()); + result.add(entry); + }); + return result; + } + /** * Opens a new project based on a tsconfig.json file. The compiler will analyze all files in the * project. @@ -409,10 +430,15 @@ public class TypeScriptParser { * *

    Only one project should be opened at once. */ - public ParsedProject openProject(File tsConfigFile) { + public ParsedProject openProject(File tsConfigFile, DependencyInstallationResult deps) { JsonObject request = new JsonObject(); request.add("command", new JsonPrimitive("open-project")); request.add("tsConfig", new JsonPrimitive(tsConfigFile.getPath())); + request.add("packageEntryPoints", mapToArray(deps.getPackageEntryPoints())); + request.add("packageJsonFiles", mapToArray(deps.getPackageJsonFiles())); + request.add("virtualSourceRoot", deps.getVirtualSourceRoot() == null + ? JsonNull.INSTANCE + : new JsonPrimitive(deps.getVirtualSourceRoot().toString())); JsonObject response = talkToParserWrapper(request); try { checkResponseType(response, "project-opened"); diff --git a/javascript/extractor/src/com/semmle/ts/ast/ExternalModuleReference.java b/javascript/extractor/src/com/semmle/ts/ast/ExternalModuleReference.java index 48b03ae5a16..022bf3b2aa7 100644 --- a/javascript/extractor/src/com/semmle/ts/ast/ExternalModuleReference.java +++ b/javascript/extractor/src/com/semmle/ts/ast/ExternalModuleReference.java @@ -4,8 +4,9 @@ import com.semmle.js.ast.Expression; import com.semmle.js.ast.SourceLocation; import com.semmle.js.ast.Visitor; -public class ExternalModuleReference extends Expression { +public class ExternalModuleReference extends Expression implements INodeWithSymbol { private final Expression expression; + private int symbol = -1; public ExternalModuleReference(SourceLocation loc, Expression expression) { super("ExternalModuleReference", loc); @@ -20,4 +21,14 @@ public class ExternalModuleReference extends Expression { public R accept(Visitor v, C c) { return v.visit(this, c); } + + @Override + public int getSymbol() { + return this.symbol; + } + + @Override + public void setSymbol(int symbol) { + this.symbol = symbol; + } } diff --git a/javascript/extractor/tests/shebang/output/trap/tst.html.trap b/javascript/extractor/tests/shebang/output/trap/tst.html.trap index 3f56959df24..fc0a476640a 100644 --- a/javascript/extractor/tests/shebang/output/trap/tst.html.trap +++ b/javascript/extractor/tests/shebang/output/trap/tst.html.trap @@ -14,7 +14,7 @@ toplevels(#20001,1) locations_default(#20002,#10000,3,17,3,17) hasLocation(#20001,#20002) #20003=* -jsParseErrors(#20003,#20001,"Error: Unexpected character '#' (U+0023)","#!/usr/bin/node +jsParseErrors(#20003,#20001,"Error: Unexpected token","#!/usr/bin/node ") #20004=@"loc,{#10000},4,1,4,1" locations_default(#20004,#10000,4,1,4,1) diff --git a/javascript/ql/src/Security/CWE-400/PrototypePollutionUtility.ql b/javascript/ql/src/Security/CWE-400/PrototypePollutionUtility.ql index 9e1e38662fc..376e2e16f56 100644 --- a/javascript/ql/src/Security/CWE-400/PrototypePollutionUtility.ql +++ b/javascript/ql/src/Security/CWE-400/PrototypePollutionUtility.ql @@ -15,6 +15,7 @@ import javascript import DataFlow import PathGraph import semmle.javascript.dataflow.InferredTypes +import semmle.javascript.dataflow.internal.FlowSteps /** * Gets a node that refers to an element of `array`, likely obtained @@ -47,26 +48,17 @@ abstract class EnumeratedPropName extends DataFlow::Node { */ abstract DataFlow::Node getSourceObject(); - /** - * Gets a local reference of the source object. - */ - SourceNode getASourceObjectRef() { - exists(SourceNode root, string path | - getSourceObject() = AccessPath::getAReferenceTo(root, path) and - result = AccessPath::getAReferenceTo(root, path) - ) - or - result = getSourceObject().getALocalSource() - } - /** * Gets a property read that accesses the corresponding property value in the source object. * * For example, gets `src[key]` in `for (var key in src) { src[key]; }`. */ - PropRead getASourceProp() { - result = getASourceObjectRef().getAPropertyRead() and - result.getPropertyNameExpr().flow().getImmediatePredecessor*() = this + SourceNode getASourceProp() { + exists(Node base, Node key | + dynamicPropReadStep(base, key, result) and + AccessPath::getAnAliasedSourceNode(getSourceObject()).flowsTo(base) and + key.getImmediatePredecessor*() = this + ) } } @@ -114,7 +106,7 @@ class EntriesEnumeratedPropName extends EnumeratedPropName { result = entries.getArgument(0) } - override PropRead getASourceProp() { + override SourceNode getASourceProp() { result = super.getASourceProp() or result = entry.getAPropertyRead("1") @@ -125,7 +117,7 @@ class EntriesEnumeratedPropName extends EnumeratedPropName { * Holds if the properties of `node` are enumerated locally. */ predicate arePropertiesEnumerated(DataFlow::SourceNode node) { - node = any(EnumeratedPropName name).getASourceObjectRef() + node = AccessPath::getAnAliasedSourceNode(any(EnumeratedPropName name).getSourceObject()) } /** @@ -145,6 +137,9 @@ class DynamicPropRead extends DataFlow::SourceNode, DataFlow::ValueNode { /** Gets the base of the dynamic read. */ DataFlow::Node getBase() { result = astNode.getBase().flow() } + /** Gets the node holding the name of the property. */ + DataFlow::Node getPropertyNameNode() { result = astNode.getIndex().flow() } + /** * Holds if the value of this read was assigned to earlier in the same basic block. * @@ -166,6 +161,77 @@ class DynamicPropRead extends DataFlow::SourceNode, DataFlow::ValueNode { } } +/** + * Holds if `output` is the result of `base[key]`, either directly or through + * one or more function calls, ignoring reads that can't access the prototype chain. + */ +predicate dynamicPropReadStep(Node base, Node key, SourceNode output) { + exists(DynamicPropRead read | + not read.hasDominatingAssignment() and + base = read.getBase() and + key = read.getPropertyNameNode() and + output = read + ) + or + // Summarize functions returning a dynamic property read of two parameters, such as `function getProp(obj, prop) { return obj[prop]; }`. + exists(CallNode call, Function callee, ParameterNode baseParam, ParameterNode keyParam, Node innerBase, Node innerKey, SourceNode innerOutput | + dynamicPropReadStep(innerBase, innerKey, innerOutput) and + baseParam.flowsTo(innerBase) and + keyParam.flowsTo(innerKey) and + innerOutput.flowsTo(callee.getAReturnedExpr().flow()) and + call.getACallee() = callee and + argumentPassingStep(call, base, callee, baseParam) and + argumentPassingStep(call, key, callee, keyParam) and + output = call + ) +} + +/** + * Holds if `node` may flow from an enumerated prop name, possibly + * into function calls (but not returns). + */ +predicate isEnumeratedPropName(Node node) { + node instanceof EnumeratedPropName + or + exists(Node pred | + isEnumeratedPropName(pred) + | + node = pred.getASuccessor() + or + argumentPassingStep(_, pred, _, node) + or + // Handle one level of callbacks + exists(FunctionNode function, ParameterNode callback, int i | + pred = callback.getAnInvocation().getArgument(i) and + argumentPassingStep(_, function, _, callback) and + node = function.getParameter(i) + ) + ) +} + +/** + * Holds if `node` may refer to `Object.prototype` obtained through dynamic property + * read of a property obtained through property enumeration. + */ +predicate isPotentiallyObjectPrototype(SourceNode node) { + exists(Node base, Node key | + dynamicPropReadStep(base, key, node) and + isEnumeratedPropName(key) and + + // Ignore cases where the properties of `base` are enumerated, to avoid FPs + // where the key came from that enumeration (and thus will not return Object.prototype). + // For example, `src[key]` in `for (let key in src) { ... src[key] ... }` will generally + // not return Object.prototype because `key` is an enumerable property of `src`. + not arePropertiesEnumerated(base.getALocalSource()) + ) + or + exists(Node use | + isPotentiallyObjectPrototype(use.getALocalSource()) + | + argumentPassingStep(_, use, _, node) + ) +} + /** * Holds if there is a dynamic property assignment of form `base[prop] = rhs` * which might act as the writing operation in a recursive merge function. @@ -183,7 +249,12 @@ predicate dynamicPropWrite(DataFlow::Node base, DataFlow::Node prop, DataFlow::N prop = index.getPropertyNameExpr().flow() and rhs = write.getRhs().flow() and not exists(prop.getStringValue()) and - not arePropertiesEnumerated(base.getALocalSource()) + not arePropertiesEnumerated(base.getALocalSource()) and + + // Prune writes that are unlikely to modify Object.prototype. + // This is mainly for performance, but may block certain results due to + // not tracking out of function returns and into callbacks. + isPotentiallyObjectPrototype(base.getALocalSource()) ) } diff --git a/javascript/ql/src/meta/analysis-quality/UnresolvableImports.ql b/javascript/ql/src/meta/analysis-quality/UnresolvableImports.ql new file mode 100644 index 00000000000..a2a35e8f11c --- /dev/null +++ b/javascript/ql/src/meta/analysis-quality/UnresolvableImports.ql @@ -0,0 +1,18 @@ +/** + * @name Unresolvable imports + * @description The number of imports that could not be resolved to a module. + * @kind metric + * @metricType project + * @metricAggregate sum + * @tags meta + * @id js/meta/unresolvable-imports + */ + +import javascript +import CallGraphQuality + +Import unresolvableImport() { + not exists(result.getImportedModule()) +} + +select projectRoot(), count(unresolvableImport()) diff --git a/javascript/ql/src/semmle/javascript/GlobalAccessPaths.qll b/javascript/ql/src/semmle/javascript/GlobalAccessPaths.qll index 35755fb6c8b..2a3d8f963a2 100644 --- a/javascript/ql/src/semmle/javascript/GlobalAccessPaths.qll +++ b/javascript/ql/src/semmle/javascript/GlobalAccessPaths.qll @@ -412,4 +412,17 @@ module AccessPath { isAssignedInUniqueFile(name) ) } + + /** + * Gets a `SourceNode` that refers to the same value or access path as the given node. + */ + pragma[inline] + DataFlow::SourceNode getAnAliasedSourceNode(DataFlow::Node node) { + exists(DataFlow::SourceNode root, string accessPath | + node = AccessPath::getAReferenceTo(root, accessPath) and + result = AccessPath::getAReferenceTo(root, accessPath) + ) + or + result = node.getALocalSource() + } } diff --git a/javascript/ql/src/semmle/javascript/Modules.qll b/javascript/ql/src/semmle/javascript/Modules.qll index c18637f866a..26b2331afcf 100644 --- a/javascript/ql/src/semmle/javascript/Modules.qll +++ b/javascript/ql/src/semmle/javascript/Modules.qll @@ -148,6 +148,16 @@ abstract class Import extends ASTNode { ) } + /** + * Gets the imported module, as determined by the TypeScript compiler, if any. + */ + private Module resolveFromTypeScriptSymbol() { + exists(CanonicalName symbol | + ast_node_symbol(this, symbol) and + ast_node_symbol(result, symbol) + ) + } + /** * Gets the module this import refers to. * @@ -162,7 +172,8 @@ abstract class Import extends ASTNode { else ( result = resolveAsProvidedModule() or result = resolveImportedPath() or - result = resolveFromTypeRoot() + result = resolveFromTypeRoot() or + result = resolveFromTypeScriptSymbol() ) } diff --git a/javascript/ql/src/semmle/javascript/NodeModuleResolutionImpl.qll b/javascript/ql/src/semmle/javascript/NodeModuleResolutionImpl.qll index 5d85d47e1d2..83023fabb6f 100644 --- a/javascript/ql/src/semmle/javascript/NodeModuleResolutionImpl.qll +++ b/javascript/ql/src/semmle/javascript/NodeModuleResolutionImpl.qll @@ -84,10 +84,16 @@ File tryExtensions(Folder dir, string basename, int priority) { File resolveMainModule(PackageJSON pkg, int priority) { if exists(MainModulePath::of(pkg)) then - exists(Container c | c = MainModulePath::of(pkg).resolve() | - result = c and priority = 0 + exists(PathExpr main | main = MainModulePath::of(pkg) | + result = main.resolve() and priority = 0 or - result = tryExtensions(c, "index", priority) + result = tryExtensions(main.resolve(), "index", priority) + or + not exists(main.resolve()) and + not exists(main.getExtension()) and + exists(int n | n = main.getNumComponent() | + result = tryExtensions(main.resolveUpTo(n-1), main.getComponent(n-1), priority) + ) ) else result = tryExtensions(pkg.getFile().getParentContainer(), "index", priority) } diff --git a/javascript/ql/src/semmle/javascript/Promises.qll b/javascript/ql/src/semmle/javascript/Promises.qll index d74aafc265c..cac97e54e09 100644 --- a/javascript/ql/src/semmle/javascript/Promises.qll +++ b/javascript/ql/src/semmle/javascript/Promises.qll @@ -1,9 +1,385 @@ /** - * Provides classes for modelling promise libraries. + * Provides classes for modelling promises and their data-flow. */ import javascript +/** + * A definition of a `Promise` object. + */ +abstract class PromiseDefinition extends DataFlow::SourceNode { + /** Gets the executor function of this promise object. */ + abstract DataFlow::FunctionNode getExecutor(); + + /** Gets the `resolve` parameter of the executor function. */ + DataFlow::ParameterNode getResolveParameter() { result = getExecutor().getParameter(0) } + + /** Gets the `reject` parameter of the executor function. */ + DataFlow::ParameterNode getRejectParameter() { result = getExecutor().getParameter(1) } + + /** Gets the `i`th callback handler installed by method `m`. */ + private DataFlow::FunctionNode getAHandler(string m, int i) { + result = getAMethodCall(m).getCallback(i) + } + + /** + * Gets a function that handles promise resolution, including both + * `then` handlers and `finally` handlers. + */ + DataFlow::FunctionNode getAResolveHandler() { + result = getAHandler("then", 0) or + result = getAFinallyHandler() + } + + /** + * Gets a function that handles promise rejection, including + * `then` handlers, `catch` handlers and `finally` handlers. + */ + DataFlow::FunctionNode getARejectHandler() { + result = getAHandler("then", 1) or + result = getACatchHandler() or + result = getAFinallyHandler() + } + + /** + * Gets a `catch` handler of this promise. + */ + DataFlow::FunctionNode getACatchHandler() { result = getAHandler("catch", 0) } + + /** + * Gets a `finally` handler of this promise. + */ + DataFlow::FunctionNode getAFinallyHandler() { result = getAHandler("finally", 0) } +} + +/** Holds if the `i`th callback handler is installed by method `m`. */ +private predicate hasHandler(DataFlow::InvokeNode promise, string m, int i) { + exists(promise.getAMethodCall(m).getCallback(i)) +} + +/** + * A call that looks like a Promise. + * + * For example, this could be the call `promise(f).then(function(v){...})` + */ +class PromiseCandidate extends DataFlow::InvokeNode { + PromiseCandidate() { + hasHandler(this, "then", [0 .. 1]) or + hasHandler(this, "catch", 0) or + hasHandler(this, "finally", 0) + } +} + +/** + * A promise object created by the standard ECMAScript 2015 `Promise` constructor. + */ +private class ES2015PromiseDefinition extends PromiseDefinition, DataFlow::NewNode { + ES2015PromiseDefinition() { this = DataFlow::globalVarRef("Promise").getAnInstantiation() } + + override DataFlow::FunctionNode getExecutor() { result = getCallback(0) } +} + +/** + * A promise that is created and resolved with one or more value. + */ +abstract class PromiseCreationCall extends DataFlow::CallNode { + /** + * Gets the value this promise is resolved with. + */ + abstract DataFlow::Node getValue(); +} + +/** + * A promise that is created using a `.resolve()` call. + */ +abstract class ResolvedPromiseDefinition extends PromiseCreationCall { } + +/** + * A resolved promise created by the standard ECMAScript 2015 `Promise.resolve` function. + */ +class ResolvedES2015PromiseDefinition extends ResolvedPromiseDefinition { + ResolvedES2015PromiseDefinition() { + this = DataFlow::globalVarRef("Promise").getAMemberCall("resolve") + } + + override DataFlow::Node getValue() { result = getArgument(0) } +} + +/** + * An aggregated promise produced either by `Promise.all` or `Promise.race`. + */ +class AggregateES2015PromiseDefinition extends PromiseCreationCall { + AggregateES2015PromiseDefinition() { + exists(string m | m = "all" or m = "race" | + this = DataFlow::globalVarRef("Promise").getAMemberCall(m) + ) + } + + override DataFlow::Node getValue() { + result = getArgument(0).getALocalSource().(DataFlow::ArrayCreationNode).getAnElement() + } +} + +/** + * This module defines how data-flow propagates into and out of a Promise. + * The data-flow is based on pseudo-properties rather than tainting the Promise object (which is what `PromiseTaintStep` does). + */ +private module PromiseFlow { + /** + * Gets the pseudo-field used to describe resolved values in a promise. + */ + string resolveField() { + result = "$PromiseResolveField$" + } + + /** + * Gets the pseudo-field used to describe rejected values in a promise. + */ + string rejectField() { + result = "$PromiseRejectField$" + } + + /** + * A flow step describing a promise definition. + * + * The resolved/rejected value is written to a pseudo-field on the promise. + */ + class PromiseDefitionStep extends DataFlow::AdditionalFlowStep { + PromiseDefinition promise; + PromiseDefitionStep() { + this = promise + } + + override predicate storeStep(DataFlow::Node pred, DataFlow::Node succ, string prop) { + prop = resolveField() and + pred = promise.getResolveParameter().getACall().getArgument(0) and + succ = this + or + prop = rejectField() and + ( + pred = promise.getRejectParameter().getACall().getArgument(0) or + pred = promise.getExecutor().getExceptionalReturn() + ) and + succ = this + } + + override predicate loadStoreStep(DataFlow::Node pred, DataFlow::Node succ, string prop) { + // Copy the value of a resolved promise to the value of this promise. + prop = resolveField() and + pred = promise.getResolveParameter().getACall().getArgument(0) and + succ = this + } + } + + /** + * A flow step describing the a Promise.resolve (and similar) call. + */ + class CreationStep extends DataFlow::AdditionalFlowStep { + PromiseCreationCall promise; + CreationStep() { + this = promise + } + + override predicate storeStep(DataFlow::Node pred, DataFlow::Node succ, string prop) { + prop = resolveField() and + pred = promise.getValue() and + succ = this + } + + override predicate loadStoreStep(DataFlow::Node pred, DataFlow::Node succ, string prop) { + // Copy the value of a resolved promise to the value of this promise. + prop = resolveField() and + pred = promise.getValue() and + succ = this + } + } + + + /** + * A load step loading the pseudo-field describing that the promise is rejected. + * The rejected value is thrown as a exception. + */ + class AwaitStep extends DataFlow::AdditionalFlowStep { + DataFlow::Node operand; + AwaitExpr await; + AwaitStep() { + this.getEnclosingExpr() = await and + operand.getEnclosingExpr() = await.getOperand() + } + + override predicate loadStep(DataFlow::Node pred, DataFlow::Node succ, string prop) { + prop = resolveField() and + succ = this and + pred = operand + or + prop = rejectField() and + succ = await.getExceptionTarget() and + pred = operand + } + } + + /** + * A flow step describing the data-flow related to the `.then` method of a promise. + */ + class ThenStep extends DataFlow::AdditionalFlowStep, DataFlow::MethodCallNode { + ThenStep() { + this.getMethodName() = "then" + } + + override predicate loadStep(DataFlow::Node pred, DataFlow::Node succ, string prop) { + prop = resolveField() and + pred = getReceiver() and + succ = getCallback(0).getParameter(0) + or + prop = rejectField() and + pred = getReceiver() and + succ = getCallback(1).getParameter(0) + } + + override predicate loadStoreStep(DataFlow::Node pred, DataFlow::Node succ, string prop) { + not exists(this.getArgument(1)) and + prop = rejectField() and + pred = getReceiver() and + succ = this + or + // read the value of a resolved/rejected promise that is returned + (prop = rejectField() or prop = resolveField()) and + pred = getCallback([0..1]).getAReturn() and + succ = this + } + + override predicate storeStep(DataFlow::Node pred, DataFlow::Node succ, string prop) { + prop = resolveField() and + pred = getCallback([0..1]).getAReturn() and + succ = this + or + prop = rejectField() and + pred = getCallback([0..1]).getExceptionalReturn() and + succ = this + } + } + + /** + * A flow step describing the data-flow related to the `.catch` method of a promise. + */ + class CatchStep extends DataFlow::AdditionalFlowStep, DataFlow::MethodCallNode { + CatchStep() { + this.getMethodName() = "catch" + } + + override predicate loadStep(DataFlow::Node pred, DataFlow::Node succ, string prop) { + prop = rejectField() and + pred = getReceiver() and + succ = getCallback(0).getParameter(0) + } + + override predicate loadStoreStep(DataFlow::Node pred, DataFlow::Node succ, string prop) { + prop = resolveField() and + pred = getReceiver().getALocalSource() and + succ = this + or + // read the value of a resolved/rejected promise that is returned + (prop = rejectField() or prop = resolveField()) and + pred = getCallback(0).getAReturn() and + succ = this + } + + override predicate storeStep(DataFlow::Node pred, DataFlow::Node succ, string prop) { + prop = rejectField() and + pred = getCallback(0).getExceptionalReturn() and + succ = this + or + prop = resolveField() and + pred = getCallback(0).getAReturn() and + succ = this + } + } + + /** + * A flow step describing the data-flow related to the `.finally` method of a promise. + */ + class FinallyStep extends DataFlow::AdditionalFlowStep, DataFlow::MethodCallNode { + FinallyStep() { + this.getMethodName() = "finally" + } + + override predicate loadStoreStep(DataFlow::Node pred, DataFlow::Node succ, string prop) { + (prop = resolveField() or prop = rejectField()) and + pred = getReceiver() and + succ = this + or + // read the value of a rejected promise that is returned + prop = rejectField() and + pred = getCallback(0).getAReturn() and + succ = this + } + + override predicate storeStep(DataFlow::Node pred, DataFlow::Node succ, string prop) { + prop = rejectField() and + pred = getCallback(0).getExceptionalReturn() and + succ = this + } + } +} + +/** + * Holds if taint propagates from `pred` to `succ` through promises. + */ +predicate promiseTaintStep(DataFlow::Node pred, DataFlow::Node succ) { + // from `x` to `new Promise((res, rej) => res(x))` + pred = succ.(PromiseDefinition).getResolveParameter().getACall().getArgument(0) + or + // from `x` to `Promise.resolve(x)` + pred = succ.(PromiseCreationCall).getValue() + or + exists(DataFlow::MethodCallNode thn | + thn.getMethodName() = "then" + | + // from `p` to `x` in `p.then(x => ...)` + pred = thn.getReceiver() and + succ = thn.getCallback(0).getParameter(0) + or + // from `v` to `p.then(x => return v)` + pred = thn.getCallback([0..1]).getAReturn() and + succ = thn + ) + or + exists(DataFlow::MethodCallNode catch | catch.getMethodName() = "catch" | + // from `p` to `p.catch(..)` + pred = catch.getReceiver() and + succ = catch + or + // from `v` to `p.catch(x => return v)` + pred = catch.getCallback(0).getAReturn() and + succ = catch + ) + or + // from `p` to `p.finally(..)` + exists(DataFlow::MethodCallNode finally | finally.getMethodName() = "finally" | + pred = finally.getReceiver() and + succ = finally + ) + or + // from `x` to `await x` + exists(AwaitExpr await | + pred.getEnclosingExpr() = await.getOperand() and + succ.getEnclosingExpr() = await + ) +} + +/** + * An additional taint step that involves promises. + */ +private class PromiseTaintStep extends TaintTracking::AdditionalTaintStep { + DataFlow::Node source; + + PromiseTaintStep() { promiseTaintStep(source, this) } + + override predicate step(DataFlow::Node pred, DataFlow::Node succ) { + pred = source and succ = this + } +} + /** * Provides classes for working with the `bluebird` library (http://bluebirdjs.com). */ diff --git a/javascript/ql/src/semmle/javascript/StandardLibrary.qll b/javascript/ql/src/semmle/javascript/StandardLibrary.qll index 03b15b012e0..0de4cce8e05 100644 --- a/javascript/ql/src/semmle/javascript/StandardLibrary.qll +++ b/javascript/ql/src/semmle/javascript/StandardLibrary.qll @@ -76,191 +76,6 @@ private class AnalyzedThisInArrayIterationFunction extends AnalyzedNode, DataFlo } } -/** - * A definition of a `Promise` object. - */ -abstract class PromiseDefinition extends DataFlow::SourceNode { - /** Gets the executor function of this promise object. */ - abstract DataFlow::FunctionNode getExecutor(); - - /** Gets the `resolve` parameter of the executor function. */ - DataFlow::ParameterNode getResolveParameter() { result = getExecutor().getParameter(0) } - - /** Gets the `reject` parameter of the executor function. */ - DataFlow::ParameterNode getRejectParameter() { result = getExecutor().getParameter(1) } - - /** Gets the `i`th callback handler installed by method `m`. */ - private DataFlow::FunctionNode getAHandler(string m, int i) { - result = getAMethodCall(m).getCallback(i) - } - - /** - * Gets a function that handles promise resolution, including both - * `then` handlers and `finally` handlers. - */ - DataFlow::FunctionNode getAResolveHandler() { - result = getAHandler("then", 0) or - result = getAFinallyHandler() - } - - /** - * Gets a function that handles promise rejection, including - * `then` handlers, `catch` handlers and `finally` handlers. - */ - DataFlow::FunctionNode getARejectHandler() { - result = getAHandler("then", 1) or - result = getACatchHandler() or - result = getAFinallyHandler() - } - - /** - * Gets a `catch` handler of this promise. - */ - DataFlow::FunctionNode getACatchHandler() { result = getAHandler("catch", 0) } - - /** - * Gets a `finally` handler of this promise. - */ - DataFlow::FunctionNode getAFinallyHandler() { result = getAHandler("finally", 0) } -} - -/** Holds if the `i`th callback handler is installed by method `m`. */ -private predicate hasHandler(DataFlow::InvokeNode promise, string m, int i) { - exists(promise.getAMethodCall(m).getCallback(i)) -} - -/** - * A call that looks like a Promise. - * - * For example, this could be the call `promise(f).then(function(v){...})` - */ -class PromiseCandidate extends DataFlow::InvokeNode { - PromiseCandidate() { - hasHandler(this, "then", [0 .. 1]) or - hasHandler(this, "catch", 0) or - hasHandler(this, "finally", 0) - } -} - -/** - * A promise object created by the standard ECMAScript 2015 `Promise` constructor. - */ -private class ES2015PromiseDefinition extends PromiseDefinition, DataFlow::NewNode { - ES2015PromiseDefinition() { this = DataFlow::globalVarRef("Promise").getAnInstantiation() } - - override DataFlow::FunctionNode getExecutor() { result = getCallback(0) } -} - -/** - * A promise that is created and resolved with one or more value. - */ -abstract class PromiseCreationCall extends DataFlow::CallNode { - /** - * Gets the value this promise is resolved with. - */ - abstract DataFlow::Node getValue(); -} - -/** - * A promise that is created using a `.resolve()` call. - */ -abstract class ResolvedPromiseDefinition extends PromiseCreationCall {} - -/** - * A resolved promise created by the standard ECMAScript 2015 `Promise.resolve` function. - */ -class ResolvedES2015PromiseDefinition extends ResolvedPromiseDefinition { - ResolvedES2015PromiseDefinition() { - this = DataFlow::globalVarRef("Promise").getAMemberCall("resolve") - } - - override DataFlow::Node getValue() { result = getArgument(0) } -} - -/** - * An aggregated promise produced either by `Promise.all` or `Promise.race`. - */ -class AggregateES2015PromiseDefinition extends PromiseCreationCall { - AggregateES2015PromiseDefinition() { - exists(string m | m = "all" or m = "race" | - this = DataFlow::globalVarRef("Promise").getAMemberCall(m) - ) - } - - override DataFlow::Node getValue() { - result = getArgument(0).getALocalSource().(DataFlow::ArrayCreationNode).getAnElement() - } -} - -/** - * A data flow edge from a promise reaction to the corresponding handler. - */ -private class PromiseFlowStep extends DataFlow::AdditionalFlowStep { - PromiseDefinition p; - - PromiseFlowStep() { this = p } - - override predicate step(DataFlow::Node pred, DataFlow::Node succ) { - pred = p.getResolveParameter().getACall().getArgument(0) and - succ = p.getAResolveHandler().getParameter(0) - or - pred = p.getRejectParameter().getACall().getArgument(0) and - succ = p.getARejectHandler().getParameter(0) - } -} - -/** - * A data flow edge from the exceptional return of the promise executor to the promise catch handler. - * This only adds an edge from the exceptional return of the promise executor to a `.catch()` handler. - */ -private class PromiseExceptionalStep extends DataFlow::AdditionalFlowStep { - PromiseDefinition promise; - PromiseExceptionalStep() { - promise = this - } - - override predicate step(DataFlow::Node pred, DataFlow::Node succ) { - pred = promise.getExecutor().getExceptionalReturn() and - succ = promise.getACatchHandler().getParameter(0) - } -} - -/** - * Holds if taint propagates from `pred` to `succ` through promises. - */ -predicate promiseTaintStep(DataFlow::Node pred, DataFlow::Node succ) { - // from `x` to `new Promise((res, rej) => res(x))` - pred = succ.(PromiseDefinition).getResolveParameter().getACall().getArgument(0) - or - // from `x` to `Promise.resolve(x)` - pred = succ.(PromiseCreationCall).getValue() - or - exists(DataFlow::MethodCallNode thn, DataFlow::FunctionNode cb | - thn.getMethodName() = "then" and cb = thn.getCallback(0) - | - // from `p` to `x` in `p.then(x => ...)` - pred = thn.getReceiver() and - succ = cb.getParameter(0) - or - // from `v` to `p.then(x => return v)` - pred = cb.getAReturn() and - succ = thn - ) -} - -/** - * An additional taint step that involves promises. - */ -private class PromiseTaintStep extends TaintTracking::AdditionalTaintStep { - DataFlow::Node source; - - PromiseTaintStep() { promiseTaintStep(source, this) } - - override predicate step(DataFlow::Node pred, DataFlow::Node succ) { - pred = source and succ = this - } -} - /** * A flow step propagating the exception thrown from a callback to a method whose name coincides * a built-in Array iteration method, such as `forEach` or `map`. @@ -298,9 +113,7 @@ class StringReplaceCall extends DataFlow::MethodCallNode { } /** Gets the regular expression passed as the first argument to `replace`, if any. */ - DataFlow::RegExpLiteralNode getRegExp() { - result.flowsTo(getArgument(0)) - } + DataFlow::RegExpLiteralNode getRegExp() { result.flowsTo(getArgument(0)) } /** Gets a string that is being replaced by this call. */ string getAReplacedString() { @@ -312,17 +125,13 @@ class StringReplaceCall extends DataFlow::MethodCallNode { * Gets the second argument of this call to `replace`, which is either a string * or a callback. */ - DataFlow::Node getRawReplacement() { - result = getArgument(1) - } + 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() - } + predicate isGlobal() { getRegExp().isGlobal() } /** * Holds if this call to `replace` replaces `old` with `new`. diff --git a/javascript/ql/src/semmle/javascript/StringConcatenation.qll b/javascript/ql/src/semmle/javascript/StringConcatenation.qll index 2f423202bcb..941e6f6b908 100644 --- a/javascript/ql/src/semmle/javascript/StringConcatenation.qll +++ b/javascript/ql/src/semmle/javascript/StringConcatenation.qll @@ -51,6 +51,24 @@ module StringConcatenation { call = Closure::moduleImport("goog.string.buildString").getACall() and result = call.getArgument(n) ) + or + exists(DataFlow::MethodCallNode call | + node = call and + call.getMethodName() = "concat" and + not ( + exists(DataFlow::ArrayCreationNode array | + array.flowsTo(call.getAnArgument()) or array.flowsTo(call.getReceiver()) + ) + or + DataFlow::reflectiveCallNode(_) = call + ) and + ( + n = 0 and + result = call.getReceiver() + or + result = call.getArgument(n - 1) + ) + ) } /** Gets an operand to the string concatenation defining `node`. */ diff --git a/javascript/ql/src/semmle/javascript/StringOps.qll b/javascript/ql/src/semmle/javascript/StringOps.qll index d916e8e469d..fac784cb49f 100644 --- a/javascript/ql/src/semmle/javascript/StringOps.qll +++ b/javascript/ql/src/semmle/javascript/StringOps.qll @@ -165,10 +165,10 @@ module StringOps { StartsWith_Substring() { astNode.hasOperands(call.asExpr(), substring.asExpr()) and - (call.getMethodName() = "substring" or call.getMethodName() = "substr") and + (call.getMethodName() = "substring" or call.getMethodName() = "substr" or call.getMethodName() = "slice") and call.getNumArgument() = 2 and ( - substring.getALocalSource().getAPropertyRead("length").flowsTo(call.getArgument(1)) + AccessPath::getAnAliasedSourceNode(substring).getAPropertyRead("length").flowsTo(call.getArgument(1)) or substring.getStringValue().length() = call.getArgument(1).asExpr().getIntValue() ) diff --git a/javascript/ql/src/semmle/javascript/dataflow/Configuration.qll b/javascript/ql/src/semmle/javascript/dataflow/Configuration.qll index 577e7382921..f281a8aa23e 100644 --- a/javascript/ql/src/semmle/javascript/dataflow/Configuration.qll +++ b/javascript/ql/src/semmle/javascript/dataflow/Configuration.qll @@ -223,6 +223,29 @@ abstract class Configuration extends string { predicate hasFlowPath(SourcePathNode source, SinkPathNode sink) { flowsTo(source, _, sink, _, this) } + + /** + * EXPERIMENTAL. This API may change in the future. + * + * Holds if `pred` should be stored in the object `succ` under the property `prop`. + */ + predicate isAdditionalStoreStep(DataFlow::Node pred, DataFlow::Node succ, string prop) { none() } + + /** + * EXPERIMENTAL. This API may change in the future. + * + * Holds if the property `prop` of the object `pred` should be loaded into `succ`. + */ + predicate isAdditionalLoadStep(DataFlow::Node pred, DataFlow::Node succ, string prop) { none() } + + /** + * EXPERIMENTAL. This API may change in the future. + * + * Holds if the property `prop` should be copied from the object `pred` to the object `succ`. + */ + predicate isAdditionalLoadStoreStep(DataFlow::Node pred, DataFlow::Node succ, string prop) { + none() + } } /** @@ -307,11 +330,8 @@ abstract class BarrierGuardNode extends DataFlow::Node { // 1) `nd` is a use of a refinement node that blocks its input variable exists(SsaRefinementNode ref, boolean outcome | nd = DataFlow::ssaDefinitionNode(ref) and - forex(SsaVariable input | input = ref.getAnInput() | - getEnclosingExpr() = ref.getGuard().getTest() and - outcome = ref.getGuard().(ConditionGuardNode).getOutcome() and - barrierGuardBlocksExpr(this, outcome, input.getAUse(), label) - ) + outcome = ref.getGuard().(ConditionGuardNode).getOutcome() and + ssaRefinementBlocks(outcome, ref, label) ) or // 2) `nd` is an instance of an access path `p`, and dominated by a barrier for `p` @@ -324,6 +344,18 @@ abstract class BarrierGuardNode extends DataFlow::Node { ) } + /** + * Holds if there exists an input variable of `ref` that blocks the label `label`. + * + * This predicate is outlined to give the optimizer a hint about the join ordering. + */ + private predicate ssaRefinementBlocks(boolean outcome, SsaRefinementNode ref, string label) { + getEnclosingExpr() = ref.getGuard().getTest() and + forex(SsaVariable input | input = ref.getAnInput() | + barrierGuardBlocksExpr(this, outcome, input.getAUse(), label) + ) + } + /** * Holds if this node blocks expression `e` provided it evaluates to `outcome`. * @@ -338,11 +370,13 @@ abstract class BarrierGuardNode extends DataFlow::Node { } /** - * Holds if data flow node `nd` acts as a barrier for data flow. - * - * `label` is bound to the blocked label, or the empty string if all labels should be blocked. - */ -private predicate barrierGuardBlocksExpr(BarrierGuardNode guard, boolean outcome, Expr test, string label) { + * Holds if data flow node `nd` acts as a barrier for data flow. + * + * `label` is bound to the blocked label, or the empty string if all labels should be blocked. + */ +private predicate barrierGuardBlocksExpr( + BarrierGuardNode guard, boolean outcome, Expr test, string label +) { guard.blocks(outcome, test) and label = "" or guard.blocks(outcome, test, label) @@ -353,23 +387,29 @@ private predicate barrierGuardBlocksExpr(BarrierGuardNode guard, boolean outcome } /** - * Holds if data flow node `nd` acts as a barrier for data flow due to aliasing through - * an access path. - * - * `label` is bound to the blocked label, or the empty string if all labels should be blocked. - */ + * Holds if data flow node `nd` acts as a barrier for data flow due to aliasing through + * an access path. + * + * `label` is bound to the blocked label, or the empty string if all labels should be blocked. + */ pragma[noinline] -private predicate barrierGuardBlocksAccessPath(BarrierGuardNode guard, boolean outcome, AccessPath ap, string label) { +private predicate barrierGuardBlocksAccessPath( + BarrierGuardNode guard, boolean outcome, AccessPath ap, string label +) { barrierGuardBlocksExpr(guard, outcome, ap.getAnInstance(), label) } /** - * Holds if `guard` should block flow along the edge `pred -> succ`. - * - * `label` is bound to the blocked label, or the empty string if all labels should be blocked. - */ -private predicate barrierGuardBlocksEdge(BarrierGuardNode guard, DataFlow::Node pred, DataFlow::Node succ, string label) { - exists(SsaVariable input, SsaPhiNode phi, BasicBlock bb, ConditionGuardNode cond, boolean outcome | + * Holds if `guard` should block flow along the edge `pred -> succ`. + * + * `label` is bound to the blocked label, or the empty string if all labels should be blocked. + */ +private predicate barrierGuardBlocksEdge( + BarrierGuardNode guard, DataFlow::Node pred, DataFlow::Node succ, string label +) { + exists( + SsaVariable input, SsaPhiNode phi, BasicBlock bb, ConditionGuardNode cond, boolean outcome + | pred = DataFlow::ssaDefinitionNode(input) and succ = DataFlow::ssaDefinitionNode(phi) and input = phi.getInputFromBlock(bb) and @@ -399,7 +439,9 @@ private predicate isBarrierEdge(Configuration cfg, DataFlow::Node pred, DataFlow * Holds if there is a labeled barrier edge `pred -> succ` in `cfg` either through an explicit barrier edge * or one implied by a barrier guard. */ -private predicate isLabeledBarrierEdge(Configuration cfg, DataFlow::Node pred, DataFlow::Node succ, DataFlow::FlowLabel label) { +private predicate isLabeledBarrierEdge( + Configuration cfg, DataFlow::Node pred, DataFlow::Node succ, DataFlow::FlowLabel label +) { cfg.isBarrierEdge(pred, succ, label) or exists(DataFlow::BarrierGuardNode guard | @@ -449,6 +491,30 @@ abstract class AdditionalFlowStep extends DataFlow::Node { ) { none() } + + /** + * EXPERIMENTAL. This API may change in the future. + * + * Holds if `pred` should be stored in the object `succ` under the property `prop`. + */ + cached + predicate storeStep(DataFlow::Node pred, DataFlow::Node succ, string prop) { none() } + + /** + * EXPERIMENTAL. This API may change in the future. + * + * Holds if the property `prop` of the object `pred` should be loaded into `succ`. + */ + cached + predicate loadStep(DataFlow::Node pred, DataFlow::Node succ, string prop) { none() } + + /** + * EXPERIMENTAL. This API may change in the future. + * + * Holds if the property `prop` should be copied from the object `pred` to the object `succ`. + */ + cached + predicate loadStoreStep(DataFlow::Node pred, DataFlow::Node succ, string prop) { none() } } /** @@ -551,6 +617,9 @@ private predicate exploratoryFlowStep( basicFlowStep(pred, succ, _, cfg) or basicStoreStep(pred, succ, _) or basicLoadStep(pred, succ, _) or + isAdditionalStoreStep(pred, succ, _, cfg) or + isAdditionalLoadStep(pred, succ, _, cfg) or + isAdditionalLoadStoreStep(pred, succ, _, cfg) or // the following two disjuncts taken together over-approximate flow through // higher-order calls callback(pred, succ) or @@ -712,6 +781,9 @@ private predicate storeStep( basicStoreStep(pred, succ, prop) and summary = PathSummary::level() or + isAdditionalStoreStep(pred, succ, prop, cfg) and + summary = PathSummary::level() + or exists(Function f, DataFlow::Node mid | // `f` stores its parameter `pred` in property `prop` of a value that flows back to the caller, // and `succ` is an invocation of `f` @@ -719,6 +791,10 @@ private predicate storeStep( ( returnedPropWrite(f, _, prop, mid) or + exists(DataFlow::SourceNode base | base.flowsToExpr(f.getAReturnedExpr()) | + isAdditionalStoreStep(mid, base, prop, cfg) + ) + or succ instanceof DataFlow::NewNode and receiverPropWrite(f, prop, mid) ) @@ -729,12 +805,16 @@ private predicate storeStep( * Holds if `f` may `read` property `prop` of parameter `parm`. */ private predicate parameterPropRead( - Function f, DataFlow::Node invk, DataFlow::Node arg, string prop, DataFlow::PropRead read, + Function f, DataFlow::Node invk, DataFlow::Node arg, string prop, DataFlow::Node read, DataFlow::Configuration cfg ) { exists(DataFlow::SourceNode parm | callInputStep(f, invk, arg, parm, cfg) and - read = parm.getAPropertyRead(prop) + ( + read = parm.getAPropertyRead(prop) + or + exists(DataFlow::Node use | parm.flowsTo(use) | isAdditionalLoadStep(use, read, prop, cfg)) + ) ) } @@ -756,6 +836,39 @@ private predicate reachesReturn( ) } +/** + * Holds if the property `prop` of the object `pred` should be loaded into `succ`. + */ +private predicate isAdditionalLoadStep( + DataFlow::Node pred, DataFlow::Node succ, string prop, DataFlow::Configuration cfg +) { + any(AdditionalFlowStep s).loadStep(pred, succ, prop) + or + cfg.isAdditionalLoadStep(pred, succ, prop) +} + +/** + * Holds if `pred` should be stored in the object `succ` under the property `prop`. + */ +private predicate isAdditionalStoreStep( + DataFlow::Node pred, DataFlow::Node succ, string prop, DataFlow::Configuration cfg +) { + any(AdditionalFlowStep s).storeStep(pred, succ, prop) + or + cfg.isAdditionalStoreStep(pred, succ, prop) +} + +/** + * Holds if the property `prop` should be copied from the object `pred` to the object `succ`. + */ +private predicate isAdditionalLoadStoreStep( + DataFlow::Node pred, DataFlow::Node succ, string prop, DataFlow::Configuration cfg +) { + any(AdditionalFlowStep s).loadStoreStep(pred, succ, prop) + or + cfg.isAdditionalLoadStoreStep(pred, succ, prop) +} + /** * Holds if property `prop` of `pred` may flow into `succ` along a path summarized by * `summary`. @@ -767,7 +880,10 @@ private predicate loadStep( basicLoadStep(pred, succ, prop) and summary = PathSummary::level() or - exists(Function f, DataFlow::PropRead read | + isAdditionalLoadStep(pred, succ, prop, cfg) and + summary = PathSummary::level() + or + exists(Function f, DataFlow::Node read | parameterPropRead(f, succ, pred, prop, read, cfg) and reachesReturn(f, read, cfg, summary) ) @@ -788,7 +904,12 @@ private predicate reachableFromStoreBase( or exists(DataFlow::Node mid, PathSummary oldSummary, PathSummary newSummary | reachableFromStoreBase(prop, rhs, mid, cfg, oldSummary) and - flowStep(mid, cfg, nd, newSummary) and + ( + flowStep(mid, cfg, nd, newSummary) + or + isAdditionalLoadStoreStep(mid, nd, prop, cfg) and + newSummary = PathSummary::level() + ) and summary = oldSummary.appendValuePreserving(newSummary) ) } @@ -996,19 +1117,19 @@ private predicate onPath(DataFlow::Node nd, DataFlow::Configuration cfg, PathSum * Holds if there is a configuration that has at least one source and at least one sink. */ pragma[noinline] -private predicate isLive() { exists(DataFlow::Configuration cfg | isSource(_, cfg, _) and isSink(_, cfg, _)) } +private predicate isLive() { + exists(DataFlow::Configuration cfg | isSource(_, cfg, _) and isSink(_, cfg, _)) +} /** * A data flow node on an inter-procedural path from a source. */ private newtype TPathNode = - MkSourceNode(DataFlow::Node nd, DataFlow::Configuration cfg) { isSourceNode(nd, cfg, _) } - or + MkSourceNode(DataFlow::Node nd, DataFlow::Configuration cfg) { isSourceNode(nd, cfg, _) } or MkMidNode(DataFlow::Node nd, DataFlow::Configuration cfg, PathSummary summary) { isLive() and onPath(nd, cfg, summary) - } - or + } or MkSinkNode(DataFlow::Node nd, DataFlow::Configuration cfg) { isSinkNode(nd, cfg, _) } /** @@ -1069,9 +1190,7 @@ class PathNode extends TPathNode { } /** Holds if this path node wraps data-flow node `nd` and configuration `c`. */ - predicate wraps(DataFlow::Node n, DataFlow::Configuration c) { - nd = n and cfg = c - } + predicate wraps(DataFlow::Node n, DataFlow::Configuration c) { nd = n and cfg = c } /** Gets the underlying configuration of this path node. */ DataFlow::Configuration getConfiguration() { result = cfg } @@ -1080,9 +1199,7 @@ class PathNode extends TPathNode { DataFlow::Node getNode() { result = nd } /** Gets a successor node of this path node. */ - final PathNode getASuccessor() { - result = getASuccessor(this) - } + final PathNode getASuccessor() { result = getASuccessor(this) } /** Gets a textual representation of this path node. */ string toString() { result = nd.toString() } @@ -1126,7 +1243,10 @@ private MidPathNode finalMidNode(SinkPathNode snk) { * This helper predicate exists to clarify the intended join order in `getASuccessor` below. */ pragma[noinline] -private predicate midNodeStep(PathNode nd, DataFlow::Node predNd, Configuration cfg, PathSummary summary, DataFlow::Node succNd, PathSummary newSummary) { +private predicate midNodeStep( + PathNode nd, DataFlow::Node predNd, Configuration cfg, PathSummary summary, DataFlow::Node succNd, + PathSummary newSummary +) { nd = MkMidNode(predNd, cfg, summary) and flowStep(predNd, id(cfg), succNd, newSummary) } @@ -1139,7 +1259,10 @@ private PathNode getASuccessor(PathNode nd) { result = initialMidNode(nd) or // mid node to mid node - exists(Configuration cfg, DataFlow::Node predNd, PathSummary summary, DataFlow::Node succNd, PathSummary newSummary | + exists( + Configuration cfg, DataFlow::Node predNd, PathSummary summary, DataFlow::Node succNd, + PathSummary newSummary + | midNodeStep(nd, predNd, cfg, summary, succNd, newSummary) and result = MkMidNode(succNd, id(cfg), summary.append(newSummary)) ) @@ -1210,9 +1333,7 @@ class SinkPathNode extends PathNode, MkSinkNode { */ module PathGraph { /** Holds if `nd` is a node in the graph of data flow path explanations. */ - query predicate nodes(PathNode nd) { - not nd.(MidPathNode).isHidden() - } + query predicate nodes(PathNode nd) { not nd.(MidPathNode).isHidden() } /** * Gets a node to which data from `nd` may flow in one step, skipping over hidden nodes. @@ -1220,7 +1341,8 @@ module PathGraph { private PathNode succ0(PathNode nd) { result = getASuccessorIfHidden*(nd.getASuccessor()) and // skip hidden nodes - nodes(nd) and nodes(result) + nodes(nd) and + nodes(result) } /** @@ -1260,27 +1382,21 @@ module PathGraph { } } - +/** + * Gets an operand of the given `&&` operator. + * + * We use this to construct the transitive closure over a relation + * that does not include all of `BinaryExpr.getAnOperand`. + */ +private Expr getALogicalAndOperand(LogAndExpr e) { result = e.getAnOperand() } /** - * Gets an operand of the given `&&` operator. - * - * We use this to construct the transitive closure over a relation - * that does not include all of `BinaryExpr.getAnOperand`. - */ -private Expr getALogicalAndOperand(LogAndExpr e) { - result = e.getAnOperand() -} - -/** - * Gets an operand of the given `||` operator. - * - * We use this to construct the transitive closure over a relation - * that does not include all of `BinaryExpr.getAnOperand`. - */ -private Expr getALogicalOrOperand(LogOrExpr e) { - result = e.getAnOperand() -} + * Gets an operand of the given `||` operator. + * + * We use this to construct the transitive closure over a relation + * that does not include all of `BinaryExpr.getAnOperand`. + */ +private Expr getALogicalOrOperand(LogOrExpr e) { result = e.getAnOperand() } /** * A `BarrierGuardNode` that controls which data flow @@ -1295,8 +1411,8 @@ abstract class AdditionalBarrierGuardNode extends BarrierGuardNode { } /** - * A function that returns the result of a barrier guard. - */ + * A function that returns the result of a barrier guard. + */ private class BarrierGuardFunction extends Function { DataFlow::ParameterNode sanitizedParameter; BarrierGuardNode guard; @@ -1329,8 +1445,8 @@ private class BarrierGuardFunction extends Function { } /** - * Holds if this function sanitizes argument `e` of call `call`, provided the call evaluates to `outcome`. - */ + * Holds if this function sanitizes argument `e` of call `call`, provided the call evaluates to `outcome`. + */ predicate isBarrierCall(DataFlow::CallNode call, Expr e, boolean outcome, string lbl) { exists(DataFlow::Node arg | arg.asExpr() = e and @@ -1343,22 +1459,20 @@ private class BarrierGuardFunction extends Function { } /** - * Holds if this function applies to the flow in `cfg`. - */ + * Holds if this function applies to the flow in `cfg`. + */ predicate appliesTo(Configuration cfg) { cfg.isBarrierGuard(guard) } } /** - * A call that sanitizes an argument. - */ + * A call that sanitizes an argument. + */ private class AdditionalBarrierGuardCall extends AdditionalBarrierGuardNode, DataFlow::CallNode { BarrierGuardFunction f; AdditionalBarrierGuardCall() { f.isBarrierCall(this, _, _, _) } - override predicate blocks(boolean outcome, Expr e) { - f.isBarrierCall(this, e, outcome, "") - } + override predicate blocks(boolean outcome, Expr e) { f.isBarrierCall(this, e, outcome, "") } predicate internalBlocksLabel(boolean outcome, Expr e, DataFlow::FlowLabel label) { f.isBarrierCall(this, e, outcome, label) diff --git a/javascript/ql/src/semmle/javascript/dataflow/DataFlow.qll b/javascript/ql/src/semmle/javascript/dataflow/DataFlow.qll index b8589c21393..5f97f0845bb 100644 --- a/javascript/ql/src/semmle/javascript/dataflow/DataFlow.qll +++ b/javascript/ql/src/semmle/javascript/dataflow/DataFlow.qll @@ -20,6 +20,7 @@ import javascript private import internal.CallGraphs +private import internal.FlowSteps as FlowSteps module DataFlow { cached @@ -535,6 +536,13 @@ module DataFlow { */ pragma[noinline] predicate accesses(Node base, string p) { getBase() = base and getPropertyName() = p } + + /** + * Holds if this data flow node reads or writes a private field in a class. + */ + predicate isPrivateField() { + getPropertyName().charAt(0) = "#" and getPropertyNameExpr() instanceof Label + } } /** @@ -1470,6 +1478,8 @@ module DataFlow { ) } + predicate argumentPassingStep = FlowSteps::argumentPassing/4; + /** * Gets the data flow node representing the source of definition `def`, taking * flow through IIFE calls into account. diff --git a/javascript/ql/src/semmle/javascript/frameworks/Koa.qll b/javascript/ql/src/semmle/javascript/frameworks/Koa.qll index ca38db8429e..ab306964ebe 100644 --- a/javascript/ql/src/semmle/javascript/frameworks/Koa.qll +++ b/javascript/ql/src/semmle/javascript/frameworks/Koa.qll @@ -9,9 +9,9 @@ module Koa { /** * An expression that creates a new Koa application. */ - class AppDefinition extends HTTP::Servers::StandardServerDefinition, NewExpr { + class AppDefinition extends HTTP::Servers::StandardServerDefinition, InvokeExpr { AppDefinition() { - // `app = new Koa()` + // `app = new Koa()` / `app = Koa()` this = DataFlow::moduleImport("koa").getAnInvocation().asExpr() } } @@ -115,6 +115,26 @@ module Koa { override RouteHandler getRouteHandler() { result = ctx.getRouteHandler() } } + /** + * A Koa request source, accessed through the a request property of a + * generator route handler (deprecated in Koa 3). + */ + private class GeneratorRequestSource extends HTTP::Servers::RequestSource { + RouteHandler rh; + + GeneratorRequestSource() { + exists(DataFlow::FunctionNode fun | fun = rh | + fun.getFunction().isGenerator() and + fun.getReceiver().getAPropertyRead("request") = this + ) + } + + /** + * Gets the route handler that provides this response. + */ + override RouteHandler getRouteHandler() { result = rh } + } + /** * A Koa response source, that is, an access to the `response` property * of a context object. diff --git a/javascript/ql/src/semmle/javascript/frameworks/Logging.qll b/javascript/ql/src/semmle/javascript/frameworks/Logging.qll index d8646575c4a..8fda6f8e1c1 100644 --- a/javascript/ql/src/semmle/javascript/frameworks/Logging.qll +++ b/javascript/ql/src/semmle/javascript/frameworks/Logging.qll @@ -17,7 +17,7 @@ abstract class LoggerCall extends DataFlow::CallNode { /** * Gets a log level name that is used in RFC5424, `npm`, `console`. */ -private string getAStandardLoggerMethodName() { +string getAStandardLoggerMethodName() { result = "crit" or result = "debug" or result = "error" or diff --git a/javascript/ql/src/semmle/javascript/frameworks/NodeJSLib.qll b/javascript/ql/src/semmle/javascript/frameworks/NodeJSLib.qll index 1262f212747..ddc6756cc82 100644 --- a/javascript/ql/src/semmle/javascript/frameworks/NodeJSLib.qll +++ b/javascript/ql/src/semmle/javascript/frameworks/NodeJSLib.qll @@ -906,9 +906,7 @@ module NodeJSLib { * An instance of an EventEmitter that is imported through the 'events' module. */ private class ImportedNodeJSEventEmitter extends NodeJSEventEmitter { - ImportedNodeJSEventEmitter() { - this = getAnEventEmitterImport().getAnInstantiation() - } + ImportedNodeJSEventEmitter() { this = getAnEventEmitterImport().getAnInstantiation() } } /** @@ -922,20 +920,33 @@ module NodeJSLib { } /** - * An instantiation of a class that extends EventEmitter. - * + * An instantiation of a class that extends EventEmitter. + * * By extending `NodeJSEventEmitter' we get data-flow on the events passing through this EventEmitter. */ - private class CustomEventEmitter extends NodeJSEventEmitter { + class CustomEventEmitter extends NodeJSEventEmitter { + EventEmitterSubClass clazz; CustomEventEmitter() { - this = any(EventEmitterSubClass clazz).getAClassReference().getAnInstantiation() + if exists(clazz.getAClassReference().getAnInstantiation()) then + this = clazz.getAClassReference().getAnInstantiation() + else + // In case there are no explicit instantiations of the clazz, then we still want to track data flow between `this` nodes. + // This cannot produce false flow as the `.ref()` method below is always used when creating event-registrations/event-dispatches. + this = clazz + } + + override DataFlow::SourceNode ref() { + result = NodeJSEventEmitter.super.ref() and not this = clazz + or + result = clazz.getAReceiverNode() } } /** * A registration of an event handler on a NodeJS EventEmitter instance. */ - private class NodeJSEventRegistration extends EventRegistration::DefaultEventRegistration, DataFlow::MethodCallNode { + private class NodeJSEventRegistration extends EventRegistration::DefaultEventRegistration, + DataFlow::MethodCallNode { override NodeJSEventEmitter emitter; NodeJSEventRegistration() { this = emitter.ref().getAMethodCall(EventEmitter::on()) } @@ -944,9 +955,94 @@ module NodeJSLib { /** * A dispatch of an event on a NodeJS EventEmitter instance. */ - private class NodeJSEventDispatch extends EventDispatch::DefaultEventDispatch, DataFlow::MethodCallNode { + private class NodeJSEventDispatch extends EventDispatch::DefaultEventDispatch, + DataFlow::MethodCallNode { override NodeJSEventEmitter emitter; NodeJSEventDispatch() { this = emitter.ref().getAMethodCall("emit") } } + + /** + * An instance of net.createServer(), which creates a new TCP/IPC server. + */ + private class NodeJSNetServer extends DataFlow::SourceNode { + NodeJSNetServer() { this = DataFlow::moduleMember("net", "createServer").getAnInvocation() } + + private DataFlow::SourceNode ref(DataFlow::TypeTracker t) { + t.start() and result = this + or + exists(DataFlow::TypeTracker t2 | result = ref(t2).track(t2, t)) + } + + /** + * Gets a reference to this server. + */ + DataFlow::SourceNode ref() { result = ref(DataFlow::TypeTracker::end()) } + } + + /** + * A connection opened on a NodeJS net server. + */ + private class NodeJSNetServerConnection extends EventEmitter::Range { + NodeJSNetServer server; + + NodeJSNetServerConnection() { + exists(DataFlow::MethodCallNode call | + call = server.ref().getAMethodCall("on") and + call.getArgument(0).mayHaveStringValue("connection") + | + this = call.getCallback(1).getParameter(0) + ) + } + + DataFlow::SourceNode ref() { result = EventEmitter::trackEventEmitter(this) } + } + + /** + * A registration of an event handler on a NodeJS net server instance. + */ + private class NodeJSNetServerRegistration extends EventRegistration::DefaultEventRegistration, + DataFlow::MethodCallNode { + override NodeJSNetServerConnection emitter; + + NodeJSNetServerRegistration() { this = emitter.ref().getAMethodCall(EventEmitter::on()) } + } + + /** + * A data flow node representing data received from a client to a NodeJS net server, viewed as remote user input. + */ + private class NodeJSNetServerItemAsRemoteFlow extends RemoteFlowSource { + NodeJSNetServerRegistration reg; + + NodeJSNetServerItemAsRemoteFlow() { this = reg.getReceivedItem(_) } + + override string getSourceType() { result = "NodeJS server" } + } + + /** + * An instantiation of the `respjs` library, which is an EventEmitter. + */ + private class RespJS extends NodeJSEventEmitter { + RespJS() { + this = DataFlow::moduleImport("respjs").getAnInstantiation() + } + } + + /** + * A event dispatch that serializes the input data and emits the result on the "data" channel. + */ + private class RespWrite extends EventDispatch::DefaultEventDispatch, + DataFlow::MethodCallNode { + override RespJS emitter; + + RespWrite() { this = emitter.ref().getAMethodCall("write") } + + override string getChannel() { + result = "data" + } + + override DataFlow::Node getSentItem(int i) { + i = 0 and result = this.getArgument(i) + } + } } diff --git a/javascript/ql/src/semmle/javascript/frameworks/React.qll b/javascript/ql/src/semmle/javascript/frameworks/React.qll index e94f3c6bacd..a8c1793a5cc 100644 --- a/javascript/ql/src/semmle/javascript/frameworks/React.qll +++ b/javascript/ql/src/semmle/javascript/frameworks/React.qll @@ -38,10 +38,15 @@ abstract class ReactComponent extends ASTNode { abstract AbstractValue getAbstractComponent(); /** - * Gets the value that instantiates this component when invoked. + * Gets the function that instantiates this component when invoked. */ abstract DataFlow::SourceNode getComponentCreatorSource(); + /** + * Gets a reference to the function that instantiates this component when invoked. + */ + abstract DataFlow::SourceNode getAComponentCreatorReference(); + /** * Gets a reference to this component. */ @@ -181,7 +186,7 @@ abstract class ReactComponent extends ASTNode { * constructor of this component. */ DataFlow::SourceNode getACandidatePropsSource() { - result.flowsTo(getComponentCreatorSource().getAnInvocation().getArgument(0)) + result.flowsTo(getAComponentCreatorReference().getAnInvocation().getArgument(0)) or result = getADefaultPropsSource() or @@ -269,6 +274,19 @@ class FunctionalComponent extends ReactComponent, Function { override AbstractValue getAbstractComponent() { result = AbstractInstance::of(this) } + private DataFlow::SourceNode getAComponentCreatorReference(DataFlow::TypeTracker t) { + t.start() and + result = DataFlow::valueNode(this) + or + exists(DataFlow::TypeTracker t2 | + result = getAComponentCreatorReference(t2).track(t2, t) + ) + } + + override DataFlow::SourceNode getAComponentCreatorReference() { + result = getAComponentCreatorReference(DataFlow::TypeTracker::end()) + } + override DataFlow::SourceNode getComponentCreatorSource() { result = DataFlow::valueNode(this) } override DataFlow::SourceNode getADefaultPropsSource() { @@ -302,6 +320,10 @@ abstract private class SharedReactPreactClassComponent extends ReactComponent, C override AbstractValue getAbstractComponent() { result = AbstractInstance::of(this) } + override DataFlow::SourceNode getAComponentCreatorReference() { + result = DataFlow::valueNode(this).(DataFlow::ClassNode).getAClassReference() + } + override DataFlow::SourceNode getComponentCreatorSource() { result = DataFlow::valueNode(this) } override DataFlow::SourceNode getACandidateStateSource() { @@ -420,6 +442,19 @@ class ES5Component extends ReactComponent, ObjectExpr { override AbstractValue getAbstractComponent() { result = TAbstractObjectLiteral(this) } + private DataFlow::SourceNode getAComponentCreatorReference(DataFlow::TypeTracker t) { + t.start() and + result = create + or + exists(DataFlow::TypeTracker t2 | + result = getAComponentCreatorReference(t2).track(t2, t) + ) + } + + override DataFlow::SourceNode getAComponentCreatorReference() { + result = getAComponentCreatorReference(DataFlow::TypeTracker::end()) + } + override DataFlow::SourceNode getComponentCreatorSource() { result = create } override DataFlow::SourceNode getACandidateStateSource() { @@ -517,7 +552,7 @@ private class AnalyzedThisInBoundCallback extends AnalyzedNode, DataFlow::ThisNo private class ReactJSXElement extends JSXElement { ReactComponent component; - ReactJSXElement() { component.getComponentCreatorSource().flowsToExpr(getNameExpr()) } + ReactJSXElement() { component.getAComponentCreatorReference().flowsToExpr(getNameExpr()) } /** * Gets the component this element instantiates. diff --git a/javascript/ql/src/semmle/javascript/security/dataflow/ClientSideUrlRedirectCustomizations.qll b/javascript/ql/src/semmle/javascript/security/dataflow/ClientSideUrlRedirectCustomizations.qll index 02326bcdb92..f8d5c798e1e 100644 --- a/javascript/ql/src/semmle/javascript/security/dataflow/ClientSideUrlRedirectCustomizations.qll +++ b/javascript/ql/src/semmle/javascript/security/dataflow/ClientSideUrlRedirectCustomizations.qll @@ -55,7 +55,7 @@ module ClientSideUrlRedirect { // exclude `location.href.split('?')[0]`, which can never refer to the query string not exists(PropAccess pacc | mce = pacc.getBase() | pacc.getPropertyName() = "0") or - (methodName = "substring" or methodName = "substr") and + (methodName = "substring" or methodName = "substr" or methodName = "slice") and // exclude `location.href.substring(0, ...)` and similar, which can // never refer to the query string not mce.getArgument(0).(NumberLiteral).getIntValue() = 0 diff --git a/javascript/ql/src/semmle/javascript/security/dataflow/DomBasedXss.qll b/javascript/ql/src/semmle/javascript/security/dataflow/DomBasedXss.qll index 24634eec94c..deca4bd3db4 100644 --- a/javascript/ql/src/semmle/javascript/security/dataflow/DomBasedXss.qll +++ b/javascript/ql/src/semmle/javascript/security/dataflow/DomBasedXss.qll @@ -21,15 +21,6 @@ module DomBasedXss { override predicate isSanitizer(DataFlow::Node node) { super.isSanitizer(node) or - exists(PropAccess pacc | pacc = node.asExpr() | - isSafeLocationProperty(pacc) - or - // `$(location.hash)` is a fairly common and safe idiom - // (because `location.hash` always starts with `#`), - // so we mark `hash` as safe for the purposes of this query - pacc.getPropertyName() = "hash" - ) - or node instanceof Sanitizer } } diff --git a/javascript/ql/src/semmle/javascript/security/dataflow/ExceptionXss.qll b/javascript/ql/src/semmle/javascript/security/dataflow/ExceptionXss.qll index c74b48f1e2e..c64ecfd361d 100644 --- a/javascript/ql/src/semmle/javascript/security/dataflow/ExceptionXss.qll +++ b/javascript/ql/src/semmle/javascript/security/dataflow/ExceptionXss.qll @@ -12,15 +12,33 @@ module ExceptionXss { import Xss as Xss private import semmle.javascript.dataflow.InferredTypes + /** + * Gets the name of a method that does not leak taint from its arguments if an exception is thrown by the method. + */ + private string getAnUnlikelyToThrowMethodName() { + result = "getElementById" or // document.getElementById + result = "indexOf" or // String.prototype.indexOf + result = "assign" or // Object.assign + result = "pick" or // _.pick + result = getAStandardLoggerMethodName() or // log.info etc. + result = "val" or // $.val + result = "parse" or // JSON.parse + result = "stringify" or // JSON.stringify + result = "test" or // RegExp.prototype.test + result = "setItem" or // localStorage.setItem + result = "existsSync" or + // the "fs" methods are a mix of "this is safe" and "you have bigger problems". + exists(ExternalMemberDecl decl | decl.hasQualifiedName("fs", result)) or + // Array methods are generally exception safe. + exists(ExternalMemberDecl decl | decl.hasQualifiedName("Array", result)) + } + /** * Holds if `node` is unlikely to cause an exception containing sensitive information to be thrown. */ private predicate isUnlikelyToThrowSensitiveInformation(DataFlow::Node node) { - node = any(DataFlow::CallNode call | call.getCalleeName() = "getElementById").getAnArgument() - or - node = any(DataFlow::CallNode call | call.getCalleeName() = "indexOf").getAnArgument() - or - node = any(DataFlow::CallNode call | call.getCalleeName() = "stringify").getAnArgument() + node = any(DataFlow::CallNode call | call.getCalleeName() = getAnUnlikelyToThrowMethodName()) + .getAnArgument() or node = DataFlow::globalVarRef("console").getAMemberCall(_).getAnArgument() } @@ -38,6 +56,7 @@ module ExceptionXss { */ predicate canThrowSensitiveInformation(DataFlow::Node node) { not isUnlikelyToThrowSensitiveInformation(node) and + not node instanceof Xss::Shared::Sink and // removes duplicates from js/xss. ( // in the case of reflective calls the below ensures that both InvokeNodes have no known callee. forex(DataFlow::InvokeNode call | call.getAnArgument() = node | not exists(call.getACallee())) @@ -79,15 +98,15 @@ module ExceptionXss { } /** - * Get the parameter in the callback that contains an error. + * Gets the parameter in the callback that contains an error. * In the current implementation this is always the first parameter. */ DataFlow::Node getErrorParam() { result = errorParameter } } /** - * Gets the error parameter for a callback that is supplied to the same call as `pred` is an argument to. - * For example: `outerCall(foo, , bar, (, val) => { ... })`. + * Gets the error parameter for a callback that is supplied to the same call as `pred` is an argument to. + * For example: `outerCall(foo, , bar, (, val) => { ... })`. */ DataFlow::Node getCallbackErrorParam(DataFlow::Node pred) { exists(DataFlow::CallNode call, Callback callback | @@ -101,8 +120,8 @@ module ExceptionXss { /** * Gets the data-flow node to which any exceptions thrown by * this expression will propagate. - * This predicate adds, on top of `Expr::getExceptionTarget`, exceptions - * propagated by callbacks. + * This predicate adds, on top of `Expr::getExceptionTarget`, exceptions + * propagated by callbacks. */ private DataFlow::Node getExceptionTarget(DataFlow::Node pred) { result = pred.asExpr().getExceptionTarget() diff --git a/javascript/ql/src/semmle/javascript/security/dataflow/TaintedPath.qll b/javascript/ql/src/semmle/javascript/security/dataflow/TaintedPath.qll index b9a3b234db9..de0fdbc9a71 100644 --- a/javascript/ql/src/semmle/javascript/security/dataflow/TaintedPath.qll +++ b/javascript/ql/src/semmle/javascript/security/dataflow/TaintedPath.qll @@ -67,6 +67,40 @@ module TaintedPath { read.getPropertyName() != "length" and srclabel = dstlabel ) + or + // string method calls of interest + exists(DataFlow::MethodCallNode mcn, string name | + srclabel = dstlabel and dst = mcn and mcn.calls(src, name) + | + exists(string substringMethodName | + substringMethodName = "substr" or + substringMethodName = "substring" or + substringMethodName = "slice" + | + name = substringMethodName and + // to avoid very dynamic transformations, require at least one fixed index + exists(mcn.getAnArgument().asExpr().getIntValue()) + ) + or + exists(string argumentlessMethodName | + argumentlessMethodName = "toLocaleLowerCase" or + argumentlessMethodName = "toLocaleUpperCase" or + argumentlessMethodName = "toLowerCase" or + argumentlessMethodName = "toUpperCase" or + argumentlessMethodName = "trim" or + argumentlessMethodName = "trimLeft" or + argumentlessMethodName = "trimRight" + | + name = argumentlessMethodName + ) + or + name = "split" and + not exists(DataFlow::Node splitBy | splitBy = mcn.getArgument(0) | + splitBy.mayHaveStringValue("/") or + any(DataFlow::RegExpLiteralNode reg | reg.getRoot().getAMatchedString() = "/") + .flowsTo(splitBy) + ) + ) } /** diff --git a/javascript/ql/src/semmle/javascript/security/dataflow/Xss.qll b/javascript/ql/src/semmle/javascript/security/dataflow/Xss.qll index 71b2a463f7a..badcf326f2c 100644 --- a/javascript/ql/src/semmle/javascript/security/dataflow/Xss.qll +++ b/javascript/ql/src/semmle/javascript/security/dataflow/Xss.qll @@ -259,6 +259,24 @@ module DomBasedXss { } } + /** + * A property read from a safe property is considered a sanitizer. + */ + class SafePropertyReadSanitizer extends Sanitizer, DataFlow::Node { + SafePropertyReadSanitizer() { + exists(PropAccess pacc | pacc = this.asExpr() | + isSafeLocationProperty(pacc) + or + // `$(location.hash)` is a fairly common and safe idiom + // (because `location.hash` always starts with `#`), + // so we mark `hash` as safe for the purposes of this query + pacc.getPropertyName() = "hash" + or + pacc.getPropertyName() = "length" + ) + } + } + /** * A regexp replacement involving an HTML meta-character, viewed as a sanitizer for * XSS vulnerabilities. diff --git a/javascript/ql/src/semmlecode.javascript.dbscheme b/javascript/ql/src/semmlecode.javascript.dbscheme index 96b0a386b6f..dad09eeeff5 100644 --- a/javascript/ql/src/semmlecode.javascript.dbscheme +++ b/javascript/ql/src/semmlecode.javascript.dbscheme @@ -688,7 +688,7 @@ case @symbol.kind of ; @type_with_symbol = @typereference | @typevariabletype | @typeoftype | @uniquesymboltype; -@ast_node_with_symbol = @typedefinition | @namespacedefinition | @toplevel | @typeaccess | @namespaceaccess | @vardecl | @function | @invokeexpr; +@ast_node_with_symbol = @typedefinition | @namespacedefinition | @toplevel | @typeaccess | @namespaceaccess | @vardecl | @function | @invokeexpr | @importdeclaration | @externalmodulereference; ast_node_symbol( unique int node: @ast_node_with_symbol ref, diff --git a/javascript/ql/test/library-tests/Classes/ClassFlow.qll b/javascript/ql/test/library-tests/Classes/ClassFlow.qll new file mode 100644 index 00000000000..6a6d44b667f --- /dev/null +++ b/javascript/ql/test/library-tests/Classes/ClassFlow.qll @@ -0,0 +1,17 @@ +import javascript + +class Configuration extends DataFlow::Configuration { + Configuration() { this = "ClassDataFlowTestingConfig" } + + override predicate isSource(DataFlow::Node source) { + source.getEnclosingExpr().(StringLiteral).getValue().toLowerCase() = "source" + } + + override predicate isSink(DataFlow::Node sink) { + any(DataFlow::CallNode call | call.getCalleeName() = "sink").getAnArgument() = sink + } +} + +query predicate dataflow(DataFlow::Node pred, DataFlow::Node succ) { + any(Configuration c).hasFlow(pred, succ) +} \ No newline at end of file diff --git a/javascript/ql/test/library-tests/Classes/PrivateField.qll b/javascript/ql/test/library-tests/Classes/PrivateField.qll new file mode 100644 index 00000000000..a5b4a5ef04a --- /dev/null +++ b/javascript/ql/test/library-tests/Classes/PrivateField.qll @@ -0,0 +1,9 @@ +import javascript + +query string getAccessModifier(DataFlow::PropRef ref, Expr prop) { + prop = ref.getPropertyNameExpr() and + if ref.isPrivateField() then + result = "Private" + else + result = "Public" +} \ No newline at end of file diff --git a/javascript/ql/test/library-tests/Classes/dataflow.js b/javascript/ql/test/library-tests/Classes/dataflow.js new file mode 100644 index 00000000000..bd4454f6b8e --- /dev/null +++ b/javascript/ql/test/library-tests/Classes/dataflow.js @@ -0,0 +1,17 @@ +(function () { + var source = "source"; + + class Foo { + #priv = source; + getPriv() { + return this.#priv; + } + + getFalsePrivate() { + return this["#priv"]; // not the same field as above. But we currently don't distinguish private and "public" fields. + } + } + sink(new Foo().getPriv()); // NOT OK. + + sink(new Foo().getFalsePrivate()); // OK [FP: Is FP because we do nothing special about dataflow for private fields.] +})(); \ No newline at end of file diff --git a/javascript/ql/test/library-tests/Classes/privateFields.js b/javascript/ql/test/library-tests/Classes/privateFields.js new file mode 100644 index 00000000000..5f108190bc3 --- /dev/null +++ b/javascript/ql/test/library-tests/Classes/privateFields.js @@ -0,0 +1,27 @@ +class Foo { + #privDecl = 3; + #if = "if"; // "keywords" are ok. + reads() { + var foo = this.#privUse; + var bar = this["#publicComputed"] + var baz = this.#if; + } + + equals(o) { + return this.#privDecl === o.#privDecl; + } + + writes() { + this.#privDecl = 4; + this["#public"] = 5; + } + + #privSecond; // is a PropNode, not a PropRef. Doesn't matter. + + ["#publicField"] = 6; + + calls() { + this.#privDecl(); + new this.#privDecl(); + } +} \ No newline at end of file diff --git a/javascript/ql/test/library-tests/Classes/tests.expected b/javascript/ql/test/library-tests/Classes/tests.expected index 3673235a6c2..e200256b70c 100644 --- a/javascript/ql/test/library-tests/Classes/tests.expected +++ b/javascript/ql/test/library-tests/Classes/tests.expected @@ -1,5 +1,9 @@ test_FieldInits +| dataflow.js:5:3:5:17 | #priv = source; | dataflow.js:5:11:5:16 | source | | fields.js:3:3:3:8 | y = 42 | fields.js:3:7:3:8 | 42 | +| privateFields.js:2:2:2:15 | #privDecl = 3; | privateFields.js:2:14:2:14 | 3 | +| privateFields.js:3:2:3:12 | #if = "if"; | privateFields.js:3:8:3:11 | "if" | +| privateFields.js:21:2:21:22 | ["#publ ... "] = 6; | privateFields.js:21:21:21:21 | 6 | test_ComputedMethods | tst.js:3:3:3:56 | ["const ... r. */ } | | tst.js:13:3:13:10 | [m]() {} | @@ -15,9 +19,11 @@ test_ClassNodeStaticMethod | points.js:20:1:33:1 | class C ... ;\\n }\\n} | className | points.js:30:19:32:3 | () {\\n ... t";\\n } | | staticConstructor.js:1:1:3:1 | class M ... r"; }\\n} | constructor | staticConstructor.js:2:21:2:59 | () { re ... tor"; } | test_ClassDefinitions +| dataflow.js:4:2:13:2 | class F ... \\n\\t\\t}\\n\\t} | | fields.js:1:1:4:1 | class C ... = 42\\n} | | points.js:1:1:18:1 | class P ... ;\\n }\\n} | | points.js:20:1:33:1 | class C ... ;\\n }\\n} | +| privateFields.js:1:1:27:1 | class F ... );\\n\\t}\\n} | | staticConstructor.js:1:1:3:1 | class M ... r"; }\\n} | | tst.js:1:9:4:1 | class { ... */ }\\n} | | tst.js:6:1:8:1 | class B ... t); }\\n} | @@ -25,17 +31,27 @@ test_ClassDefinitions test_AccessorMethods | points.js:7:3:9:3 | get dis ... y);\\n } | test_Fields +| dataflow.js:5:3:5:17 | #priv = source; | dataflow.js:5:3:5:7 | #priv | | fields.js:2:3:2:4 | x; | fields.js:2:3:2:3 | x | | fields.js:3:3:3:8 | y = 42 | fields.js:3:3:3:3 | y | +| privateFields.js:2:2:2:15 | #privDecl = 3; | privateFields.js:2:2:2:10 | #privDecl | +| privateFields.js:3:2:3:12 | #if = "if"; | privateFields.js:3:2:3:4 | #if | +| privateFields.js:19:2:19:13 | #privSecond; | privateFields.js:19:2:19:12 | #privSecond | +| privateFields.js:21:2:21:22 | ["#publ ... "] = 6; | privateFields.js:21:3:21:16 | "#publicField" | test_ClassDefinition_getName +| dataflow.js:4:2:13:2 | class F ... \\n\\t\\t}\\n\\t} | Foo | | fields.js:1:1:4:1 | class C ... = 42\\n} | C | | points.js:1:1:18:1 | class P ... ;\\n }\\n} | Point | | points.js:20:1:33:1 | class C ... ;\\n }\\n} | ColouredPoint | +| privateFields.js:1:1:27:1 | class F ... );\\n\\t}\\n} | Foo | | staticConstructor.js:1:1:3:1 | class M ... r"; }\\n} | MyClass | | tst.js:1:9:4:1 | class { ... */ }\\n} | A | | tst.js:6:1:8:1 | class B ... t); }\\n} | B | | tst.js:11:1:14:1 | class C ... () {}\\n} | C | test_MethodDefinitions +| dataflow.js:4:12:4:11 | constructor() {} | dataflow.js:4:12:4:11 | constructor | dataflow.js:4:12:4:11 | () {} | dataflow.js:4:2:13:2 | class F ... \\n\\t\\t}\\n\\t} | +| dataflow.js:6:3:8:3 | getPriv ... iv;\\n\\t\\t} | dataflow.js:6:3:6:9 | getPriv | dataflow.js:6:10:8:3 | () {\\n\\t\\t ... iv;\\n\\t\\t} | dataflow.js:4:2:13:2 | class F ... \\n\\t\\t}\\n\\t} | +| dataflow.js:10:3:12:3 | getFals ... . \\n\\t\\t} | dataflow.js:10:3:10:17 | getFalsePrivate | dataflow.js:10:18:12:3 | () {\\n\\t\\t ... . \\n\\t\\t} | dataflow.js:4:2:13:2 | class F ... \\n\\t\\t}\\n\\t} | | fields.js:1:9:1:8 | constructor() {} | fields.js:1:9:1:8 | constructor | fields.js:1:9:1:8 | () {} | fields.js:1:1:4:1 | class C ... = 42\\n} | | points.js:2:3:5:3 | constru ... y;\\n } | points.js:2:3:2:13 | constructor | points.js:2:14:5:3 | (x, y) ... y;\\n } | points.js:1:1:18:1 | class P ... ;\\n }\\n} | | points.js:7:3:9:3 | get dis ... y);\\n } | points.js:7:7:7:10 | dist | points.js:7:11:9:3 | () {\\n ... y);\\n } | points.js:1:1:18:1 | class P ... ;\\n }\\n} | @@ -44,6 +60,11 @@ test_MethodDefinitions | points.js:21:3:24:3 | constru ... c;\\n } | points.js:21:3:21:13 | constructor | points.js:21:14:24:3 | (x, y, ... c;\\n } | points.js:20:1:33:1 | class C ... ;\\n }\\n} | | points.js:26:3:28:3 | toStrin ... ur;\\n } | points.js:26:3:26:10 | toString | points.js:26:11:28:3 | () {\\n ... ur;\\n } | points.js:20:1:33:1 | class C ... ;\\n }\\n} | | points.js:30:3:32:3 | static ... t";\\n } | points.js:30:10:30:18 | className | points.js:30:19:32:3 | () {\\n ... t";\\n } | points.js:20:1:33:1 | class C ... ;\\n }\\n} | +| privateFields.js:1:11:1:10 | constructor() {} | privateFields.js:1:11:1:10 | constructor | privateFields.js:1:11:1:10 | () {} | privateFields.js:1:1:27:1 | class F ... );\\n\\t}\\n} | +| privateFields.js:4:2:8:2 | reads() ... #if;\\n\\t} | privateFields.js:4:2:4:6 | reads | privateFields.js:4:7:8:2 | () {\\n\\t\\t ... #if;\\n\\t} | privateFields.js:1:1:27:1 | class F ... );\\n\\t}\\n} | +| privateFields.js:10:2:12:2 | equals( ... ecl;\\n\\t} | privateFields.js:10:2:10:7 | equals | privateFields.js:10:8:12:2 | (o) {\\n\\t ... ecl;\\n\\t} | privateFields.js:1:1:27:1 | class F ... );\\n\\t}\\n} | +| privateFields.js:14:2:17:2 | writes( ... = 5;\\n\\t} | privateFields.js:14:2:14:7 | writes | privateFields.js:14:8:17:2 | () {\\n\\t\\t ... = 5;\\n\\t} | privateFields.js:1:1:27:1 | class F ... );\\n\\t}\\n} | +| privateFields.js:23:2:26:2 | calls() ... l();\\n\\t} | privateFields.js:23:2:23:6 | calls | privateFields.js:23:7:26:2 | () {\\n\\t\\t ... l();\\n\\t} | privateFields.js:1:1:27:1 | class F ... );\\n\\t}\\n} | | staticConstructor.js:1:15:1:14 | constructor() {} | staticConstructor.js:1:15:1:14 | constructor | staticConstructor.js:1:15:1:14 | () {} | staticConstructor.js:1:1:3:1 | class M ... r"; }\\n} | | staticConstructor.js:2:3:2:59 | static ... tor"; } | staticConstructor.js:2:10:2:20 | constructor | staticConstructor.js:2:21:2:59 | () { re ... tor"; } | staticConstructor.js:1:1:3:1 | class M ... r"; }\\n} | | tst.js:2:3:2:50 | "constr ... r. */ } | tst.js:2:3:2:15 | "constructor" | tst.js:2:16:2:50 | () { /* ... r. */ } | tst.js:1:9:4:1 | class { ... */ }\\n} | @@ -53,6 +74,10 @@ test_MethodDefinitions | tst.js:12:3:12:8 | m() {} | tst.js:12:3:12:3 | m | tst.js:12:4:12:8 | () {} | tst.js:11:1:14:1 | class C ... () {}\\n} | | tst.js:13:3:13:10 | [m]() {} | tst.js:13:4:13:4 | m | tst.js:13:6:13:10 | () {} | tst.js:11:1:14:1 | class C ... () {}\\n} | test_getAMember +| dataflow.js:4:2:13:2 | class F ... \\n\\t\\t}\\n\\t} | dataflow.js:4:12:4:11 | constructor() {} | +| dataflow.js:4:2:13:2 | class F ... \\n\\t\\t}\\n\\t} | dataflow.js:5:3:5:17 | #priv = source; | +| dataflow.js:4:2:13:2 | class F ... \\n\\t\\t}\\n\\t} | dataflow.js:6:3:8:3 | getPriv ... iv;\\n\\t\\t} | +| dataflow.js:4:2:13:2 | class F ... \\n\\t\\t}\\n\\t} | dataflow.js:10:3:12:3 | getFals ... . \\n\\t\\t} | | fields.js:1:1:4:1 | class C ... = 42\\n} | fields.js:1:9:1:8 | constructor() {} | | fields.js:1:1:4:1 | class C ... = 42\\n} | fields.js:2:3:2:4 | x; | | fields.js:1:1:4:1 | class C ... = 42\\n} | fields.js:3:3:3:8 | y = 42 | @@ -63,6 +88,15 @@ test_getAMember | points.js:20:1:33:1 | class C ... ;\\n }\\n} | points.js:21:3:24:3 | constru ... c;\\n } | | points.js:20:1:33:1 | class C ... ;\\n }\\n} | points.js:26:3:28:3 | toStrin ... ur;\\n } | | points.js:20:1:33:1 | class C ... ;\\n }\\n} | points.js:30:3:32:3 | static ... t";\\n } | +| privateFields.js:1:1:27:1 | class F ... );\\n\\t}\\n} | privateFields.js:1:11:1:10 | constructor() {} | +| privateFields.js:1:1:27:1 | class F ... );\\n\\t}\\n} | privateFields.js:2:2:2:15 | #privDecl = 3; | +| privateFields.js:1:1:27:1 | class F ... );\\n\\t}\\n} | privateFields.js:3:2:3:12 | #if = "if"; | +| privateFields.js:1:1:27:1 | class F ... );\\n\\t}\\n} | privateFields.js:4:2:8:2 | reads() ... #if;\\n\\t} | +| privateFields.js:1:1:27:1 | class F ... );\\n\\t}\\n} | privateFields.js:10:2:12:2 | equals( ... ecl;\\n\\t} | +| privateFields.js:1:1:27:1 | class F ... );\\n\\t}\\n} | privateFields.js:14:2:17:2 | writes( ... = 5;\\n\\t} | +| privateFields.js:1:1:27:1 | class F ... );\\n\\t}\\n} | privateFields.js:19:2:19:13 | #privSecond; | +| privateFields.js:1:1:27:1 | class F ... );\\n\\t}\\n} | privateFields.js:21:2:21:22 | ["#publ ... "] = 6; | +| privateFields.js:1:1:27:1 | class F ... );\\n\\t}\\n} | privateFields.js:23:2:26:2 | calls() ... l();\\n\\t} | | staticConstructor.js:1:1:3:1 | class M ... r"; }\\n} | staticConstructor.js:1:15:1:14 | constructor() {} | | staticConstructor.js:1:1:3:1 | class M ... r"; }\\n} | staticConstructor.js:2:3:2:59 | static ... tor"; } | | tst.js:1:9:4:1 | class { ... */ }\\n} | tst.js:2:3:2:50 | "constr ... r. */ } | @@ -72,6 +106,9 @@ test_getAMember | tst.js:11:1:14:1 | class C ... () {}\\n} | tst.js:12:3:12:8 | m() {} | | tst.js:11:1:14:1 | class C ... () {}\\n} | tst.js:13:3:13:10 | [m]() {} | test_MethodNames +| dataflow.js:4:12:4:11 | constructor() {} | constructor | +| dataflow.js:6:3:8:3 | getPriv ... iv;\\n\\t\\t} | getPriv | +| dataflow.js:10:3:12:3 | getFals ... . \\n\\t\\t} | getFalsePrivate | | fields.js:1:9:1:8 | constructor() {} | constructor | | points.js:2:3:5:3 | constru ... y;\\n } | constructor | | points.js:7:3:9:3 | get dis ... y);\\n } | dist | @@ -80,6 +117,11 @@ test_MethodNames | points.js:21:3:24:3 | constru ... c;\\n } | constructor | | points.js:26:3:28:3 | toStrin ... ur;\\n } | toString | | points.js:30:3:32:3 | static ... t";\\n } | className | +| privateFields.js:1:11:1:10 | constructor() {} | constructor | +| privateFields.js:4:2:8:2 | reads() ... #if;\\n\\t} | reads | +| privateFields.js:10:2:12:2 | equals( ... ecl;\\n\\t} | equals | +| privateFields.js:14:2:17:2 | writes( ... = 5;\\n\\t} | writes | +| privateFields.js:23:2:26:2 | calls() ... l();\\n\\t} | calls | | staticConstructor.js:1:15:1:14 | constructor() {} | constructor | | staticConstructor.js:2:3:2:59 | static ... tor"; } | constructor | | tst.js:2:3:2:50 | "constr ... r. */ } | constructor | @@ -94,27 +136,100 @@ test_SuperExpr | points.js:27:12:27:16 | super | | tst.js:7:19:7:23 | super | test_SyntheticConstructors +| dataflow.js:4:12:4:11 | constructor() {} | | fields.js:1:9:1:8 | constructor() {} | +| privateFields.js:1:11:1:10 | constructor() {} | | staticConstructor.js:1:15:1:14 | constructor() {} | | tst.js:11:9:11:8 | constructor() {} | test_ConstructorDefinitions +| dataflow.js:4:12:4:11 | constructor() {} | | fields.js:1:9:1:8 | constructor() {} | | points.js:2:3:5:3 | constru ... y;\\n } | | points.js:21:3:24:3 | constru ... c;\\n } | +| privateFields.js:1:11:1:10 | constructor() {} | | staticConstructor.js:1:15:1:14 | constructor() {} | | tst.js:2:3:2:50 | "constr ... r. */ } | | tst.js:7:3:7:38 | constru ... get); } | | tst.js:11:9:11:8 | constructor() {} | test_ClassNodeConstructor +| dataflow.js:4:2:13:2 | class F ... \\n\\t\\t}\\n\\t} | dataflow.js:4:12:4:11 | () {} | | fields.js:1:1:4:1 | class C ... = 42\\n} | fields.js:1:9:1:8 | () {} | | points.js:1:1:18:1 | class P ... ;\\n }\\n} | points.js:2:14:5:3 | (x, y) ... y;\\n } | | points.js:20:1:33:1 | class C ... ;\\n }\\n} | points.js:21:14:24:3 | (x, y, ... c;\\n } | +| privateFields.js:1:1:27:1 | class F ... );\\n\\t}\\n} | privateFields.js:1:11:1:10 | () {} | | staticConstructor.js:1:1:3:1 | class M ... r"; }\\n} | staticConstructor.js:1:15:1:14 | () {} | | tst.js:1:9:4:1 | class { ... */ }\\n} | tst.js:2:16:2:50 | () { /* ... r. */ } | | tst.js:6:1:8:1 | class B ... t); }\\n} | tst.js:7:14:7:38 | () { su ... get); } | | tst.js:11:1:14:1 | class C ... () {}\\n} | tst.js:11:9:11:8 | () {} | test_ClassNodeInstanceMethod +| dataflow.js:4:2:13:2 | class F ... \\n\\t\\t}\\n\\t} | getFalsePrivate | dataflow.js:10:18:12:3 | () {\\n\\t\\t ... . \\n\\t\\t} | +| dataflow.js:4:2:13:2 | class F ... \\n\\t\\t}\\n\\t} | getPriv | dataflow.js:6:10:8:3 | () {\\n\\t\\t ... iv;\\n\\t\\t} | | points.js:1:1:18:1 | class P ... ;\\n }\\n} | toString | points.js:11:11:13:3 | () {\\n ... )";\\n } | | points.js:20:1:33:1 | class C ... ;\\n }\\n} | toString | points.js:26:11:28:3 | () {\\n ... ur;\\n } | +| privateFields.js:1:1:27:1 | class F ... );\\n\\t}\\n} | calls | privateFields.js:23:7:26:2 | () {\\n\\t\\t ... l();\\n\\t} | +| privateFields.js:1:1:27:1 | class F ... );\\n\\t}\\n} | equals | privateFields.js:10:8:12:2 | (o) {\\n\\t ... ecl;\\n\\t} | +| privateFields.js:1:1:27:1 | class F ... );\\n\\t}\\n} | reads | privateFields.js:4:7:8:2 | () {\\n\\t\\t ... #if;\\n\\t} | +| privateFields.js:1:1:27:1 | class F ... );\\n\\t}\\n} | writes | privateFields.js:14:8:17:2 | () {\\n\\t\\t ... = 5;\\n\\t} | | tst.js:1:9:4:1 | class { ... */ }\\n} | constructor | tst.js:3:18:3:56 | () { /* ... r. */ } | | tst.js:11:1:14:1 | class C ... () {}\\n} | m | tst.js:12:4:12:8 | () {} | +getAccessModifier +| dataflow.js:4:12:4:11 | constructor() {} | dataflow.js:4:12:4:11 | constructor | Public | +| dataflow.js:5:3:5:17 | #priv = source; | dataflow.js:5:3:5:7 | #priv | Private | +| dataflow.js:6:3:8:3 | getPriv ... iv;\\n\\t\\t} | dataflow.js:6:3:6:9 | getPriv | Public | +| dataflow.js:7:11:7:20 | this.#priv | dataflow.js:7:16:7:20 | #priv | Private | +| dataflow.js:10:3:12:3 | getFals ... . \\n\\t\\t} | dataflow.js:10:3:10:17 | getFalsePrivate | Public | +| dataflow.js:11:11:11:23 | this["#priv"] | dataflow.js:11:16:11:22 | "#priv" | Public | +| dataflow.js:14:7:14:23 | new Foo().getPriv | dataflow.js:14:17:14:23 | getPriv | Public | +| dataflow.js:16:7:16:31 | new Foo ... Private | dataflow.js:16:17:16:31 | getFalsePrivate | Public | +| fields.js:1:9:1:8 | constructor() {} | fields.js:1:9:1:8 | constructor | Public | +| fields.js:3:3:3:8 | y = 42 | fields.js:3:3:3:3 | y | Public | +| points.js:2:3:5:3 | constru ... y;\\n } | points.js:2:3:2:13 | constructor | Public | +| points.js:3:5:3:10 | this.x | points.js:3:10:3:10 | x | Public | +| points.js:4:5:4:10 | this.y | points.js:4:10:4:10 | y | Public | +| points.js:7:3:9:3 | get dis ... y);\\n } | points.js:7:7:7:10 | dist | Public | +| points.js:8:12:8:20 | Math.sqrt | points.js:8:17:8:20 | sqrt | Public | +| points.js:8:22:8:27 | this.x | points.js:8:27:8:27 | x | Public | +| points.js:8:29:8:34 | this.x | points.js:8:34:8:34 | x | Public | +| points.js:8:36:8:41 | this.y | points.js:8:41:8:41 | y | Public | +| points.js:8:43:8:48 | this.y | points.js:8:48:8:48 | y | Public | +| points.js:11:3:13:3 | toStrin ... )";\\n } | points.js:11:3:11:10 | toString | Public | +| points.js:12:18:12:23 | this.x | points.js:12:23:12:23 | x | Public | +| points.js:12:34:12:39 | this.y | points.js:12:39:12:39 | y | Public | +| points.js:15:3:17:3 | static ... t";\\n } | points.js:15:10:15:18 | className | Public | +| points.js:21:3:24:3 | constru ... c;\\n } | points.js:21:3:21:13 | constructor | Public | +| points.js:23:5:23:15 | this.colour | points.js:23:10:23:15 | colour | Public | +| points.js:26:3:28:3 | toStrin ... ur;\\n } | points.js:26:3:26:10 | toString | Public | +| points.js:27:12:27:25 | super.toString | points.js:27:18:27:25 | toString | Public | +| points.js:27:40:27:50 | this.colour | points.js:27:45:27:50 | colour | Public | +| points.js:30:3:32:3 | static ... t";\\n } | points.js:30:10:30:18 | className | Public | +| privateFields.js:1:11:1:10 | constructor() {} | privateFields.js:1:11:1:10 | constructor | Public | +| privateFields.js:2:2:2:15 | #privDecl = 3; | privateFields.js:2:2:2:10 | #privDecl | Private | +| privateFields.js:3:2:3:12 | #if = "if"; | privateFields.js:3:2:3:4 | #if | Private | +| privateFields.js:4:2:8:2 | reads() ... #if;\\n\\t} | privateFields.js:4:2:4:6 | reads | Public | +| privateFields.js:5:13:5:25 | this.#privUse | privateFields.js:5:18:5:25 | #privUse | Private | +| privateFields.js:6:13:6:35 | this["# ... puted"] | privateFields.js:6:18:6:34 | "#publicComputed" | Public | +| privateFields.js:7:13:7:20 | this.#if | privateFields.js:7:18:7:20 | #if | Private | +| privateFields.js:10:2:12:2 | equals( ... ecl;\\n\\t} | privateFields.js:10:2:10:7 | equals | Public | +| privateFields.js:11:10:11:23 | this.#privDecl | privateFields.js:11:15:11:23 | #privDecl | Private | +| privateFields.js:11:29:11:39 | o.#privDecl | privateFields.js:11:31:11:39 | #privDecl | Private | +| privateFields.js:14:2:17:2 | writes( ... = 5;\\n\\t} | privateFields.js:14:2:14:7 | writes | Public | +| privateFields.js:15:3:15:16 | this.#privDecl | privateFields.js:15:8:15:16 | #privDecl | Private | +| privateFields.js:16:3:16:17 | this["#public"] | privateFields.js:16:8:16:16 | "#public" | Public | +| privateFields.js:21:2:21:22 | ["#publ ... "] = 6; | privateFields.js:21:3:21:16 | "#publicField" | Public | +| privateFields.js:23:2:26:2 | calls() ... l();\\n\\t} | privateFields.js:23:2:23:6 | calls | Public | +| privateFields.js:24:3:24:16 | this.#privDecl | privateFields.js:24:8:24:16 | #privDecl | Private | +| privateFields.js:25:7:25:20 | this.#privDecl | privateFields.js:25:12:25:20 | #privDecl | Private | +| staticConstructor.js:1:15:1:14 | constructor() {} | staticConstructor.js:1:15:1:14 | constructor | Public | +| staticConstructor.js:2:3:2:59 | static ... tor"; } | staticConstructor.js:2:10:2:20 | constructor | Public | +| staticConstructor.js:4:1:4:11 | console.log | staticConstructor.js:4:9:4:11 | log | Public | +| staticConstructor.js:4:13:4:31 | MyClass.constructor | staticConstructor.js:4:21:4:31 | constructor | Public | +| tst.js:2:3:2:50 | "constr ... r. */ } | tst.js:2:3:2:15 | "constructor" | Public | +| tst.js:3:3:3:56 | ["const ... r. */ } | tst.js:3:4:3:16 | "constructor" | Public | +| tst.js:7:3:7:38 | constru ... get); } | tst.js:7:3:7:13 | constructor | Public | +| tst.js:11:9:11:8 | constructor() {} | tst.js:11:9:11:8 | constructor | Public | +| tst.js:12:3:12:8 | m() {} | tst.js:12:3:12:3 | m | Public | +| tst.js:13:3:13:10 | [m]() {} | tst.js:13:4:13:4 | m | Public | +| tst.js:17:3:17:20 | m() { return 42; } | tst.js:17:3:17:3 | m | Public | +dataflow +| dataflow.js:2:15:2:22 | "source" | dataflow.js:14:7:14:25 | new Foo().getPriv() | +| dataflow.js:2:15:2:22 | "source" | dataflow.js:16:7:16:33 | new Foo ... ivate() | diff --git a/javascript/ql/test/library-tests/Classes/tests.ql b/javascript/ql/test/library-tests/Classes/tests.ql index 3009546636c..41fb13f3af0 100644 --- a/javascript/ql/test/library-tests/Classes/tests.ql +++ b/javascript/ql/test/library-tests/Classes/tests.ql @@ -16,3 +16,5 @@ import SyntheticConstructors import ConstructorDefinitions import ClassNodeConstructor import ClassNodeInstanceMethod +import PrivateField +import ClassFlow \ No newline at end of file diff --git a/javascript/ql/test/library-tests/CustomLoadStoreSteps/test.expected b/javascript/ql/test/library-tests/CustomLoadStoreSteps/test.expected new file mode 100644 index 00000000000..b53738c569a --- /dev/null +++ b/javascript/ql/test/library-tests/CustomLoadStoreSteps/test.expected @@ -0,0 +1 @@ +| tst.js:4:15:4:22 | "source" | tst.js:9:7:9:24 | readTaint(tainted) | diff --git a/javascript/ql/test/library-tests/CustomLoadStoreSteps/test.ql b/javascript/ql/test/library-tests/CustomLoadStoreSteps/test.ql new file mode 100644 index 00000000000..e4ffc89294d --- /dev/null +++ b/javascript/ql/test/library-tests/CustomLoadStoreSteps/test.ql @@ -0,0 +1,26 @@ +import javascript + +class Configuration extends TaintTracking::Configuration { + Configuration() { this = "PromiseFlowTestingConfig" } + + override predicate isSource(DataFlow::Node source) { + source.getEnclosingExpr().getStringValue() = "source" + } + + override predicate isSink(DataFlow::Node sink) { + any(DataFlow::InvokeNode call | call.getCalleeName() = "sink").getAnArgument() = sink + } + + // When the source code states that "foo" is being read, "bar" is additionally being read. + override predicate isAdditionalLoadStep(DataFlow::Node pred, DataFlow::Node succ, string prop) { + exists(DataFlow::PropRead read | read = succ | + read.getBase() = pred and + read.getPropertyName() = "foo" + ) and + prop = "bar" + } +} + +from DataFlow::Node pred, DataFlow::Node succ, Configuration cfg +where cfg.hasFlow(pred, succ) +select pred, succ diff --git a/javascript/ql/test/library-tests/CustomLoadStoreSteps/tst.js b/javascript/ql/test/library-tests/CustomLoadStoreSteps/tst.js new file mode 100644 index 00000000000..c3fff1b4187 --- /dev/null +++ b/javascript/ql/test/library-tests/CustomLoadStoreSteps/tst.js @@ -0,0 +1,10 @@ +// When the source code states that "foo" is being read, "bar" is additionally being read. + +(function () { + var source = "source"; + var tainted = { bar: source }; + function readTaint(x) { + return x.foo; + } + sink(readTaint(tainted)); +})(); \ No newline at end of file diff --git a/javascript/ql/test/library-tests/InterProceduralFlow/DataFlow.expected b/javascript/ql/test/library-tests/InterProceduralFlow/DataFlow.expected index ab45472d898..24e5f967f9f 100644 --- a/javascript/ql/test/library-tests/InterProceduralFlow/DataFlow.expected +++ b/javascript/ql/test/library-tests/InterProceduralFlow/DataFlow.expected @@ -23,11 +23,11 @@ | partial.js:5:15:5:24 | "tainted1" | partial.js:21:15:21:15 | x | | partial.js:5:15:5:24 | "tainted1" | partial.js:27:15:27:15 | x | | promises.js:2:16:2:24 | "tainted" | promises.js:7:16:7:18 | val | +| promises.js:2:16:2:24 | "tainted" | promises.js:38:32:38:32 | v | | promises.js:11:22:11:31 | "resolved" | promises.js:19:20:19:20 | v | -| promises.js:11:22:11:31 | "resolved" | promises.js:27:16:27:16 | v | | promises.js:12:22:12:31 | "rejected" | promises.js:21:20:21:20 | v | | promises.js:12:22:12:31 | "rejected" | promises.js:24:20:24:20 | v | -| promises.js:12:22:12:31 | "rejected" | promises.js:27:16:27:16 | v | +| promises.js:32:24:32:37 | "also tainted" | promises.js:38:32:38:32 | v | | properties2.js:7:14:7:21 | "source" | properties2.js:8:12:8:24 | foo(source).p | | properties2.js:7:14:7:21 | "source" | properties2.js:33:13:33:20 | getP(o3) | | properties.js:2:16:2:24 | "tainted" | properties.js:5:14:5:23 | a.someProp | diff --git a/javascript/ql/test/library-tests/InterProceduralFlow/GermanFlow.expected b/javascript/ql/test/library-tests/InterProceduralFlow/GermanFlow.expected index fef4bafb3fd..9c3980797ea 100644 --- a/javascript/ql/test/library-tests/InterProceduralFlow/GermanFlow.expected +++ b/javascript/ql/test/library-tests/InterProceduralFlow/GermanFlow.expected @@ -24,11 +24,11 @@ | partial.js:5:15:5:24 | "tainted1" | partial.js:21:15:21:15 | x | | partial.js:5:15:5:24 | "tainted1" | partial.js:27:15:27:15 | x | | promises.js:2:16:2:24 | "tainted" | promises.js:7:16:7:18 | val | +| promises.js:2:16:2:24 | "tainted" | promises.js:38:32:38:32 | v | | promises.js:11:22:11:31 | "resolved" | promises.js:19:20:19:20 | v | -| promises.js:11:22:11:31 | "resolved" | promises.js:27:16:27:16 | v | | promises.js:12:22:12:31 | "rejected" | promises.js:21:20:21:20 | v | | promises.js:12:22:12:31 | "rejected" | promises.js:24:20:24:20 | v | -| promises.js:12:22:12:31 | "rejected" | promises.js:27:16:27:16 | v | +| promises.js:32:24:32:37 | "also tainted" | promises.js:38:32:38:32 | v | | properties2.js:7:14:7:21 | "source" | properties2.js:8:12:8:24 | foo(source).p | | properties2.js:7:14:7:21 | "source" | properties2.js:33:13:33:20 | getP(o3) | | properties.js:2:16:2:24 | "tainted" | properties.js:5:14:5:23 | a.someProp | diff --git a/javascript/ql/test/library-tests/InterProceduralFlow/TaintTracking.expected b/javascript/ql/test/library-tests/InterProceduralFlow/TaintTracking.expected index 67db1a56cd9..5c252b4aeea 100644 --- a/javascript/ql/test/library-tests/InterProceduralFlow/TaintTracking.expected +++ b/javascript/ql/test/library-tests/InterProceduralFlow/TaintTracking.expected @@ -30,10 +30,8 @@ | promises.js:2:16:2:24 | "tainted" | promises.js:7:16:7:18 | val | | promises.js:2:16:2:24 | "tainted" | promises.js:38:32:38:32 | v | | promises.js:11:22:11:31 | "resolved" | promises.js:19:20:19:20 | v | -| promises.js:11:22:11:31 | "resolved" | promises.js:27:16:27:16 | v | | promises.js:12:22:12:31 | "rejected" | promises.js:21:20:21:20 | v | | promises.js:12:22:12:31 | "rejected" | promises.js:24:20:24:20 | v | -| promises.js:12:22:12:31 | "rejected" | promises.js:27:16:27:16 | v | | promises.js:32:24:32:37 | "also tainted" | promises.js:38:32:38:32 | v | | properties2.js:7:14:7:21 | "source" | properties2.js:8:12:8:24 | foo(source).p | | properties2.js:7:14:7:21 | "source" | properties2.js:33:13:33:20 | getP(o3) | diff --git a/javascript/ql/test/library-tests/InterProceduralFlow/TrackedNodes.expected b/javascript/ql/test/library-tests/InterProceduralFlow/TrackedNodes.expected index e69de29bb2d..7c8c2ca4b45 100644 --- a/javascript/ql/test/library-tests/InterProceduralFlow/TrackedNodes.expected +++ b/javascript/ql/test/library-tests/InterProceduralFlow/TrackedNodes.expected @@ -0,0 +1,32 @@ +| missing | promises.js:1:2:1:2 | source | promises.js:6:26:6:28 | val | +| missing | promises.js:1:2:1:2 | source | promises.js:7:16:7:18 | val | +| missing | promises.js:1:2:1:2 | source | promises.js:37:11:37:11 | v | +| missing | promises.js:1:2:1:2 | source | promises.js:38:32:38:32 | v | +| missing | promises.js:2:16:2:24 | "tainted" | promises.js:6:26:6:28 | val | +| missing | promises.js:2:16:2:24 | "tainted" | promises.js:7:16:7:18 | val | +| missing | promises.js:2:16:2:24 | "tainted" | promises.js:37:11:37:11 | v | +| missing | promises.js:2:16:2:24 | "tainted" | promises.js:38:32:38:32 | v | +| missing | promises.js:10:30:17:3 | exceptional return of anonymous function | promises.js:20:7:20:7 | v | +| missing | promises.js:10:30:17:3 | exceptional return of anonymous function | promises.js:21:20:21:20 | v | +| missing | promises.js:10:30:17:3 | exceptional return of anonymous function | promises.js:23:19:23:19 | v | +| missing | promises.js:10:30:17:3 | exceptional return of anonymous function | promises.js:24:20:24:20 | v | +| missing | promises.js:11:22:11:31 | "resolved" | promises.js:18:18:18:18 | v | +| missing | promises.js:11:22:11:31 | "resolved" | promises.js:19:20:19:20 | v | +| missing | promises.js:12:22:12:31 | "rejected" | promises.js:20:7:20:7 | v | +| missing | promises.js:12:22:12:31 | "rejected" | promises.js:21:20:21:20 | v | +| missing | promises.js:12:22:12:31 | "rejected" | promises.js:23:19:23:19 | v | +| missing | promises.js:12:22:12:31 | "rejected" | promises.js:24:20:24:20 | v | +| missing | promises.js:13:9:13:21 | exceptional return of Math.random() | promises.js:20:7:20:7 | v | +| missing | promises.js:13:9:13:21 | exceptional return of Math.random() | promises.js:21:20:21:20 | v | +| missing | promises.js:13:9:13:21 | exceptional return of Math.random() | promises.js:23:19:23:19 | v | +| missing | promises.js:13:9:13:21 | exceptional return of Math.random() | promises.js:24:20:24:20 | v | +| missing | promises.js:14:7:14:21 | exceptional return of res(res_source) | promises.js:20:7:20:7 | v | +| missing | promises.js:14:7:14:21 | exceptional return of res(res_source) | promises.js:21:20:21:20 | v | +| missing | promises.js:14:7:14:21 | exceptional return of res(res_source) | promises.js:23:19:23:19 | v | +| missing | promises.js:14:7:14:21 | exceptional return of res(res_source) | promises.js:24:20:24:20 | v | +| missing | promises.js:16:7:16:21 | exceptional return of rej(rej_source) | promises.js:20:7:20:7 | v | +| missing | promises.js:16:7:16:21 | exceptional return of rej(rej_source) | promises.js:21:20:21:20 | v | +| missing | promises.js:16:7:16:21 | exceptional return of rej(rej_source) | promises.js:23:19:23:19 | v | +| missing | promises.js:16:7:16:21 | exceptional return of rej(rej_source) | promises.js:24:20:24:20 | v | +| missing | promises.js:32:24:32:37 | "also tainted" | promises.js:37:11:37:11 | v | +| missing | promises.js:32:24:32:37 | "also tainted" | promises.js:38:32:38:32 | v | diff --git a/javascript/ql/test/library-tests/InterProceduralFlow/promises.js b/javascript/ql/test/library-tests/InterProceduralFlow/promises.js index 2f67ea30194..b0055c78781 100644 --- a/javascript/ql/test/library-tests/InterProceduralFlow/promises.js +++ b/javascript/ql/test/library-tests/InterProceduralFlow/promises.js @@ -23,7 +23,7 @@ promise2.catch((v) => { var rej_sink = v; }); - promise2.finally((v) => { + promise2.finally((v) => { // no promise implementation sends an argument to the finally handler. So there is no data-flow here. var sink = v; }); diff --git a/javascript/ql/test/library-tests/NPM/Modules.expected b/javascript/ql/test/library-tests/NPM/Modules.expected index e7fa54d6f9a..9ce1e5fccd6 100644 --- a/javascript/ql/test/library-tests/NPM/Modules.expected +++ b/javascript/ql/test/library-tests/NPM/Modules.expected @@ -1,6 +1,7 @@ | b | src/node_modules/b/lib/index.js:1:1:2:0 | | | b | src/node_modules/b/lib/index.ts:1:1:2:0 | | | c | src/node_modules/c/src/index.js:1:1:2:0 | | +| d | src/node_modules/d/main.js:1:1:2:0 | | | test-package | src/index.js:1:1:4:0 | | | test-package | src/lib/tst2.js:1:1:1:14 | | | test-package | src/lib/tst.js:1:1:4:0 | | diff --git a/javascript/ql/test/library-tests/NPM/NPMPackage_getMainModule.expected b/javascript/ql/test/library-tests/NPM/NPMPackage_getMainModule.expected index a8b7753abae..c8fe4a8ba4b 100644 --- a/javascript/ql/test/library-tests/NPM/NPMPackage_getMainModule.expected +++ b/javascript/ql/test/library-tests/NPM/NPMPackage_getMainModule.expected @@ -1,4 +1,5 @@ | b | src/node_modules/b/lib/index.ts:1:1:2:0 | | | c | src/node_modules/c/src/index.js:1:1:2:0 | | +| d | src/node_modules/d/main.js:1:1:2:0 | | | test-package | src/index.js:1:1:4:0 | | | third-party-module | src/node_modules/third-party-module/fancy.js:1:1:4:0 | | diff --git a/javascript/ql/test/library-tests/NPM/PackageJSON.expected b/javascript/ql/test/library-tests/NPM/PackageJSON.expected index 22e42834813..5ad71e3cb45 100644 --- a/javascript/ql/test/library-tests/NPM/PackageJSON.expected +++ b/javascript/ql/test/library-tests/NPM/PackageJSON.expected @@ -1,4 +1,5 @@ | src/node_modules/b/package.json:1:1:4:1 | {\\n "na ... "lib"\\n} | | src/node_modules/c/package.json:1:1:4:1 | {\\n "na ... src/"\\n} | +| src/node_modules/d/package.json:1:1:4:1 | {\\n "na ... main"\\n} | | src/node_modules/third-party-module/package.json:1:1:5:1 | {\\n "na ... y.js"\\n} | | src/package.json:1:1:18:1 | {\\n "na ... "\\n }\\n} | diff --git a/javascript/ql/test/library-tests/NPM/src/node_modules/d/main.js b/javascript/ql/test/library-tests/NPM/src/node_modules/d/main.js new file mode 100644 index 00000000000..987d6d7e401 --- /dev/null +++ b/javascript/ql/test/library-tests/NPM/src/node_modules/d/main.js @@ -0,0 +1 @@ +export default "d"; diff --git a/javascript/ql/test/library-tests/NPM/src/node_modules/d/package.json b/javascript/ql/test/library-tests/NPM/src/node_modules/d/package.json new file mode 100644 index 00000000000..2b264e942ef --- /dev/null +++ b/javascript/ql/test/library-tests/NPM/src/node_modules/d/package.json @@ -0,0 +1,4 @@ +{ + "name": "d", + "main": "main" +} diff --git a/javascript/ql/test/library-tests/Promises/AdditionalPromises.expected b/javascript/ql/test/library-tests/Promises/AdditionalPromises.expected index a3fa5795b30..1c1f14deabe 100644 --- a/javascript/ql/test/library-tests/Promises/AdditionalPromises.expected +++ b/javascript/ql/test/library-tests/Promises/AdditionalPromises.expected @@ -1,4 +1,74 @@ | additional-promises.js:2:13:2:57 | new Pin ... ct) {}) | +| flow.js:7:11:7:59 | new Pro ... ource)) | +| flow.js:10:11:10:58 | new Pro ... ource)) | +| flow.js:13:11:13:58 | new Pro ... ource)) | +| flow.js:20:2:20:24 | Promise ... source) | +| flow.js:22:2:22:24 | Promise ... source) | +| flow.js:24:2:24:49 | new Pro ... ource)) | +| flow.js:26:2:26:49 | new Pro ... ource)) | +| flow.js:28:2:28:23 | Promise ... ("foo") | +| flow.js:28:2:28:41 | Promise ... source) | +| flow.js:30:2:30:24 | Promise ... source) | +| flow.js:30:2:30:41 | Promise ... "foo") | +| flow.js:32:2:32:49 | new Pro ... ource)) | +| flow.js:34:2:34:24 | Promise ... source) | +| flow.js:34:2:34:41 | Promise ... => { }) | +| flow.js:36:11:36:33 | Promise ... source) | +| flow.js:37:11:37:29 | p5.catch(() => { }) | +| flow.js:40:2:40:49 | new Pro ... ource)) | +| flow.js:40:2:40:65 | new Pro ... => { }) | +| flow.js:42:2:42:49 | new Pro ... ource)) | +| flow.js:42:2:42:76 | new Pro ... => { }) | +| flow.js:44:2:44:24 | Promise ... source) | +| flow.js:44:2:44:41 | Promise ... => { }) | +| flow.js:44:2:44:58 | Promise ... => { }) | +| flow.js:44:2:44:75 | Promise ... => { }) | +| flow.js:46:2:46:24 | Promise ... source) | +| flow.js:46:2:46:43 | Promise ... => { }) | +| flow.js:48:2:48:36 | new Pro ... urce }) | +| flow.js:53:2:53:22 | createP ... source) | +| flow.js:55:11:55:58 | new Pro ... ource)) | +| flow.js:56:11:56:27 | p8.then(() => {}) | +| flow.js:57:12:57:31 | p9.finally(() => {}) | +| flow.js:60:12:60:59 | new Pro ... ource)) | +| flow.js:61:12:61:29 | p11.then(() => {}) | +| flow.js:65:9:65:56 | new Pro ... ource)) | +| flow.js:74:10:74:57 | new Pro ... ource)) | +| flow.js:76:2:76:17 | chainedPromise() | +| flow.js:76:2:76:32 | chained ... => {}) | +| flow.js:86:23:86:70 | new Pro ... ource)) | +| flow.js:89:3:89:27 | ("foo", ... => {}) | +| flow.js:91:21:91:68 | new Pro ... ource)) | +| flow.js:100:28:100:75 | new Pro ... ource)) | +| flow.js:103:2:103:48 | new Pro ... "BLA")) | +| flow.js:103:2:103:76 | new Pro ... ource}) | +| flow.js:105:2:105:48 | new Pro ... "BLA")) | +| flow.js:105:2:105:77 | new Pro ... ource}) | +| flow.js:107:17:107:64 | new Pro ... ource)) | +| flow.js:109:2:109:48 | new Pro ... "BLA")) | +| flow.js:109:2:109:71 | new Pro ... jected) | +| flow.js:111:2:111:48 | new Pro ... "BLA")) | +| flow.js:111:2:111:69 | new Pro ... jected) | +| flow.js:113:2:113:48 | new Pro ... "BLA")) | +| flow.js:113:2:113:69 | new Pro ... jected) | +| flow.js:117:2:117:48 | new Pro ... "BLA")) | +| flow.js:117:2:117:69 | new Pro ... solved) | +| flow.js:119:2:119:48 | new Pro ... "BLA")) | +| flow.js:119:2:119:69 | new Pro ... solved) | +| flow.js:121:2:121:21 | Promise.resolve(123) | +| flow.js:121:2:121:41 | Promise ... solved) | +| flow.js:123:2:123:21 | Promise.resolve(123) | +| flow.js:123:2:123:41 | Promise ... solved) | +| flow.js:125:2:125:21 | Promise.resolve(123) | +| flow.js:125:2:125:41 | Promise ... jected) | +| flow.js:127:2:127:21 | Promise.resolve(123) | +| flow.js:127:2:127:41 | Promise ... jected) | +| flow.js:129:2:129:52 | new Pro ... olved)) | +| flow.js:131:2:131:26 | Promise ... solved) | +| interflow.js:6:3:6:25 | loadScr ... urce()) | +| interflow.js:6:3:7:26 | loadScr ... () { }) | +| interflow.js:6:3:8:26 | loadScr ... () { }) | +| interflow.js:11:12:15:6 | new Pro ... \\n }) | | promises.js:3:17:5:4 | new Pro ... );\\n }) | | promises.js:10:18:17:4 | new Pro ... );\\n }) | | promises.js:33:19:35:6 | new Pro ... \\n }) | diff --git a/javascript/ql/test/library-tests/Promises/flow.js b/javascript/ql/test/library-tests/Promises/flow.js new file mode 100644 index 00000000000..d92ef541b85 --- /dev/null +++ b/javascript/ql/test/library-tests/Promises/flow.js @@ -0,0 +1,132 @@ +(async function () { + var source = "source"; + + var p1 = Promise.resolve(source); + sink(await p1); // NOT OK + + var p2 = new Promise((resolve, reject) => resolve(source)); + sink(await p2); // NOT OK + + var p3 = new Promise((resolve, reject) => reject(source)); + sink(await p3); // OK! + + var p4 = new Promise((resolve, reject) => reject(source)); + try { + var foo = await p4; + } catch (e) { + sink(e); // NOT OK! + } + + Promise.resolve(source).then(x => sink(x)); // NOT OK! + + Promise.resolve(source).then(x => foo(x), y => sink(y)); // OK! + + new Promise((resolve, reject) => reject(source)).then(x => sink(x)); // OK! + + new Promise((resolve, reject) => reject(source)).then(x => foo(x), y => sink(y)); // NOT OK! + + Promise.resolve("foo").then(x => source).then(z => sink(z)); // NOT OK! + + Promise.resolve(source).then(x => "foo").then(z => sink(z)); // OK! + + new Promise((resolve, reject) => reject(source)).catch(x => sink(x)); // NOT OK! + + Promise.resolve(source).catch(() => { }).then(a => sink(a)); // NOT OK! + + var p5 = Promise.resolve(source); + var p6 = p5.catch(() => { }); + var p7 = p6.then(a => sink(a)); // NOT OK! + + new Promise((resolve, reject) => reject(source)).then(() => { }).catch(x => sink(x)); // NOT OK! + + new Promise((resolve, reject) => reject(source)).then(() => { }, () => { }).catch(x => sink(x)); // OK! + + Promise.resolve(source).catch(() => { }).catch(() => { }).catch(() => { }).then(a => sink(a)); // NOT OK! + + Promise.resolve(source).finally(() => { }).then(a => sink(a)); // NOT OK! + + new Promise(() => { throw source }).catch(x => sink(x)); // NOT OK! + + function createPromise(src) { + return Promise.resolve(src); + } + createPromise(source).then(v => sink(v)); // NOT OK! + + var p8 = new Promise((resolve, reject) => reject(source)); + var p9 = p8.then(() => {}); + var p10 = p9.finally(() => {}); + p10.catch((x) => sink(x)); // NOT OK! + + var p11 = new Promise((resolve, reject) => reject(source)); + var p12 = p11.then(() => {}); + p12.catch(x => sink(x)); // NOT OK! + + async function throws() { + await new Promise((resolve, reject) => reject(source)); + } + try { + throws(); + } catch(e) { + sink(e); // NOT OK! + } + + function chainedPromise() { + return new Promise((resolve, reject) => reject(source)).then(() => {}); + } + chainedPromise().then(() => {}).catch(e => sink(e)); // NOT OK! + + function leaksResolvedPromise(p) { + p.then(x => sink(x)); // NOT OK! + } + leaksResolvedPromise(Promise.resolve(source)); + + function leaksRejectedPromise(p) { + p.catch(e => sink(e)); // NOT OK! + } + leaksRejectedPromise(new Promise((resolve, reject) => reject(source))); + + function leaksRejectedAgain(p) { + ("foo", p).then(() => {}).catch(e => sink(e)); // NOT OK! + } + leaksRejectedAgain(new Promise((resolve, reject) => reject(source)).then(() => {})); + + async function returnsRejected(p) { + try { + await p; + } catch(e) { + return e; + } + } + var foo = returnsRejected(new Promise((resolve, reject) => reject(source))); + sink(foo); // NOT OK! + + new Promise((resolve, reject) => reject("BLA")).catch(x => {return source}).then(x => sink(x)); // NOT OK + + new Promise((resolve, reject) => reject("BLA")).finally(x => {throw source}).catch(x => sink(x)); // NOT OK + + var rejected = new Promise((resolve, reject) => reject(source)); + + new Promise((resolve, reject) => reject("BLA")).finally(x => rejected).catch(x => sink(x)); // NOT OK + + new Promise((resolve, reject) => reject("BLA")).catch(x => rejected).then(x => sink(x)) // OK + + new Promise((resolve, reject) => reject("BLA")).catch(x => rejected).catch(x => sink(x)) // NOT OK + + var resolved = Promise.resolve(source); + + new Promise((resolve, reject) => reject("BLA")).catch(x => resolved).catch(x => sink(x)) // OK + + new Promise((resolve, reject) => reject("BLA")).catch(x => resolved).then(x => sink(x)) // NOT OK + + Promise.resolve(123).then(x => resolved).catch(x => sink(x)) // OK + + Promise.resolve(123).then(x => resolved).then(x => sink(x)) // NOT OK + + Promise.resolve(123).then(x => rejected).catch(x => sink(x)) // NOT OK + + Promise.resolve(123).then(x => rejected).then(x => sink(x)) // OK + + new Promise((resolve, reject) => resolve(resolved)).then(x => sink(x)); // NOT OK + + Promise.resolve(resolved).then(x => sink(x)); // NOT OK +})(); \ No newline at end of file diff --git a/javascript/ql/test/library-tests/Promises/flow.qll b/javascript/ql/test/library-tests/Promises/flow.qll new file mode 100644 index 00000000000..395d5cb88a8 --- /dev/null +++ b/javascript/ql/test/library-tests/Promises/flow.qll @@ -0,0 +1,34 @@ +import javascript + +class Configuration extends DataFlow::Configuration { + Configuration() { this = "PromiseDataFlowFlowTestingConfig" } + + override predicate isSource(DataFlow::Node source) { + source.getEnclosingExpr().getStringValue() = "source" + } + + override predicate isSink(DataFlow::Node sink) { + any(DataFlow::InvokeNode call | call.getCalleeName() = "sink").getAnArgument() = sink + } +} + +class TaintConfig extends TaintTracking::Configuration { + TaintConfig() { this = "PromiseTaintFlowTestingConfig" } + + override predicate isSource(DataFlow::Node source) { + source.getEnclosingExpr().getStringValue() = "source" + } + + override predicate isSink(DataFlow::Node sink) { + any(DataFlow::InvokeNode call | call.getCalleeName() = "sink").getAnArgument() = sink + } +} + +query predicate flow(DataFlow::Node source, DataFlow::Node sink) { + any(Configuration c).hasFlow(source, sink) +} + +query predicate exclusiveTaintFlow(DataFlow::Node source, DataFlow::Node sink) { + not any(Configuration c).hasFlow(source, sink) and + any(TaintConfig c).hasFlow(source, sink) +} \ No newline at end of file diff --git a/javascript/ql/test/library-tests/Promises/interflow.js b/javascript/ql/test/library-tests/Promises/interflow.js new file mode 100644 index 00000000000..836b18950af --- /dev/null +++ b/javascript/ql/test/library-tests/Promises/interflow.js @@ -0,0 +1,20 @@ +(function () { + function getSource() { + var source = "source"; // step 1 + return source; // step 2 + } + loadScript(getSource()) // step 3 + .then(function () { }) + .then(function () { }) + .catch(handleError); + function loadScript(src) { // step 4 (is summarized) + return new Promise(function (resolve, reject) { + setTimeout(function (error) { + reject(new Error('Blah: ' + src)); // step 5 + }, 1000); + }); + } + function handleError(error) { // step 6 + sink(error); // step 7 + } +})(); \ No newline at end of file diff --git a/javascript/ql/test/library-tests/Promises/options b/javascript/ql/test/library-tests/Promises/options new file mode 100644 index 00000000000..ae107b46f9e --- /dev/null +++ b/javascript/ql/test/library-tests/Promises/options @@ -0,0 +1 @@ +semmle-extractor-options: --experimental diff --git a/javascript/ql/test/library-tests/Promises/tests.expected b/javascript/ql/test/library-tests/Promises/tests.expected index 144b9e19036..f4ee1263be5 100644 --- a/javascript/ql/test/library-tests/Promises/tests.expected +++ b/javascript/ql/test/library-tests/Promises/tests.expected @@ -1,38 +1,222 @@ test_ResolvedPromiseDefinition +| flow.js:4:11:4:33 | Promise ... source) | flow.js:4:27:4:32 | source | +| flow.js:20:2:20:24 | Promise ... source) | flow.js:20:18:20:23 | source | +| flow.js:22:2:22:24 | Promise ... source) | flow.js:22:18:22:23 | source | +| flow.js:28:2:28:23 | Promise ... ("foo") | flow.js:28:18:28:22 | "foo" | +| flow.js:30:2:30:24 | Promise ... source) | flow.js:30:18:30:23 | source | +| flow.js:34:2:34:24 | Promise ... source) | flow.js:34:18:34:23 | source | +| flow.js:36:11:36:33 | Promise ... source) | flow.js:36:27:36:32 | source | +| flow.js:44:2:44:24 | Promise ... source) | flow.js:44:18:44:23 | source | +| flow.js:46:2:46:24 | Promise ... source) | flow.js:46:18:46:23 | source | +| flow.js:51:10:51:29 | Promise.resolve(src) | flow.js:51:26:51:28 | src | +| flow.js:81:23:81:45 | Promise ... source) | flow.js:81:39:81:44 | source | +| flow.js:115:17:115:39 | Promise ... source) | flow.js:115:33:115:38 | source | +| flow.js:121:2:121:21 | Promise.resolve(123) | flow.js:121:18:121:20 | 123 | +| flow.js:123:2:123:21 | Promise.resolve(123) | flow.js:123:18:123:20 | 123 | +| flow.js:125:2:125:21 | Promise.resolve(123) | flow.js:125:18:125:20 | 123 | +| flow.js:127:2:127:21 | Promise.resolve(123) | flow.js:127:18:127:20 | 123 | +| flow.js:131:2:131:26 | Promise ... solved) | flow.js:131:18:131:25 | resolved | | promises.js:53:19:53:41 | Promise ... source) | promises.js:53:35:53:40 | source | | promises.js:62:19:62:41 | Promise ... source) | promises.js:62:35:62:40 | source | | promises.js:71:5:71:27 | Promise ... source) | promises.js:71:21:71:26 | source | test_PromiseDefinition_getARejectHandler +| flow.js:26:2:26:49 | new Pro ... ource)) | flow.js:26:69:26:80 | y => sink(y) | +| flow.js:32:2:32:49 | new Pro ... ource)) | flow.js:32:57:32:68 | x => sink(x) | +| flow.js:42:2:42:49 | new Pro ... ource)) | flow.js:42:67:42:75 | () => { } | +| flow.js:48:2:48:36 | new Pro ... urce }) | flow.js:48:44:48:55 | x => sink(x) | +| flow.js:103:2:103:48 | new Pro ... "BLA")) | flow.js:103:56:103:75 | x => {return source} | +| flow.js:105:2:105:48 | new Pro ... "BLA")) | flow.js:105:58:105:76 | x => {throw source} | +| flow.js:109:2:109:48 | new Pro ... "BLA")) | flow.js:109:58:109:70 | x => rejected | +| flow.js:111:2:111:48 | new Pro ... "BLA")) | flow.js:111:56:111:68 | x => rejected | +| flow.js:113:2:113:48 | new Pro ... "BLA")) | flow.js:113:56:113:68 | x => rejected | +| flow.js:117:2:117:48 | new Pro ... "BLA")) | flow.js:117:56:117:68 | x => resolved | +| flow.js:119:2:119:48 | new Pro ... "BLA")) | flow.js:119:56:119:68 | x => resolved | | promises.js:10:18:17:4 | new Pro ... );\\n }) | promises.js:20:6:22:3 | (v) => ... v;\\n } | | promises.js:10:18:17:4 | new Pro ... );\\n }) | promises.js:23:18:25:3 | (v) => ... v;\\n } | | promises.js:10:18:17:4 | new Pro ... );\\n }) | promises.js:26:20:28:3 | (v) => ... v;\\n } | test_PromiseDefinition_getExecutor +| flow.js:7:11:7:59 | new Pro ... ource)) | flow.js:7:23:7:58 | (resolv ... source) | +| flow.js:10:11:10:58 | new Pro ... ource)) | flow.js:10:23:10:57 | (resolv ... source) | +| flow.js:13:11:13:58 | new Pro ... ource)) | flow.js:13:23:13:57 | (resolv ... source) | +| flow.js:24:2:24:49 | new Pro ... ource)) | flow.js:24:14:24:48 | (resolv ... source) | +| flow.js:26:2:26:49 | new Pro ... ource)) | flow.js:26:14:26:48 | (resolv ... source) | +| flow.js:32:2:32:49 | new Pro ... ource)) | flow.js:32:14:32:48 | (resolv ... source) | +| flow.js:40:2:40:49 | new Pro ... ource)) | flow.js:40:14:40:48 | (resolv ... source) | +| flow.js:42:2:42:49 | new Pro ... ource)) | flow.js:42:14:42:48 | (resolv ... source) | +| flow.js:48:2:48:36 | new Pro ... urce }) | flow.js:48:14:48:35 | () => { ... ource } | +| flow.js:55:11:55:58 | new Pro ... ource)) | flow.js:55:23:55:57 | (resolv ... source) | +| flow.js:60:12:60:59 | new Pro ... ource)) | flow.js:60:24:60:58 | (resolv ... source) | +| flow.js:65:9:65:56 | new Pro ... ource)) | flow.js:65:21:65:55 | (resolv ... source) | +| flow.js:74:10:74:57 | new Pro ... ource)) | flow.js:74:22:74:56 | (resolv ... source) | +| flow.js:86:23:86:70 | new Pro ... ource)) | flow.js:86:35:86:69 | (resolv ... source) | +| flow.js:91:21:91:68 | new Pro ... ource)) | flow.js:91:33:91:67 | (resolv ... source) | +| flow.js:100:28:100:75 | new Pro ... ource)) | flow.js:100:40:100:74 | (resolv ... source) | +| flow.js:103:2:103:48 | new Pro ... "BLA")) | flow.js:103:14:103:47 | (resolv ... ("BLA") | +| flow.js:105:2:105:48 | new Pro ... "BLA")) | flow.js:105:14:105:47 | (resolv ... ("BLA") | +| flow.js:107:17:107:64 | new Pro ... ource)) | flow.js:107:29:107:63 | (resolv ... source) | +| flow.js:109:2:109:48 | new Pro ... "BLA")) | flow.js:109:14:109:47 | (resolv ... ("BLA") | +| flow.js:111:2:111:48 | new Pro ... "BLA")) | flow.js:111:14:111:47 | (resolv ... ("BLA") | +| flow.js:113:2:113:48 | new Pro ... "BLA")) | flow.js:113:14:113:47 | (resolv ... ("BLA") | +| flow.js:117:2:117:48 | new Pro ... "BLA")) | flow.js:117:14:117:47 | (resolv ... ("BLA") | +| flow.js:119:2:119:48 | new Pro ... "BLA")) | flow.js:119:14:119:47 | (resolv ... ("BLA") | +| flow.js:129:2:129:52 | new Pro ... olved)) | flow.js:129:14:129:51 | (resolv ... solved) | +| interflow.js:11:12:15:6 | new Pro ... \\n }) | interflow.js:11:24:15:5 | functio ... ;\\n } | | promises.js:3:17:5:4 | new Pro ... );\\n }) | promises.js:3:29:5:3 | functio ... e);\\n } | | promises.js:10:18:17:4 | new Pro ... );\\n }) | promises.js:10:30:17:3 | (res, r ... e);\\n } | | promises.js:33:19:35:6 | new Pro ... \\n }) | promises.js:33:31:35:5 | functio ... ;\\n } | | promises.js:43:19:45:6 | Q.Promi ... \\n }) | promises.js:43:29:45:5 | functio ... ;\\n } | test_PromiseDefinition_getAFinallyHandler +| flow.js:105:2:105:48 | new Pro ... "BLA")) | flow.js:105:58:105:76 | x => {throw source} | +| flow.js:109:2:109:48 | new Pro ... "BLA")) | flow.js:109:58:109:70 | x => rejected | | promises.js:10:18:17:4 | new Pro ... );\\n }) | promises.js:26:20:28:3 | (v) => ... v;\\n } | test_PromiseDefinition +| flow.js:7:11:7:59 | new Pro ... ource)) | +| flow.js:10:11:10:58 | new Pro ... ource)) | +| flow.js:13:11:13:58 | new Pro ... ource)) | +| flow.js:24:2:24:49 | new Pro ... ource)) | +| flow.js:26:2:26:49 | new Pro ... ource)) | +| flow.js:32:2:32:49 | new Pro ... ource)) | +| flow.js:40:2:40:49 | new Pro ... ource)) | +| flow.js:42:2:42:49 | new Pro ... ource)) | +| flow.js:48:2:48:36 | new Pro ... urce }) | +| flow.js:55:11:55:58 | new Pro ... ource)) | +| flow.js:60:12:60:59 | new Pro ... ource)) | +| flow.js:65:9:65:56 | new Pro ... ource)) | +| flow.js:74:10:74:57 | new Pro ... ource)) | +| flow.js:86:23:86:70 | new Pro ... ource)) | +| flow.js:91:21:91:68 | new Pro ... ource)) | +| flow.js:100:28:100:75 | new Pro ... ource)) | +| flow.js:103:2:103:48 | new Pro ... "BLA")) | +| flow.js:105:2:105:48 | new Pro ... "BLA")) | +| flow.js:107:17:107:64 | new Pro ... ource)) | +| flow.js:109:2:109:48 | new Pro ... "BLA")) | +| flow.js:111:2:111:48 | new Pro ... "BLA")) | +| flow.js:113:2:113:48 | new Pro ... "BLA")) | +| flow.js:117:2:117:48 | new Pro ... "BLA")) | +| flow.js:119:2:119:48 | new Pro ... "BLA")) | +| flow.js:129:2:129:52 | new Pro ... olved)) | +| interflow.js:11:12:15:6 | new Pro ... \\n }) | | promises.js:3:17:5:4 | new Pro ... );\\n }) | | promises.js:10:18:17:4 | new Pro ... );\\n }) | | promises.js:33:19:35:6 | new Pro ... \\n }) | | promises.js:43:19:45:6 | Q.Promi ... \\n }) | test_PromiseDefinition_getAResolveHandler +| flow.js:24:2:24:49 | new Pro ... ource)) | flow.js:24:56:24:67 | x => sink(x) | +| flow.js:26:2:26:49 | new Pro ... ource)) | flow.js:26:56:26:66 | x => foo(x) | +| flow.js:40:2:40:49 | new Pro ... ource)) | flow.js:40:56:40:64 | () => { } | +| flow.js:42:2:42:49 | new Pro ... ource)) | flow.js:42:56:42:64 | () => { } | +| flow.js:55:11:55:58 | new Pro ... ource)) | flow.js:56:19:56:26 | () => {} | +| flow.js:60:12:60:59 | new Pro ... ource)) | flow.js:61:21:61:28 | () => {} | +| flow.js:74:10:74:57 | new Pro ... ource)) | flow.js:74:64:74:71 | () => {} | +| flow.js:91:21:91:68 | new Pro ... ource)) | flow.js:91:75:91:82 | () => {} | +| flow.js:105:2:105:48 | new Pro ... "BLA")) | flow.js:105:58:105:76 | x => {throw source} | +| flow.js:109:2:109:48 | new Pro ... "BLA")) | flow.js:109:58:109:70 | x => rejected | +| flow.js:129:2:129:52 | new Pro ... olved)) | flow.js:129:59:129:70 | x => sink(x) | | promises.js:3:17:5:4 | new Pro ... );\\n }) | promises.js:6:16:8:3 | functio ... al;\\n } | | promises.js:10:18:17:4 | new Pro ... );\\n }) | promises.js:18:17:20:3 | (v) => ... v;\\n } | | promises.js:10:18:17:4 | new Pro ... );\\n }) | promises.js:26:20:28:3 | (v) => ... v;\\n } | | promises.js:33:19:35:6 | new Pro ... \\n }) | promises.js:36:18:38:5 | functio ... ;\\n } | | promises.js:43:19:45:6 | Q.Promi ... \\n }) | promises.js:46:18:48:5 | functio ... ;\\n } | test_PromiseDefinition_getRejectParameter +| flow.js:7:11:7:59 | new Pro ... ource)) | flow.js:7:33:7:38 | reject | +| flow.js:10:11:10:58 | new Pro ... ource)) | flow.js:10:33:10:38 | reject | +| flow.js:13:11:13:58 | new Pro ... ource)) | flow.js:13:33:13:38 | reject | +| flow.js:24:2:24:49 | new Pro ... ource)) | flow.js:24:24:24:29 | reject | +| flow.js:26:2:26:49 | new Pro ... ource)) | flow.js:26:24:26:29 | reject | +| flow.js:32:2:32:49 | new Pro ... ource)) | flow.js:32:24:32:29 | reject | +| flow.js:40:2:40:49 | new Pro ... ource)) | flow.js:40:24:40:29 | reject | +| flow.js:42:2:42:49 | new Pro ... ource)) | flow.js:42:24:42:29 | reject | +| flow.js:55:11:55:58 | new Pro ... ource)) | flow.js:55:33:55:38 | reject | +| flow.js:60:12:60:59 | new Pro ... ource)) | flow.js:60:34:60:39 | reject | +| flow.js:65:9:65:56 | new Pro ... ource)) | flow.js:65:31:65:36 | reject | +| flow.js:74:10:74:57 | new Pro ... ource)) | flow.js:74:32:74:37 | reject | +| flow.js:86:23:86:70 | new Pro ... ource)) | flow.js:86:45:86:50 | reject | +| flow.js:91:21:91:68 | new Pro ... ource)) | flow.js:91:43:91:48 | reject | +| flow.js:100:28:100:75 | new Pro ... ource)) | flow.js:100:50:100:55 | reject | +| flow.js:103:2:103:48 | new Pro ... "BLA")) | flow.js:103:24:103:29 | reject | +| flow.js:105:2:105:48 | new Pro ... "BLA")) | flow.js:105:24:105:29 | reject | +| flow.js:107:17:107:64 | new Pro ... ource)) | flow.js:107:39:107:44 | reject | +| flow.js:109:2:109:48 | new Pro ... "BLA")) | flow.js:109:24:109:29 | reject | +| flow.js:111:2:111:48 | new Pro ... "BLA")) | flow.js:111:24:111:29 | reject | +| flow.js:113:2:113:48 | new Pro ... "BLA")) | flow.js:113:24:113:29 | reject | +| flow.js:117:2:117:48 | new Pro ... "BLA")) | flow.js:117:24:117:29 | reject | +| flow.js:119:2:119:48 | new Pro ... "BLA")) | flow.js:119:24:119:29 | reject | +| flow.js:129:2:129:52 | new Pro ... olved)) | flow.js:129:24:129:29 | reject | +| interflow.js:11:12:15:6 | new Pro ... \\n }) | interflow.js:11:43:11:48 | reject | | promises.js:3:17:5:4 | new Pro ... );\\n }) | promises.js:3:48:3:53 | reject | | promises.js:10:18:17:4 | new Pro ... );\\n }) | promises.js:10:36:10:38 | rej | | promises.js:33:19:35:6 | new Pro ... \\n }) | promises.js:33:50:33:55 | reject | | promises.js:43:19:45:6 | Q.Promi ... \\n }) | promises.js:43:48:43:53 | reject | test_PromiseDefinition_getResolveParameter +| flow.js:7:11:7:59 | new Pro ... ource)) | flow.js:7:24:7:30 | resolve | +| flow.js:10:11:10:58 | new Pro ... ource)) | flow.js:10:24:10:30 | resolve | +| flow.js:13:11:13:58 | new Pro ... ource)) | flow.js:13:24:13:30 | resolve | +| flow.js:24:2:24:49 | new Pro ... ource)) | flow.js:24:15:24:21 | resolve | +| flow.js:26:2:26:49 | new Pro ... ource)) | flow.js:26:15:26:21 | resolve | +| flow.js:32:2:32:49 | new Pro ... ource)) | flow.js:32:15:32:21 | resolve | +| flow.js:40:2:40:49 | new Pro ... ource)) | flow.js:40:15:40:21 | resolve | +| flow.js:42:2:42:49 | new Pro ... ource)) | flow.js:42:15:42:21 | resolve | +| flow.js:55:11:55:58 | new Pro ... ource)) | flow.js:55:24:55:30 | resolve | +| flow.js:60:12:60:59 | new Pro ... ource)) | flow.js:60:25:60:31 | resolve | +| flow.js:65:9:65:56 | new Pro ... ource)) | flow.js:65:22:65:28 | resolve | +| flow.js:74:10:74:57 | new Pro ... ource)) | flow.js:74:23:74:29 | resolve | +| flow.js:86:23:86:70 | new Pro ... ource)) | flow.js:86:36:86:42 | resolve | +| flow.js:91:21:91:68 | new Pro ... ource)) | flow.js:91:34:91:40 | resolve | +| flow.js:100:28:100:75 | new Pro ... ource)) | flow.js:100:41:100:47 | resolve | +| flow.js:103:2:103:48 | new Pro ... "BLA")) | flow.js:103:15:103:21 | resolve | +| flow.js:105:2:105:48 | new Pro ... "BLA")) | flow.js:105:15:105:21 | resolve | +| flow.js:107:17:107:64 | new Pro ... ource)) | flow.js:107:30:107:36 | resolve | +| flow.js:109:2:109:48 | new Pro ... "BLA")) | flow.js:109:15:109:21 | resolve | +| flow.js:111:2:111:48 | new Pro ... "BLA")) | flow.js:111:15:111:21 | resolve | +| flow.js:113:2:113:48 | new Pro ... "BLA")) | flow.js:113:15:113:21 | resolve | +| flow.js:117:2:117:48 | new Pro ... "BLA")) | flow.js:117:15:117:21 | resolve | +| flow.js:119:2:119:48 | new Pro ... "BLA")) | flow.js:119:15:119:21 | resolve | +| flow.js:129:2:129:52 | new Pro ... olved)) | flow.js:129:15:129:21 | resolve | +| interflow.js:11:12:15:6 | new Pro ... \\n }) | interflow.js:11:34:11:40 | resolve | | promises.js:3:17:5:4 | new Pro ... );\\n }) | promises.js:3:39:3:45 | resolve | | promises.js:10:18:17:4 | new Pro ... );\\n }) | promises.js:10:31:10:33 | res | | promises.js:33:19:35:6 | new Pro ... \\n }) | promises.js:33:41:33:47 | resolve | | promises.js:43:19:45:6 | Q.Promi ... \\n }) | promises.js:43:39:43:45 | resolve | test_PromiseDefinition_getACatchHandler +| flow.js:32:2:32:49 | new Pro ... ource)) | flow.js:32:57:32:68 | x => sink(x) | +| flow.js:48:2:48:36 | new Pro ... urce }) | flow.js:48:44:48:55 | x => sink(x) | +| flow.js:103:2:103:48 | new Pro ... "BLA")) | flow.js:103:56:103:75 | x => {return source} | +| flow.js:111:2:111:48 | new Pro ... "BLA")) | flow.js:111:56:111:68 | x => rejected | +| flow.js:113:2:113:48 | new Pro ... "BLA")) | flow.js:113:56:113:68 | x => rejected | +| flow.js:117:2:117:48 | new Pro ... "BLA")) | flow.js:117:56:117:68 | x => resolved | +| flow.js:119:2:119:48 | new Pro ... "BLA")) | flow.js:119:56:119:68 | x => resolved | | promises.js:10:18:17:4 | new Pro ... );\\n }) | promises.js:23:18:25:3 | (v) => ... v;\\n } | +flow +| flow.js:2:15:2:22 | "source" | flow.js:5:7:5:14 | await p1 | +| flow.js:2:15:2:22 | "source" | flow.js:8:7:8:14 | await p2 | +| flow.js:2:15:2:22 | "source" | flow.js:17:8:17:8 | e | +| flow.js:2:15:2:22 | "source" | flow.js:20:41:20:41 | x | +| flow.js:2:15:2:22 | "source" | flow.js:26:79:26:79 | y | +| flow.js:2:15:2:22 | "source" | flow.js:28:58:28:58 | z | +| flow.js:2:15:2:22 | "source" | flow.js:32:67:32:67 | x | +| flow.js:2:15:2:22 | "source" | flow.js:34:58:34:58 | a | +| flow.js:2:15:2:22 | "source" | flow.js:38:29:38:29 | a | +| flow.js:2:15:2:22 | "source" | flow.js:40:83:40:83 | x | +| flow.js:2:15:2:22 | "source" | flow.js:44:92:44:92 | a | +| flow.js:2:15:2:22 | "source" | flow.js:46:60:46:60 | a | +| flow.js:2:15:2:22 | "source" | flow.js:48:54:48:54 | x | +| flow.js:2:15:2:22 | "source" | flow.js:53:39:53:39 | v | +| flow.js:2:15:2:22 | "source" | flow.js:58:24:58:24 | x | +| flow.js:2:15:2:22 | "source" | flow.js:62:22:62:22 | x | +| flow.js:2:15:2:22 | "source" | flow.js:70:8:70:8 | e | +| flow.js:2:15:2:22 | "source" | flow.js:76:50:76:50 | e | +| flow.js:2:15:2:22 | "source" | flow.js:79:20:79:20 | x | +| flow.js:2:15:2:22 | "source" | flow.js:84:21:84:21 | e | +| flow.js:2:15:2:22 | "source" | flow.js:89:45:89:45 | e | +| flow.js:2:15:2:22 | "source" | flow.js:101:7:101:9 | foo | +| flow.js:2:15:2:22 | "source" | flow.js:103:93:103:93 | x | +| flow.js:2:15:2:22 | "source" | flow.js:105:95:105:95 | x | +| flow.js:2:15:2:22 | "source" | flow.js:109:89:109:89 | x | +| flow.js:2:15:2:22 | "source" | flow.js:113:87:113:87 | x | +| flow.js:2:15:2:22 | "source" | flow.js:119:86:119:86 | x | +| flow.js:2:15:2:22 | "source" | flow.js:123:58:123:58 | x | +| flow.js:2:15:2:22 | "source" | flow.js:125:59:125:59 | x | +| flow.js:2:15:2:22 | "source" | flow.js:129:69:129:69 | x | +| flow.js:2:15:2:22 | "source" | flow.js:131:43:131:43 | x | +exclusiveTaintFlow +| interflow.js:3:18:3:25 | "source" | interflow.js:18:10:18:14 | error | diff --git a/javascript/ql/test/library-tests/Promises/tests.ql b/javascript/ql/test/library-tests/Promises/tests.ql index 6b29dc01fa3..2490f378970 100644 --- a/javascript/ql/test/library-tests/Promises/tests.ql +++ b/javascript/ql/test/library-tests/Promises/tests.ql @@ -7,3 +7,4 @@ import PromiseDefinition_getAResolveHandler import PromiseDefinition_getRejectParameter import PromiseDefinition_getResolveParameter import PromiseDefinition_getACatchHandler +import flow \ No newline at end of file diff --git a/javascript/ql/test/library-tests/StringConcatenation/ClassContainsTwo.expected b/javascript/ql/test/library-tests/StringConcatenation/ClassContainsTwo.expected index ffdaf780711..db21cc79dd2 100644 --- a/javascript/ql/test/library-tests/StringConcatenation/ClassContainsTwo.expected +++ b/javascript/ql/test/library-tests/StringConcatenation/ClassContainsTwo.expected @@ -42,3 +42,7 @@ | tst.js:89:3:89:3 | x | | tst.js:89:3:89:14 | x += 'three' | | tst.js:90:10:90:10 | x | +| tst.js:95:3:95:30 | x = x.c ... three') | +| tst.js:95:7:95:30 | x.conca ... three') | +| tst.js:95:16:95:20 | 'two' | +| tst.js:96:10:96:10 | x | diff --git a/javascript/ql/test/library-tests/StringConcatenation/ContainsTwo.expected b/javascript/ql/test/library-tests/StringConcatenation/ContainsTwo.expected index ffdaf780711..db21cc79dd2 100644 --- a/javascript/ql/test/library-tests/StringConcatenation/ContainsTwo.expected +++ b/javascript/ql/test/library-tests/StringConcatenation/ContainsTwo.expected @@ -42,3 +42,7 @@ | tst.js:89:3:89:3 | x | | tst.js:89:3:89:14 | x += 'three' | | tst.js:90:10:90:10 | x | +| tst.js:95:3:95:30 | x = x.c ... three') | +| tst.js:95:7:95:30 | x.conca ... three') | +| tst.js:95:16:95:20 | 'two' | +| tst.js:96:10:96:10 | x | diff --git a/javascript/ql/test/library-tests/StringConcatenation/StringOps.expected b/javascript/ql/test/library-tests/StringConcatenation/StringOps.expected index 695957924d4..0afbad59a08 100644 --- a/javascript/ql/test/library-tests/StringConcatenation/StringOps.expected +++ b/javascript/ql/test/library-tests/StringConcatenation/StringOps.expected @@ -45,6 +45,7 @@ concatenation | tst.js:87:5:87:14 | x += 'two' | | tst.js:89:3:89:14 | x | | tst.js:89:3:89:14 | x += 'three' | +| tst.js:95:7:95:30 | x.conca ... three') | concatenationOperand | closure.js:5:1:5:37 | build(' ... 'four') | | closure.js:5:7:5:11 | 'one' | @@ -123,6 +124,9 @@ concatenationOperand | tst.js:87:10:87:14 | 'two' | | tst.js:89:3:89:3 | x | | tst.js:89:8:89:14 | 'three' | +| tst.js:95:7:95:7 | x | +| tst.js:95:16:95:20 | 'two' | +| tst.js:95:23:95:29 | 'three' | concatenationLeaf | closure.js:5:7:5:11 | 'one' | | closure.js:5:14:5:18 | 'two' | @@ -192,6 +196,9 @@ concatenationLeaf | tst.js:87:10:87:14 | 'two' | | tst.js:89:3:89:3 | x | | tst.js:89:8:89:14 | 'three' | +| tst.js:95:7:95:7 | x | +| tst.js:95:16:95:20 | 'two' | +| tst.js:95:23:95:29 | 'three' | concatenationNode | closure.js:5:1:5:37 | build(' ... 'four') | | closure.js:5:1:5:46 | build(' ... 'five' | @@ -307,6 +314,10 @@ concatenationNode | tst.js:89:3:89:14 | x | | tst.js:89:3:89:14 | x += 'three' | | tst.js:89:8:89:14 | 'three' | +| tst.js:95:7:95:7 | x | +| tst.js:95:7:95:30 | x.conca ... three') | +| tst.js:95:16:95:20 | 'two' | +| tst.js:95:23:95:29 | 'three' | operand | closure.js:5:1:5:37 | build(' ... 'four') | 0 | closure.js:5:7:5:11 | 'one' | | closure.js:5:1:5:37 | build(' ... 'four') | 1 | closure.js:5:14:5:28 | 'two' + 'three' | @@ -407,6 +418,9 @@ operand | tst.js:89:3:89:14 | x | 1 | tst.js:89:8:89:14 | 'three' | | tst.js:89:3:89:14 | x += 'three' | 0 | tst.js:89:3:89:3 | x | | tst.js:89:3:89:14 | x += 'three' | 1 | tst.js:89:8:89:14 | 'three' | +| tst.js:95:7:95:30 | x.conca ... three') | 0 | tst.js:95:7:95:7 | x | +| tst.js:95:7:95:30 | x.conca ... three') | 1 | tst.js:95:16:95:20 | 'two' | +| tst.js:95:7:95:30 | x.conca ... three') | 2 | tst.js:95:23:95:29 | 'three' | nextLeaf | closure.js:5:7:5:11 | 'one' | closure.js:5:14:5:18 | 'two' | | closure.js:5:14:5:18 | 'two' | closure.js:5:22:5:28 | 'three' | @@ -450,6 +464,8 @@ nextLeaf | tst.js:61:27:61:27 | x | tst.js:61:29:61:33 | last | | tst.js:87:5:87:5 | x | tst.js:87:10:87:14 | 'two' | | tst.js:89:3:89:3 | x | tst.js:89:8:89:14 | 'three' | +| tst.js:95:7:95:7 | x | tst.js:95:16:95:20 | 'two' | +| tst.js:95:16:95:20 | 'two' | tst.js:95:23:95:29 | 'three' | htmlRoot | html-concat.js:2:14:2:26 | `${x}` | | html-concat.js:3:14:3:26 | `${x}` | diff --git a/javascript/ql/test/library-tests/StringConcatenation/tst.js b/javascript/ql/test/library-tests/StringConcatenation/tst.js index e3f945a6fdd..d1e70fb7239 100644 --- a/javascript/ql/test/library-tests/StringConcatenation/tst.js +++ b/javascript/ql/test/library-tests/StringConcatenation/tst.js @@ -89,3 +89,13 @@ function addExprPhi(b) { x += 'three'; return x; } + +function concatCall() { + let x = 'one'; + x = x.concat('two', 'three'); + return x; +} + +function arrayConcat(a, b) { + return [].concat(a, b); +} diff --git a/javascript/ql/test/library-tests/StringOps/StartsWith/StartsWith.expected b/javascript/ql/test/library-tests/StringOps/StartsWith/StartsWith.expected index 3910799bd75..d072e023cd2 100644 --- a/javascript/ql/test/library-tests/StringOps/StartsWith/StartsWith.expected +++ b/javascript/ql/test/library-tests/StringOps/StartsWith/StartsWith.expected @@ -14,3 +14,5 @@ | tst.js:19:9:19:36 | A.subst ... "web/" | tst.js:19:9:19:9 | A | tst.js:19:31:19:36 | "web/" | true | | tst.js:32:9:32:32 | strings ... h(A, B) | tst.js:32:28:32:28 | A | tst.js:32:31:32:31 | B | true | | tst.js:33:9:33:47 | strings ... h(A, B) | tst.js:33:43:33:43 | A | tst.js:33:46:33:46 | B | true | +| tst.js:34:9:34:34 | A.slice ... ) !== B | tst.js:34:9:34:9 | A | tst.js:34:34:34:34 | B | false | +| tst.js:35:9:35:42 | A.slice ... = B.foo | tst.js:35:9:35:9 | A | tst.js:35:38:35:42 | B.foo | false | diff --git a/javascript/ql/test/library-tests/StringOps/StartsWith/tst.js b/javascript/ql/test/library-tests/StringOps/StartsWith/tst.js index 97bbf240ec6..0616718c9f3 100644 --- a/javascript/ql/test/library-tests/StringOps/StartsWith/tst.js +++ b/javascript/ql/test/library-tests/StringOps/StartsWith/tst.js @@ -31,4 +31,6 @@ function f(A, B) { if (strings.startsWith(A, B)) {} if (strings.caseInsensitiveStartsWith(A, B)) {} + if (A.slice(0, B.length) !== B) {} + if (A.slice(0, B.foo.length) !== B.foo) {} } diff --git a/javascript/ql/test/library-tests/TaintTracking/BasicTaintTracking.expected b/javascript/ql/test/library-tests/TaintTracking/BasicTaintTracking.expected index da4105238bc..22c9a6c4576 100644 --- a/javascript/ql/test/library-tests/TaintTracking/BasicTaintTracking.expected +++ b/javascript/ql/test/library-tests/TaintTracking/BasicTaintTracking.expected @@ -59,6 +59,7 @@ typeInferenceMismatch | exceptions.js:144:9:144:16 | source() | exceptions.js:132:8:132:27 | returnThrownSource() | | exceptions.js:150:13:150:20 | source() | exceptions.js:153:10:153:10 | e | | exceptions.js:158:13:158:20 | source() | exceptions.js:161:10:161:10 | e | +| importedReactComponent.jsx:4:40:4:47 | source() | exportedReactComponent.jsx:2:10:2:19 | props.text | | indexOf.js:4:11:4:18 | source() | indexOf.js:9:10:9:10 | x | | partialCalls.js:4:17:4:24 | source() | partialCalls.js:17:14:17:14 | x | | partialCalls.js:4:17:4:24 | source() | partialCalls.js:20:14:20:14 | y | diff --git a/javascript/ql/test/library-tests/TaintTracking/exportedReactComponent.jsx b/javascript/ql/test/library-tests/TaintTracking/exportedReactComponent.jsx new file mode 100644 index 00000000000..6489e09a1e5 --- /dev/null +++ b/javascript/ql/test/library-tests/TaintTracking/exportedReactComponent.jsx @@ -0,0 +1,4 @@ +export function UnsafeReactComponent(props) { + sink(props.text); + return

    +} diff --git a/javascript/ql/test/library-tests/TaintTracking/importedReactComponent.jsx b/javascript/ql/test/library-tests/TaintTracking/importedReactComponent.jsx new file mode 100644 index 00000000000..5bef649e05d --- /dev/null +++ b/javascript/ql/test/library-tests/TaintTracking/importedReactComponent.jsx @@ -0,0 +1,5 @@ +import { UnsafeReactComponent } from "./exportedReactComponent"; + +export function render() { + return +} diff --git a/javascript/ql/test/library-tests/TypeScript/PathMapping/Imports.expected b/javascript/ql/test/library-tests/TypeScript/PathMapping/Imports.expected new file mode 100644 index 00000000000..c608ea2c7b6 --- /dev/null +++ b/javascript/ql/test/library-tests/TypeScript/PathMapping/Imports.expected @@ -0,0 +1,11 @@ +symbols +| src/lib/foo.ts:1:1:4:0 | | library-tests/TypeScript/PathMapping/src/lib/foo.ts | +| src/lib/foo.ts:1:8:3:1 | functio ... 123;\\n} | foo in library-tests/TypeScript/PathMapping/src/lib/foo.ts | +| test/test_foo.ts:1:1:1:28 | import ... @/foo"; | library-tests/TypeScript/PathMapping/src/lib/foo.ts | +| test/test_foo.ts:1:1:7:0 | | library-tests/TypeScript/PathMapping/test/test_foo.ts | +| test/test_foo.ts:2:17:2:32 | require("@/foo") | library-tests/TypeScript/PathMapping/src/lib/foo.ts | +| test/test_foo.ts:4:1:4:5 | foo() | foo in library-tests/TypeScript/PathMapping/src/lib/foo.ts | +| test/test_foo.ts:6:1:6:12 | foolib.foo() | foo in library-tests/TypeScript/PathMapping/src/lib/foo.ts | +#select +| test/test_foo.ts:1:1:1:28 | import ... @/foo"; | src/lib/foo.ts:1:1:4:0 | | +| test/test_foo.ts:2:17:2:32 | require("@/foo") | src/lib/foo.ts:1:1:4:0 | | diff --git a/javascript/ql/test/library-tests/TypeScript/PathMapping/Imports.ql b/javascript/ql/test/library-tests/TypeScript/PathMapping/Imports.ql new file mode 100644 index 00000000000..bd272eee884 --- /dev/null +++ b/javascript/ql/test/library-tests/TypeScript/PathMapping/Imports.ql @@ -0,0 +1,8 @@ +import javascript + +query predicate symbols(ASTNode astNode, CanonicalName symbol) { + ast_node_symbol(astNode, symbol) +} + +from Import imprt +select imprt, imprt.getImportedModule() diff --git a/javascript/ql/test/library-tests/TypeScript/PathMapping/src/lib/foo.ts b/javascript/ql/test/library-tests/TypeScript/PathMapping/src/lib/foo.ts new file mode 100644 index 00000000000..0ef2bf692fd --- /dev/null +++ b/javascript/ql/test/library-tests/TypeScript/PathMapping/src/lib/foo.ts @@ -0,0 +1,3 @@ +export function foo() { + return 123; +} diff --git a/javascript/ql/test/library-tests/TypeScript/PathMapping/test/test_foo.ts b/javascript/ql/test/library-tests/TypeScript/PathMapping/test/test_foo.ts new file mode 100644 index 00000000000..d30f56c53e7 --- /dev/null +++ b/javascript/ql/test/library-tests/TypeScript/PathMapping/test/test_foo.ts @@ -0,0 +1,6 @@ +import { foo } from "@/foo"; +import foolib = require("@/foo"); + +foo(); + +foolib.foo(); diff --git a/javascript/ql/test/library-tests/TypeScript/PathMapping/tsconfig.json b/javascript/ql/test/library-tests/TypeScript/PathMapping/tsconfig.json new file mode 100644 index 00000000000..476d1ad1ee5 --- /dev/null +++ b/javascript/ql/test/library-tests/TypeScript/PathMapping/tsconfig.json @@ -0,0 +1,9 @@ +{ + "include": ["."], + "compilerOptions": { + "baseUrl": ".", + "paths": { + "@/*": ["./src/lib/*"] + } + } +} diff --git a/javascript/ql/test/library-tests/frameworks/EventEmitter/customEmitter.js b/javascript/ql/test/library-tests/frameworks/EventEmitter/customEmitter.js new file mode 100644 index 00000000000..fe3dafd31a1 --- /dev/null +++ b/javascript/ql/test/library-tests/frameworks/EventEmitter/customEmitter.js @@ -0,0 +1,22 @@ +const EventEmitter = require("events"); + +class MyEmitter extends EventEmitter { + foo() { + this.emit("foo", "bar"); + this.on("foo", (data) => {}); + } +} + +class MySecondEmitter extends EventEmitter { + foo() { + this.emit("bar", "baz"); + this.on("bar", (data) => {}); + } +} + +var x = new MySecondEmitter(); +x.emit("bar", "baz2"); + +var y = new MySecondEmitter(); +y.emit("bar", "baz3"); +y.on("bar", (yData) => {}) \ No newline at end of file diff --git a/javascript/ql/test/library-tests/frameworks/EventEmitter/test.expected b/javascript/ql/test/library-tests/frameworks/EventEmitter/test.expected index 33fa8385978..c8526d5eecf 100644 --- a/javascript/ql/test/library-tests/frameworks/EventEmitter/test.expected +++ b/javascript/ql/test/library-tests/frameworks/EventEmitter/test.expected @@ -1,3 +1,9 @@ +| customEmitter.js:5:20:5:24 | "bar" | customEmitter.js:6:19:6:22 | data | +| customEmitter.js:12:21:12:25 | "baz" | customEmitter.js:13:23:13:26 | data | +| customEmitter.js:12:21:12:25 | "baz" | customEmitter.js:22:14:22:18 | yData | +| customEmitter.js:18:15:18:20 | "baz2" | customEmitter.js:13:23:13:26 | data | +| customEmitter.js:21:15:21:20 | "baz3" | customEmitter.js:13:23:13:26 | data | +| customEmitter.js:21:15:21:20 | "baz3" | customEmitter.js:22:14:22:18 | yData | | tst.js:9:23:9:33 | 'FirstData' | tst.js:6:40:6:44 | first | | tst.js:10:24:10:35 | 'SecondData' | tst.js:7:32:7:37 | second | | tst.js:15:24:15:39 | 'OtherFirstData' | tst.js:14:41:14:50 | otherFirst | diff --git a/javascript/ql/test/library-tests/frameworks/ReactJS/exportedComponent.jsx b/javascript/ql/test/library-tests/frameworks/ReactJS/exportedComponent.jsx new file mode 100644 index 00000000000..4335b4bc308 --- /dev/null +++ b/javascript/ql/test/library-tests/frameworks/ReactJS/exportedComponent.jsx @@ -0,0 +1,3 @@ +export function MyComponent(props) { + return
    +} diff --git a/javascript/ql/test/library-tests/frameworks/ReactJS/importedComponent.jsx b/javascript/ql/test/library-tests/frameworks/ReactJS/importedComponent.jsx new file mode 100644 index 00000000000..edc333528a7 --- /dev/null +++ b/javascript/ql/test/library-tests/frameworks/ReactJS/importedComponent.jsx @@ -0,0 +1,5 @@ +import { MyComponent } from "./exportedComponent"; + +export function render(color) { + return +} diff --git a/javascript/ql/test/library-tests/frameworks/ReactJS/tests.expected b/javascript/ql/test/library-tests/frameworks/ReactJS/tests.expected index 9b737cf1ea3..93e4d0d258a 100644 --- a/javascript/ql/test/library-tests/frameworks/ReactJS/tests.expected +++ b/javascript/ql/test/library-tests/frameworks/ReactJS/tests.expected @@ -15,6 +15,7 @@ test_ReactComponent_getInstanceMethod | es5.js:1:31:11:1 | {\\n dis ... ;\\n }\\n} | render | es5.js:3:11:5:3 | functio ... v>;\\n } | | es5.js:18:33:22:1 | {\\n ren ... ;\\n }\\n} | render | es5.js:19:11:21:3 | functio ... 1>;\\n } | | es6.js:1:1:8:1 | class H ... ;\\n }\\n} | render | es6.js:2:9:4:3 | () {\\n ... v>;\\n } | +| exportedComponent.jsx:1:8:3:1 | functio ... r}}/>\\n} | render | exportedComponent.jsx:1:8:3:1 | functio ... r}}/>\\n} | | plainfn.js:1:1:3:1 | functio ... div>;\\n} | render | plainfn.js:1:1:3:1 | functio ... div>;\\n} | | plainfn.js:5:1:7:1 | functio ... iv");\\n} | render | plainfn.js:5:1:7:1 | functio ... iv");\\n} | | plainfn.js:9:1:12:1 | functio ... rn x;\\n} | render | plainfn.js:9:1:12:1 | functio ... rn x;\\n} | @@ -93,6 +94,7 @@ test_ReactComponent_ref | es6.js:14:1:20:1 | class H ... }\\n} | es6.js:16:9:16:12 | this | | es6.js:14:1:20:1 | class H ... }\\n} | es6.js:17:9:17:12 | this | | es6.js:14:1:20:1 | class H ... }\\n} | es6.js:18:9:18:12 | this | +| exportedComponent.jsx:1:8:3:1 | functio ... r}}/>\\n} | exportedComponent.jsx:1:8:1:7 | this | | namedImport.js:3:1:3:28 | class C ... nent {} | namedImport.js:3:27:3:26 | this | | namedImport.js:5:1:5:20 | class D extends C {} | namedImport.js:5:19:5:18 | this | | plainfn.js:1:1:3:1 | functio ... div>;\\n} | plainfn.js:1:1:1:0 | this | @@ -189,6 +191,7 @@ test_ReactComponent_getADirectPropsSource | es5.js:18:33:22:1 | {\\n ren ... ;\\n }\\n} | es5.js:20:24:20:33 | this.props | | es6.js:1:1:8:1 | class H ... ;\\n }\\n} | es6.js:1:37:1:36 | args | | es6.js:1:1:8:1 | class H ... ;\\n }\\n} | es6.js:3:24:3:33 | this.props | +| exportedComponent.jsx:1:8:3:1 | functio ... r}}/>\\n} | exportedComponent.jsx:1:29:1:33 | props | | namedImport.js:3:1:3:28 | class C ... nent {} | namedImport.js:3:27:3:26 | args | | namedImport.js:5:1:5:20 | class D extends C {} | namedImport.js:5:19:5:18 | args | | plainfn.js:1:1:3:1 | functio ... div>;\\n} | plainfn.js:1:16:1:20 | props | @@ -208,6 +211,7 @@ test_ReactComponent_getADirectPropsSource | thisAccesses.js:47:1:52:1 | class C ... }\\n} | thisAccesses.js:48:18:48:18 | y | test_ReactComponent_getACandidatePropsValue | es5.js:8:13:8:19 | 'world' | +| importedComponent.jsx:4:32:4:36 | color | | props.js:5:46:5:67 | "propFr ... tProps" | | props.js:7:22:7:34 | "propFromJSX" | | props.js:9:33:9:53 | "propFr ... ructor" | @@ -222,6 +226,7 @@ test_ReactComponent | es5.js:18:33:22:1 | {\\n ren ... ;\\n }\\n} | | es6.js:1:1:8:1 | class H ... ;\\n }\\n} | | es6.js:14:1:20:1 | class H ... }\\n} | +| exportedComponent.jsx:1:8:3:1 | functio ... r}}/>\\n} | | namedImport.js:3:1:3:28 | class C ... nent {} | | namedImport.js:5:1:5:20 | class D extends C {} | | plainfn.js:1:1:3:1 | functio ... div>;\\n} | @@ -248,6 +253,7 @@ test_ReactComponent_getAPropRead | es5.js:1:31:11:1 | {\\n dis ... ;\\n }\\n} | name | es5.js:4:24:4:38 | this.props.name | | es5.js:18:33:22:1 | {\\n ren ... ;\\n }\\n} | name | es5.js:20:24:20:38 | this.props.name | | es6.js:1:1:8:1 | class H ... ;\\n }\\n} | name | es6.js:3:24:3:38 | this.props.name | +| exportedComponent.jsx:1:8:3:1 | functio ... r}}/>\\n} | color | exportedComponent.jsx:2:32:2:42 | props.color | | plainfn.js:1:1:3:1 | functio ... div>;\\n} | name | plainfn.js:2:22:2:31 | props.name | | preact.js:1:1:7:1 | class H ... }\\n} | name | preact.js:3:9:3:18 | props.name | | probably-a-component.js:1:1:6:1 | class H ... }\\n} | name | probably-a-component.js:3:9:3:23 | this.props.name | diff --git a/javascript/ql/test/library-tests/frameworks/koa/src/koa.js b/javascript/ql/test/library-tests/frameworks/koa/src/koa.js index 24680a4a746..479298fd405 100644 --- a/javascript/ql/test/library-tests/frameworks/koa/src/koa.js +++ b/javascript/ql/test/library-tests/frameworks/koa/src/koa.js @@ -54,3 +54,8 @@ app2.use(async ctx => { var headers = ctx.headers; headers.foo; }); + +var app3 = Koa(); +app3.use(function*(){ + this.request.url; +}); diff --git a/javascript/ql/test/library-tests/frameworks/koa/tests.expected b/javascript/ql/test/library-tests/frameworks/koa/tests.expected index 0d8e37d13a6..8e6f1aa06a2 100644 --- a/javascript/ql/test/library-tests/frameworks/koa/tests.expected +++ b/javascript/ql/test/library-tests/frameworks/koa/tests.expected @@ -3,6 +3,7 @@ test_RouteSetup | src/koa.js:10:1:28:2 | app2.us ... z');\\n}) | | src/koa.js:30:1:45:2 | app2.us ... rl);\\n}) | | src/koa.js:47:1:56:2 | app2.us ... foo;\\n}) | +| src/koa.js:59:1:61:2 | app3.us ... url;\\n}) | test_RequestInputAccess | src/koa.js:19:3:19:18 | ctx.request.body | body | src/koa.js:10:10:28:1 | functio ... az');\\n} | | src/koa.js:20:3:20:23 | ctx.req ... ery.foo | parameter | src/koa.js:10:10:28:1 | functio ... az');\\n} | @@ -24,6 +25,7 @@ test_RequestInputAccess | src/koa.js:49:2:49:14 | cookies.get() | cookie | src/koa.js:47:10:56:1 | async c ... .foo;\\n} | | src/koa.js:52:2:52:10 | query.foo | parameter | src/koa.js:47:10:56:1 | async c ... .foo;\\n} | | src/koa.js:55:2:55:12 | headers.foo | header | src/koa.js:47:10:56:1 | async c ... .foo;\\n} | +| src/koa.js:60:2:60:17 | this.request.url | url | src/koa.js:59:10:61:1 | functio ... .url;\\n} | test_RouteHandler_getAResponseHeader | src/koa.js:10:10:28:1 | functio ... az');\\n} | header1 | src/koa.js:11:3:11:25 | this.se ... 1', '') | | src/koa.js:10:10:28:1 | functio ... az');\\n} | header2 | src/koa.js:12:3:12:37 | this.re ... 2', '') | @@ -75,6 +77,7 @@ test_RouteHandler_getAContextExpr | src/koa.js:47:10:56:1 | async c ... .foo;\\n} | src/koa.js:48:16:48:18 | ctx | | src/koa.js:47:10:56:1 | async c ... .foo;\\n} | src/koa.js:51:14:51:16 | ctx | | src/koa.js:47:10:56:1 | async c ... .foo;\\n} | src/koa.js:54:16:54:18 | ctx | +| src/koa.js:59:10:61:1 | functio ... .url;\\n} | src/koa.js:60:2:60:5 | this | test_HeaderDefinition | src/koa.js:11:3:11:25 | this.se ... 1', '') | src/koa.js:10:10:28:1 | functio ... az');\\n} | | src/koa.js:12:3:12:37 | this.re ... 2', '') | src/koa.js:10:10:28:1 | functio ... az');\\n} | @@ -87,6 +90,7 @@ test_RouteSetup_getServer | src/koa.js:10:1:28:2 | app2.us ... z');\\n}) | src/koa.js:5:12:5:20 | new Koa() | | src/koa.js:30:1:45:2 | app2.us ... rl);\\n}) | src/koa.js:5:12:5:20 | new Koa() | | src/koa.js:47:1:56:2 | app2.us ... foo;\\n}) | src/koa.js:5:12:5:20 | new Koa() | +| src/koa.js:59:1:61:2 | app3.us ... url;\\n}) | src/koa.js:58:12:58:16 | Koa() | test_HeaderDefinition_getAHeaderName | src/koa.js:11:3:11:25 | this.se ... 1', '') | header1 | | src/koa.js:12:3:12:37 | this.re ... 2', '') | header2 | @@ -116,14 +120,17 @@ test_RouteSetup_getARouteHandler | src/koa.js:10:1:28:2 | app2.us ... z');\\n}) | src/koa.js:10:10:28:1 | functio ... az');\\n} | | src/koa.js:30:1:45:2 | app2.us ... rl);\\n}) | src/koa.js:30:10:45:1 | async c ... url);\\n} | | src/koa.js:47:1:56:2 | app2.us ... foo;\\n}) | src/koa.js:47:10:56:1 | async c ... .foo;\\n} | +| src/koa.js:59:1:61:2 | app3.us ... url;\\n}) | src/koa.js:59:10:61:1 | functio ... .url;\\n} | test_AppDefinition | src/koa.js:2:12:2:33 | new (re ... oa'))() | | src/koa.js:5:12:5:20 | new Koa() | +| src/koa.js:58:12:58:16 | Koa() | test_RouteHandler | src/koa.js:7:1:7:22 | functio ... r1() {} | src/koa.js:5:12:5:20 | new Koa() | | src/koa.js:10:10:28:1 | functio ... az');\\n} | src/koa.js:5:12:5:20 | new Koa() | | src/koa.js:30:10:45:1 | async c ... url);\\n} | src/koa.js:5:12:5:20 | new Koa() | | src/koa.js:47:10:56:1 | async c ... .foo;\\n} | src/koa.js:5:12:5:20 | new Koa() | +| src/koa.js:59:10:61:1 | functio ... .url;\\n} | src/koa.js:58:12:58:16 | Koa() | test_RequestExpr | src/koa.js:19:3:19:13 | ctx.request | src/koa.js:10:10:28:1 | functio ... az');\\n} | | src/koa.js:20:3:20:13 | ctx.request | src/koa.js:10:10:28:1 | functio ... az');\\n} | @@ -133,6 +140,7 @@ test_RequestExpr | src/koa.js:24:3:24:13 | ctx.request | src/koa.js:10:10:28:1 | functio ... az');\\n} | | src/koa.js:25:3:25:13 | ctx.request | src/koa.js:10:10:28:1 | functio ... az');\\n} | | src/koa.js:26:3:26:13 | ctx.request | src/koa.js:10:10:28:1 | functio ... az');\\n} | +| src/koa.js:60:2:60:13 | this.request | src/koa.js:59:10:61:1 | functio ... .url;\\n} | test_RouteHandler_getARequestExpr | src/koa.js:10:10:28:1 | functio ... az');\\n} | src/koa.js:19:3:19:13 | ctx.request | | src/koa.js:10:10:28:1 | functio ... az');\\n} | src/koa.js:20:3:20:13 | ctx.request | @@ -142,6 +150,7 @@ test_RouteHandler_getARequestExpr | src/koa.js:10:10:28:1 | functio ... az');\\n} | src/koa.js:24:3:24:13 | ctx.request | | src/koa.js:10:10:28:1 | functio ... az');\\n} | src/koa.js:25:3:25:13 | ctx.request | | src/koa.js:10:10:28:1 | functio ... az');\\n} | src/koa.js:26:3:26:13 | ctx.request | +| src/koa.js:59:10:61:1 | functio ... .url;\\n} | src/koa.js:60:2:60:13 | this.request | test_ContextExpr | src/koa.js:11:3:11:6 | this | src/koa.js:10:10:28:1 | functio ... az');\\n} | | src/koa.js:12:3:12:6 | this | src/koa.js:10:10:28:1 | functio ... az');\\n} | @@ -174,6 +183,7 @@ test_ContextExpr | src/koa.js:48:16:48:18 | ctx | src/koa.js:47:10:56:1 | async c ... .foo;\\n} | | src/koa.js:51:14:51:16 | ctx | src/koa.js:47:10:56:1 | async c ... .foo;\\n} | | src/koa.js:54:16:54:18 | ctx | src/koa.js:47:10:56:1 | async c ... .foo;\\n} | +| src/koa.js:60:2:60:5 | this | src/koa.js:59:10:61:1 | functio ... .url;\\n} | test_RedirectInvocation | src/koa.js:43:2:43:18 | ctx.redirect(url) | src/koa.js:43:15:43:17 | url | src/koa.js:30:10:45:1 | async c ... url);\\n} | | src/koa.js:44:2:44:27 | ctx.res ... ct(url) | src/koa.js:44:24:44:26 | url | src/koa.js:30:10:45:1 | async c ... url);\\n} | diff --git a/javascript/ql/test/query-tests/LanguageFeatures/SyntaxError/SyntaxError.expected b/javascript/ql/test/query-tests/LanguageFeatures/SyntaxError/SyntaxError.expected index 575b5e21e7c..e643a123124 100644 --- a/javascript/ql/test/query-tests/LanguageFeatures/SyntaxError/SyntaxError.expected +++ b/javascript/ql/test/query-tests/LanguageFeatures/SyntaxError/SyntaxError.expected @@ -1,2 +1,4 @@ | arrows.js:1:5:1:5 | Error: Argument name clash | Error: Argument name clash | +| destructingPrivate.js:2:12:2:12 | Error: Unexpected token | Error: Unexpected token | +| privateMethod.js:2:3:2:3 | Error: Only fields, not methods, can be declared private. | Error: Only fields, not methods, can be declared private. | | tst.js:2:12:2:12 | Error: Unterminated string constant | Error: Unterminated string constant | diff --git a/javascript/ql/test/query-tests/LanguageFeatures/SyntaxError/destructingPrivate.js b/javascript/ql/test/query-tests/LanguageFeatures/SyntaxError/destructingPrivate.js new file mode 100644 index 00000000000..060111d86b4 --- /dev/null +++ b/javascript/ql/test/query-tests/LanguageFeatures/SyntaxError/destructingPrivate.js @@ -0,0 +1,6 @@ +class C { + #privDecl; + bar() { + {#privDecl} = this; + } +} \ No newline at end of file diff --git a/javascript/ql/test/query-tests/LanguageFeatures/SyntaxError/privateMethod.js b/javascript/ql/test/query-tests/LanguageFeatures/SyntaxError/privateMethod.js new file mode 100644 index 00000000000..3236346841b --- /dev/null +++ b/javascript/ql/test/query-tests/LanguageFeatures/SyntaxError/privateMethod.js @@ -0,0 +1,3 @@ +class C { + #privateMethod() {} +} \ No newline at end of file diff --git a/javascript/ql/test/query-tests/Security/CWE-022/TaintedPath/Consistency.expected b/javascript/ql/test/query-tests/Security/CWE-022/TaintedPath/Consistency.expected index 187526de3a2..659b85c8b14 100644 --- a/javascript/ql/test/query-tests/Security/CWE-022/TaintedPath/Consistency.expected +++ b/javascript/ql/test/query-tests/Security/CWE-022/TaintedPath/Consistency.expected @@ -1 +1,6 @@ | normalizedPaths.js:208:38:208:63 | // OK - ... anyway | Spurious alert | +| tainted-string-steps.js:13:41:13:72 | // NOT ... flagged | Missing alert | +| tainted-string-steps.js:14:41:14:72 | // NOT ... flagged | Missing alert | +| tainted-string-steps.js:15:50:15:81 | // NOT ... flagged | Missing alert | +| tainted-string-steps.js:25:43:25:74 | // NOT ... flagged | Missing alert | +| tainted-string-steps.js:26:49:26:74 | // OK - ... flagged | Spurious alert | diff --git a/javascript/ql/test/query-tests/Security/CWE-022/TaintedPath/TaintedPath.expected b/javascript/ql/test/query-tests/Security/CWE-022/TaintedPath/TaintedPath.expected index 056f020660c..4391a019664 100644 --- a/javascript/ql/test/query-tests/Security/CWE-022/TaintedPath/TaintedPath.expected +++ b/javascript/ql/test/query-tests/Security/CWE-022/TaintedPath/TaintedPath.expected @@ -1145,6 +1145,34 @@ nodes | normalizedPaths.js:228:21:228:24 | path | | normalizedPaths.js:228:21:228:24 | path | | normalizedPaths.js:228:21:228:24 | path | +| normalizedPaths.js:236:7:236:47 | path | +| normalizedPaths.js:236:7:236:47 | path | +| normalizedPaths.js:236:7:236:47 | path | +| normalizedPaths.js:236:7:236:47 | path | +| normalizedPaths.js:236:14:236:47 | pathMod ... y.path) | +| normalizedPaths.js:236:14:236:47 | pathMod ... y.path) | +| normalizedPaths.js:236:14:236:47 | pathMod ... y.path) | +| normalizedPaths.js:236:14:236:47 | pathMod ... y.path) | +| normalizedPaths.js:236:33:236:46 | req.query.path | +| normalizedPaths.js:236:33:236:46 | req.query.path | +| normalizedPaths.js:236:33:236:46 | req.query.path | +| normalizedPaths.js:236:33:236:46 | req.query.path | +| normalizedPaths.js:236:33:236:46 | req.query.path | +| normalizedPaths.js:238:19:238:22 | path | +| normalizedPaths.js:238:19:238:22 | path | +| normalizedPaths.js:238:19:238:22 | path | +| normalizedPaths.js:238:19:238:22 | path | +| normalizedPaths.js:238:19:238:22 | path | +| normalizedPaths.js:245:21:245:24 | path | +| normalizedPaths.js:245:21:245:24 | path | +| normalizedPaths.js:245:21:245:24 | path | +| normalizedPaths.js:245:21:245:24 | path | +| normalizedPaths.js:245:21:245:24 | path | +| normalizedPaths.js:250:21:250:24 | path | +| normalizedPaths.js:250:21:250:24 | path | +| normalizedPaths.js:250:21:250:24 | path | +| normalizedPaths.js:250:21:250:24 | path | +| normalizedPaths.js:250:21:250:24 | path | | tainted-require.js:7:19:7:37 | req.param("module") | | tainted-require.js:7:19:7:37 | req.param("module") | | tainted-require.js:7:19:7:37 | req.param("module") | @@ -1183,6 +1211,404 @@ nodes | tainted-sendFile.js:25:34:25:45 | req.params.x | | tainted-sendFile.js:25:34:25:45 | req.params.x | | tainted-sendFile.js:25:34:25:45 | req.params.x | +| tainted-string-steps.js:6:7:6:48 | path | +| tainted-string-steps.js:6:7:6:48 | path | +| tainted-string-steps.js:6:7:6:48 | path | +| tainted-string-steps.js:6:7:6:48 | path | +| tainted-string-steps.js:6:7:6:48 | path | +| tainted-string-steps.js:6:7:6:48 | path | +| tainted-string-steps.js:6:7:6:48 | path | +| tainted-string-steps.js:6:7:6:48 | path | +| tainted-string-steps.js:6:7:6:48 | path | +| tainted-string-steps.js:6:7:6:48 | path | +| tainted-string-steps.js:6:7:6:48 | path | +| tainted-string-steps.js:6:7:6:48 | path | +| tainted-string-steps.js:6:7:6:48 | path | +| tainted-string-steps.js:6:7:6:48 | path | +| tainted-string-steps.js:6:7:6:48 | path | +| tainted-string-steps.js:6:7:6:48 | path | +| tainted-string-steps.js:6:14:6:37 | url.par ... , true) | +| tainted-string-steps.js:6:14:6:37 | url.par ... , true) | +| tainted-string-steps.js:6:14:6:37 | url.par ... , true) | +| tainted-string-steps.js:6:14:6:37 | url.par ... , true) | +| tainted-string-steps.js:6:14:6:37 | url.par ... , true) | +| tainted-string-steps.js:6:14:6:37 | url.par ... , true) | +| tainted-string-steps.js:6:14:6:37 | url.par ... , true) | +| tainted-string-steps.js:6:14:6:37 | url.par ... , true) | +| tainted-string-steps.js:6:14:6:37 | url.par ... , true) | +| tainted-string-steps.js:6:14:6:37 | url.par ... , true) | +| tainted-string-steps.js:6:14:6:37 | url.par ... , true) | +| tainted-string-steps.js:6:14:6:37 | url.par ... , true) | +| tainted-string-steps.js:6:14:6:37 | url.par ... , true) | +| tainted-string-steps.js:6:14:6:37 | url.par ... , true) | +| tainted-string-steps.js:6:14:6:37 | url.par ... , true) | +| tainted-string-steps.js:6:14:6:37 | url.par ... , true) | +| tainted-string-steps.js:6:14:6:43 | url.par ... ).query | +| tainted-string-steps.js:6:14:6:43 | url.par ... ).query | +| tainted-string-steps.js:6:14:6:43 | url.par ... ).query | +| tainted-string-steps.js:6:14:6:43 | url.par ... ).query | +| tainted-string-steps.js:6:14:6:43 | url.par ... ).query | +| tainted-string-steps.js:6:14:6:43 | url.par ... ).query | +| tainted-string-steps.js:6:14:6:43 | url.par ... ).query | +| tainted-string-steps.js:6:14:6:43 | url.par ... ).query | +| tainted-string-steps.js:6:14:6:43 | url.par ... ).query | +| tainted-string-steps.js:6:14:6:43 | url.par ... ).query | +| tainted-string-steps.js:6:14:6:43 | url.par ... ).query | +| tainted-string-steps.js:6:14:6:43 | url.par ... ).query | +| tainted-string-steps.js:6:14:6:43 | url.par ... ).query | +| tainted-string-steps.js:6:14:6:43 | url.par ... ).query | +| tainted-string-steps.js:6:14:6:43 | url.par ... ).query | +| tainted-string-steps.js:6:14:6:43 | url.par ... ).query | +| tainted-string-steps.js:6:14:6:48 | url.par ... ry.path | +| tainted-string-steps.js:6:14:6:48 | url.par ... ry.path | +| tainted-string-steps.js:6:14:6:48 | url.par ... ry.path | +| tainted-string-steps.js:6:14:6:48 | url.par ... ry.path | +| tainted-string-steps.js:6:14:6:48 | url.par ... ry.path | +| tainted-string-steps.js:6:14:6:48 | url.par ... ry.path | +| tainted-string-steps.js:6:14:6:48 | url.par ... ry.path | +| tainted-string-steps.js:6:14:6:48 | url.par ... ry.path | +| tainted-string-steps.js:6:14:6:48 | url.par ... ry.path | +| tainted-string-steps.js:6:14:6:48 | url.par ... ry.path | +| tainted-string-steps.js:6:14:6:48 | url.par ... ry.path | +| tainted-string-steps.js:6:14:6:48 | url.par ... ry.path | +| tainted-string-steps.js:6:14:6:48 | url.par ... ry.path | +| tainted-string-steps.js:6:14:6:48 | url.par ... ry.path | +| tainted-string-steps.js:6:14:6:48 | url.par ... ry.path | +| tainted-string-steps.js:6:14:6:48 | url.par ... ry.path | +| tainted-string-steps.js:6:24:6:30 | req.url | +| tainted-string-steps.js:6:24:6:30 | req.url | +| tainted-string-steps.js:6:24:6:30 | req.url | +| tainted-string-steps.js:6:24:6:30 | req.url | +| tainted-string-steps.js:6:24:6:30 | req.url | +| tainted-string-steps.js:8:18:8:21 | path | +| tainted-string-steps.js:8:18:8:21 | path | +| tainted-string-steps.js:8:18:8:21 | path | +| tainted-string-steps.js:8:18:8:21 | path | +| tainted-string-steps.js:8:18:8:21 | path | +| tainted-string-steps.js:8:18:8:21 | path | +| tainted-string-steps.js:8:18:8:21 | path | +| tainted-string-steps.js:8:18:8:21 | path | +| tainted-string-steps.js:8:18:8:21 | path | +| tainted-string-steps.js:8:18:8:21 | path | +| tainted-string-steps.js:8:18:8:21 | path | +| tainted-string-steps.js:8:18:8:21 | path | +| tainted-string-steps.js:8:18:8:21 | path | +| tainted-string-steps.js:8:18:8:21 | path | +| tainted-string-steps.js:8:18:8:21 | path | +| tainted-string-steps.js:8:18:8:21 | path | +| tainted-string-steps.js:8:18:8:34 | path.substring(4) | +| tainted-string-steps.js:8:18:8:34 | path.substring(4) | +| tainted-string-steps.js:8:18:8:34 | path.substring(4) | +| tainted-string-steps.js:8:18:8:34 | path.substring(4) | +| tainted-string-steps.js:8:18:8:34 | path.substring(4) | +| tainted-string-steps.js:8:18:8:34 | path.substring(4) | +| tainted-string-steps.js:8:18:8:34 | path.substring(4) | +| tainted-string-steps.js:8:18:8:34 | path.substring(4) | +| tainted-string-steps.js:8:18:8:34 | path.substring(4) | +| tainted-string-steps.js:8:18:8:34 | path.substring(4) | +| tainted-string-steps.js:8:18:8:34 | path.substring(4) | +| tainted-string-steps.js:8:18:8:34 | path.substring(4) | +| tainted-string-steps.js:8:18:8:34 | path.substring(4) | +| tainted-string-steps.js:8:18:8:34 | path.substring(4) | +| tainted-string-steps.js:8:18:8:34 | path.substring(4) | +| tainted-string-steps.js:8:18:8:34 | path.substring(4) | +| tainted-string-steps.js:8:18:8:34 | path.substring(4) | +| tainted-string-steps.js:9:18:9:21 | path | +| tainted-string-steps.js:9:18:9:21 | path | +| tainted-string-steps.js:9:18:9:21 | path | +| tainted-string-steps.js:9:18:9:21 | path | +| tainted-string-steps.js:9:18:9:21 | path | +| tainted-string-steps.js:9:18:9:21 | path | +| tainted-string-steps.js:9:18:9:21 | path | +| tainted-string-steps.js:9:18:9:21 | path | +| tainted-string-steps.js:9:18:9:21 | path | +| tainted-string-steps.js:9:18:9:21 | path | +| tainted-string-steps.js:9:18:9:21 | path | +| tainted-string-steps.js:9:18:9:21 | path | +| tainted-string-steps.js:9:18:9:21 | path | +| tainted-string-steps.js:9:18:9:21 | path | +| tainted-string-steps.js:9:18:9:21 | path | +| tainted-string-steps.js:9:18:9:21 | path | +| tainted-string-steps.js:9:18:9:37 | path.substring(0, i) | +| tainted-string-steps.js:9:18:9:37 | path.substring(0, i) | +| tainted-string-steps.js:9:18:9:37 | path.substring(0, i) | +| tainted-string-steps.js:9:18:9:37 | path.substring(0, i) | +| tainted-string-steps.js:9:18:9:37 | path.substring(0, i) | +| tainted-string-steps.js:9:18:9:37 | path.substring(0, i) | +| tainted-string-steps.js:9:18:9:37 | path.substring(0, i) | +| tainted-string-steps.js:9:18:9:37 | path.substring(0, i) | +| tainted-string-steps.js:9:18:9:37 | path.substring(0, i) | +| tainted-string-steps.js:9:18:9:37 | path.substring(0, i) | +| tainted-string-steps.js:9:18:9:37 | path.substring(0, i) | +| tainted-string-steps.js:9:18:9:37 | path.substring(0, i) | +| tainted-string-steps.js:9:18:9:37 | path.substring(0, i) | +| tainted-string-steps.js:9:18:9:37 | path.substring(0, i) | +| tainted-string-steps.js:9:18:9:37 | path.substring(0, i) | +| tainted-string-steps.js:9:18:9:37 | path.substring(0, i) | +| tainted-string-steps.js:9:18:9:37 | path.substring(0, i) | +| tainted-string-steps.js:10:18:10:21 | path | +| tainted-string-steps.js:10:18:10:21 | path | +| tainted-string-steps.js:10:18:10:21 | path | +| tainted-string-steps.js:10:18:10:21 | path | +| tainted-string-steps.js:10:18:10:21 | path | +| tainted-string-steps.js:10:18:10:21 | path | +| tainted-string-steps.js:10:18:10:21 | path | +| tainted-string-steps.js:10:18:10:21 | path | +| tainted-string-steps.js:10:18:10:21 | path | +| tainted-string-steps.js:10:18:10:21 | path | +| tainted-string-steps.js:10:18:10:21 | path | +| tainted-string-steps.js:10:18:10:21 | path | +| tainted-string-steps.js:10:18:10:21 | path | +| tainted-string-steps.js:10:18:10:21 | path | +| tainted-string-steps.js:10:18:10:21 | path | +| tainted-string-steps.js:10:18:10:21 | path | +| tainted-string-steps.js:10:18:10:31 | path.substr(4) | +| tainted-string-steps.js:10:18:10:31 | path.substr(4) | +| tainted-string-steps.js:10:18:10:31 | path.substr(4) | +| tainted-string-steps.js:10:18:10:31 | path.substr(4) | +| tainted-string-steps.js:10:18:10:31 | path.substr(4) | +| tainted-string-steps.js:10:18:10:31 | path.substr(4) | +| tainted-string-steps.js:10:18:10:31 | path.substr(4) | +| tainted-string-steps.js:10:18:10:31 | path.substr(4) | +| tainted-string-steps.js:10:18:10:31 | path.substr(4) | +| tainted-string-steps.js:10:18:10:31 | path.substr(4) | +| tainted-string-steps.js:10:18:10:31 | path.substr(4) | +| tainted-string-steps.js:10:18:10:31 | path.substr(4) | +| tainted-string-steps.js:10:18:10:31 | path.substr(4) | +| tainted-string-steps.js:10:18:10:31 | path.substr(4) | +| tainted-string-steps.js:10:18:10:31 | path.substr(4) | +| tainted-string-steps.js:10:18:10:31 | path.substr(4) | +| tainted-string-steps.js:10:18:10:31 | path.substr(4) | +| tainted-string-steps.js:11:18:11:21 | path | +| tainted-string-steps.js:11:18:11:21 | path | +| tainted-string-steps.js:11:18:11:21 | path | +| tainted-string-steps.js:11:18:11:21 | path | +| tainted-string-steps.js:11:18:11:21 | path | +| tainted-string-steps.js:11:18:11:21 | path | +| tainted-string-steps.js:11:18:11:21 | path | +| tainted-string-steps.js:11:18:11:21 | path | +| tainted-string-steps.js:11:18:11:21 | path | +| tainted-string-steps.js:11:18:11:21 | path | +| tainted-string-steps.js:11:18:11:21 | path | +| tainted-string-steps.js:11:18:11:21 | path | +| tainted-string-steps.js:11:18:11:21 | path | +| tainted-string-steps.js:11:18:11:21 | path | +| tainted-string-steps.js:11:18:11:21 | path | +| tainted-string-steps.js:11:18:11:21 | path | +| tainted-string-steps.js:11:18:11:30 | path.slice(4) | +| tainted-string-steps.js:11:18:11:30 | path.slice(4) | +| tainted-string-steps.js:11:18:11:30 | path.slice(4) | +| tainted-string-steps.js:11:18:11:30 | path.slice(4) | +| tainted-string-steps.js:11:18:11:30 | path.slice(4) | +| tainted-string-steps.js:11:18:11:30 | path.slice(4) | +| tainted-string-steps.js:11:18:11:30 | path.slice(4) | +| tainted-string-steps.js:11:18:11:30 | path.slice(4) | +| tainted-string-steps.js:11:18:11:30 | path.slice(4) | +| tainted-string-steps.js:11:18:11:30 | path.slice(4) | +| tainted-string-steps.js:11:18:11:30 | path.slice(4) | +| tainted-string-steps.js:11:18:11:30 | path.slice(4) | +| tainted-string-steps.js:11:18:11:30 | path.slice(4) | +| tainted-string-steps.js:11:18:11:30 | path.slice(4) | +| tainted-string-steps.js:11:18:11:30 | path.slice(4) | +| tainted-string-steps.js:11:18:11:30 | path.slice(4) | +| tainted-string-steps.js:11:18:11:30 | path.slice(4) | +| tainted-string-steps.js:17:18:17:21 | path | +| tainted-string-steps.js:17:18:17:21 | path | +| tainted-string-steps.js:17:18:17:21 | path | +| tainted-string-steps.js:17:18:17:21 | path | +| tainted-string-steps.js:17:18:17:21 | path | +| tainted-string-steps.js:17:18:17:21 | path | +| tainted-string-steps.js:17:18:17:21 | path | +| tainted-string-steps.js:17:18:17:21 | path | +| tainted-string-steps.js:17:18:17:21 | path | +| tainted-string-steps.js:17:18:17:21 | path | +| tainted-string-steps.js:17:18:17:21 | path | +| tainted-string-steps.js:17:18:17:21 | path | +| tainted-string-steps.js:17:18:17:21 | path | +| tainted-string-steps.js:17:18:17:21 | path | +| tainted-string-steps.js:17:18:17:21 | path | +| tainted-string-steps.js:17:18:17:21 | path | +| tainted-string-steps.js:17:18:17:28 | path.trim() | +| tainted-string-steps.js:17:18:17:28 | path.trim() | +| tainted-string-steps.js:17:18:17:28 | path.trim() | +| tainted-string-steps.js:17:18:17:28 | path.trim() | +| tainted-string-steps.js:17:18:17:28 | path.trim() | +| tainted-string-steps.js:17:18:17:28 | path.trim() | +| tainted-string-steps.js:17:18:17:28 | path.trim() | +| tainted-string-steps.js:17:18:17:28 | path.trim() | +| tainted-string-steps.js:17:18:17:28 | path.trim() | +| tainted-string-steps.js:17:18:17:28 | path.trim() | +| tainted-string-steps.js:17:18:17:28 | path.trim() | +| tainted-string-steps.js:17:18:17:28 | path.trim() | +| tainted-string-steps.js:17:18:17:28 | path.trim() | +| tainted-string-steps.js:17:18:17:28 | path.trim() | +| tainted-string-steps.js:17:18:17:28 | path.trim() | +| tainted-string-steps.js:17:18:17:28 | path.trim() | +| tainted-string-steps.js:17:18:17:28 | path.trim() | +| tainted-string-steps.js:18:18:18:21 | path | +| tainted-string-steps.js:18:18:18:21 | path | +| tainted-string-steps.js:18:18:18:21 | path | +| tainted-string-steps.js:18:18:18:21 | path | +| tainted-string-steps.js:18:18:18:21 | path | +| tainted-string-steps.js:18:18:18:21 | path | +| tainted-string-steps.js:18:18:18:21 | path | +| tainted-string-steps.js:18:18:18:21 | path | +| tainted-string-steps.js:18:18:18:21 | path | +| tainted-string-steps.js:18:18:18:21 | path | +| tainted-string-steps.js:18:18:18:21 | path | +| tainted-string-steps.js:18:18:18:21 | path | +| tainted-string-steps.js:18:18:18:21 | path | +| tainted-string-steps.js:18:18:18:21 | path | +| tainted-string-steps.js:18:18:18:21 | path | +| tainted-string-steps.js:18:18:18:21 | path | +| tainted-string-steps.js:18:18:18:35 | path.toLowerCase() | +| tainted-string-steps.js:18:18:18:35 | path.toLowerCase() | +| tainted-string-steps.js:18:18:18:35 | path.toLowerCase() | +| tainted-string-steps.js:18:18:18:35 | path.toLowerCase() | +| tainted-string-steps.js:18:18:18:35 | path.toLowerCase() | +| tainted-string-steps.js:18:18:18:35 | path.toLowerCase() | +| tainted-string-steps.js:18:18:18:35 | path.toLowerCase() | +| tainted-string-steps.js:18:18:18:35 | path.toLowerCase() | +| tainted-string-steps.js:18:18:18:35 | path.toLowerCase() | +| tainted-string-steps.js:18:18:18:35 | path.toLowerCase() | +| tainted-string-steps.js:18:18:18:35 | path.toLowerCase() | +| tainted-string-steps.js:18:18:18:35 | path.toLowerCase() | +| tainted-string-steps.js:18:18:18:35 | path.toLowerCase() | +| tainted-string-steps.js:18:18:18:35 | path.toLowerCase() | +| tainted-string-steps.js:18:18:18:35 | path.toLowerCase() | +| tainted-string-steps.js:18:18:18:35 | path.toLowerCase() | +| tainted-string-steps.js:18:18:18:35 | path.toLowerCase() | +| tainted-string-steps.js:24:18:24:21 | path | +| tainted-string-steps.js:24:18:24:21 | path | +| tainted-string-steps.js:24:18:24:21 | path | +| tainted-string-steps.js:24:18:24:21 | path | +| tainted-string-steps.js:24:18:24:21 | path | +| tainted-string-steps.js:24:18:24:21 | path | +| tainted-string-steps.js:24:18:24:21 | path | +| tainted-string-steps.js:24:18:24:21 | path | +| tainted-string-steps.js:24:18:24:21 | path | +| tainted-string-steps.js:24:18:24:21 | path | +| tainted-string-steps.js:24:18:24:21 | path | +| tainted-string-steps.js:24:18:24:21 | path | +| tainted-string-steps.js:24:18:24:21 | path | +| tainted-string-steps.js:24:18:24:21 | path | +| tainted-string-steps.js:24:18:24:21 | path | +| tainted-string-steps.js:24:18:24:21 | path | +| tainted-string-steps.js:24:18:24:32 | path.split("?") | +| tainted-string-steps.js:24:18:24:32 | path.split("?") | +| tainted-string-steps.js:24:18:24:32 | path.split("?") | +| tainted-string-steps.js:24:18:24:32 | path.split("?") | +| tainted-string-steps.js:24:18:24:32 | path.split("?") | +| tainted-string-steps.js:24:18:24:32 | path.split("?") | +| tainted-string-steps.js:24:18:24:32 | path.split("?") | +| tainted-string-steps.js:24:18:24:32 | path.split("?") | +| tainted-string-steps.js:24:18:24:32 | path.split("?") | +| tainted-string-steps.js:24:18:24:32 | path.split("?") | +| tainted-string-steps.js:24:18:24:32 | path.split("?") | +| tainted-string-steps.js:24:18:24:32 | path.split("?") | +| tainted-string-steps.js:24:18:24:32 | path.split("?") | +| tainted-string-steps.js:24:18:24:32 | path.split("?") | +| tainted-string-steps.js:24:18:24:32 | path.split("?") | +| tainted-string-steps.js:24:18:24:32 | path.split("?") | +| tainted-string-steps.js:24:18:24:35 | path.split("?")[0] | +| tainted-string-steps.js:24:18:24:35 | path.split("?")[0] | +| tainted-string-steps.js:24:18:24:35 | path.split("?")[0] | +| tainted-string-steps.js:24:18:24:35 | path.split("?")[0] | +| tainted-string-steps.js:24:18:24:35 | path.split("?")[0] | +| tainted-string-steps.js:24:18:24:35 | path.split("?")[0] | +| tainted-string-steps.js:24:18:24:35 | path.split("?")[0] | +| tainted-string-steps.js:24:18:24:35 | path.split("?")[0] | +| tainted-string-steps.js:24:18:24:35 | path.split("?")[0] | +| tainted-string-steps.js:24:18:24:35 | path.split("?")[0] | +| tainted-string-steps.js:24:18:24:35 | path.split("?")[0] | +| tainted-string-steps.js:24:18:24:35 | path.split("?")[0] | +| tainted-string-steps.js:24:18:24:35 | path.split("?")[0] | +| tainted-string-steps.js:24:18:24:35 | path.split("?")[0] | +| tainted-string-steps.js:24:18:24:35 | path.split("?")[0] | +| tainted-string-steps.js:24:18:24:35 | path.split("?")[0] | +| tainted-string-steps.js:24:18:24:35 | path.split("?")[0] | +| tainted-string-steps.js:26:18:26:21 | path | +| tainted-string-steps.js:26:18:26:21 | path | +| tainted-string-steps.js:26:18:26:21 | path | +| tainted-string-steps.js:26:18:26:21 | path | +| tainted-string-steps.js:26:18:26:21 | path | +| tainted-string-steps.js:26:18:26:21 | path | +| tainted-string-steps.js:26:18:26:21 | path | +| tainted-string-steps.js:26:18:26:21 | path | +| tainted-string-steps.js:26:18:26:21 | path | +| tainted-string-steps.js:26:18:26:21 | path | +| tainted-string-steps.js:26:18:26:21 | path | +| tainted-string-steps.js:26:18:26:21 | path | +| tainted-string-steps.js:26:18:26:21 | path | +| tainted-string-steps.js:26:18:26:21 | path | +| tainted-string-steps.js:26:18:26:21 | path | +| tainted-string-steps.js:26:18:26:21 | path | +| tainted-string-steps.js:26:18:26:36 | path.split(unknown) | +| tainted-string-steps.js:26:18:26:36 | path.split(unknown) | +| tainted-string-steps.js:26:18:26:36 | path.split(unknown) | +| tainted-string-steps.js:26:18:26:36 | path.split(unknown) | +| tainted-string-steps.js:26:18:26:36 | path.split(unknown) | +| tainted-string-steps.js:26:18:26:36 | path.split(unknown) | +| tainted-string-steps.js:26:18:26:36 | path.split(unknown) | +| tainted-string-steps.js:26:18:26:36 | path.split(unknown) | +| tainted-string-steps.js:26:18:26:36 | path.split(unknown) | +| tainted-string-steps.js:26:18:26:36 | path.split(unknown) | +| tainted-string-steps.js:26:18:26:36 | path.split(unknown) | +| tainted-string-steps.js:26:18:26:36 | path.split(unknown) | +| tainted-string-steps.js:26:18:26:36 | path.split(unknown) | +| tainted-string-steps.js:26:18:26:36 | path.split(unknown) | +| tainted-string-steps.js:26:18:26:36 | path.split(unknown) | +| tainted-string-steps.js:26:18:26:36 | path.split(unknown) | +| tainted-string-steps.js:26:18:26:45 | path.sp ... hatever | +| tainted-string-steps.js:26:18:26:45 | path.sp ... hatever | +| tainted-string-steps.js:26:18:26:45 | path.sp ... hatever | +| tainted-string-steps.js:26:18:26:45 | path.sp ... hatever | +| tainted-string-steps.js:26:18:26:45 | path.sp ... hatever | +| tainted-string-steps.js:26:18:26:45 | path.sp ... hatever | +| tainted-string-steps.js:26:18:26:45 | path.sp ... hatever | +| tainted-string-steps.js:26:18:26:45 | path.sp ... hatever | +| tainted-string-steps.js:26:18:26:45 | path.sp ... hatever | +| tainted-string-steps.js:26:18:26:45 | path.sp ... hatever | +| tainted-string-steps.js:26:18:26:45 | path.sp ... hatever | +| tainted-string-steps.js:26:18:26:45 | path.sp ... hatever | +| tainted-string-steps.js:26:18:26:45 | path.sp ... hatever | +| tainted-string-steps.js:26:18:26:45 | path.sp ... hatever | +| tainted-string-steps.js:26:18:26:45 | path.sp ... hatever | +| tainted-string-steps.js:26:18:26:45 | path.sp ... hatever | +| tainted-string-steps.js:26:18:26:45 | path.sp ... hatever | +| tainted-string-steps.js:27:18:27:21 | path | +| tainted-string-steps.js:27:18:27:21 | path | +| tainted-string-steps.js:27:18:27:21 | path | +| tainted-string-steps.js:27:18:27:21 | path | +| tainted-string-steps.js:27:18:27:21 | path | +| tainted-string-steps.js:27:18:27:21 | path | +| tainted-string-steps.js:27:18:27:21 | path | +| tainted-string-steps.js:27:18:27:21 | path | +| tainted-string-steps.js:27:18:27:21 | path | +| tainted-string-steps.js:27:18:27:21 | path | +| tainted-string-steps.js:27:18:27:21 | path | +| tainted-string-steps.js:27:18:27:21 | path | +| tainted-string-steps.js:27:18:27:21 | path | +| tainted-string-steps.js:27:18:27:21 | path | +| tainted-string-steps.js:27:18:27:21 | path | +| tainted-string-steps.js:27:18:27:21 | path | +| tainted-string-steps.js:27:18:27:36 | path.split(unknown) | +| tainted-string-steps.js:27:18:27:36 | path.split(unknown) | +| tainted-string-steps.js:27:18:27:36 | path.split(unknown) | +| tainted-string-steps.js:27:18:27:36 | path.split(unknown) | +| tainted-string-steps.js:27:18:27:36 | path.split(unknown) | +| tainted-string-steps.js:27:18:27:36 | path.split(unknown) | +| tainted-string-steps.js:27:18:27:36 | path.split(unknown) | +| tainted-string-steps.js:27:18:27:36 | path.split(unknown) | +| tainted-string-steps.js:27:18:27:36 | path.split(unknown) | +| tainted-string-steps.js:27:18:27:36 | path.split(unknown) | +| tainted-string-steps.js:27:18:27:36 | path.split(unknown) | +| tainted-string-steps.js:27:18:27:36 | path.split(unknown) | +| tainted-string-steps.js:27:18:27:36 | path.split(unknown) | +| tainted-string-steps.js:27:18:27:36 | path.split(unknown) | +| tainted-string-steps.js:27:18:27:36 | path.split(unknown) | +| tainted-string-steps.js:27:18:27:36 | path.split(unknown) | +| tainted-string-steps.js:27:18:27:36 | path.split(unknown) | | torrents.js:5:6:5:38 | name | | torrents.js:5:6:5:38 | name | | torrents.js:5:6:5:38 | name | @@ -2903,6 +3329,42 @@ edges | normalizedPaths.js:226:35:226:48 | req.query.path | normalizedPaths.js:226:14:226:49 | pathMod ... y.path) | | normalizedPaths.js:226:35:226:48 | req.query.path | normalizedPaths.js:226:14:226:49 | pathMod ... y.path) | | normalizedPaths.js:226:35:226:48 | req.query.path | normalizedPaths.js:226:14:226:49 | pathMod ... y.path) | +| normalizedPaths.js:236:7:236:47 | path | normalizedPaths.js:238:19:238:22 | path | +| normalizedPaths.js:236:7:236:47 | path | normalizedPaths.js:238:19:238:22 | path | +| normalizedPaths.js:236:7:236:47 | path | normalizedPaths.js:238:19:238:22 | path | +| normalizedPaths.js:236:7:236:47 | path | normalizedPaths.js:238:19:238:22 | path | +| normalizedPaths.js:236:7:236:47 | path | normalizedPaths.js:238:19:238:22 | path | +| normalizedPaths.js:236:7:236:47 | path | normalizedPaths.js:238:19:238:22 | path | +| normalizedPaths.js:236:7:236:47 | path | normalizedPaths.js:238:19:238:22 | path | +| normalizedPaths.js:236:7:236:47 | path | normalizedPaths.js:238:19:238:22 | path | +| normalizedPaths.js:236:7:236:47 | path | normalizedPaths.js:245:21:245:24 | path | +| normalizedPaths.js:236:7:236:47 | path | normalizedPaths.js:245:21:245:24 | path | +| normalizedPaths.js:236:7:236:47 | path | normalizedPaths.js:245:21:245:24 | path | +| normalizedPaths.js:236:7:236:47 | path | normalizedPaths.js:245:21:245:24 | path | +| normalizedPaths.js:236:7:236:47 | path | normalizedPaths.js:245:21:245:24 | path | +| normalizedPaths.js:236:7:236:47 | path | normalizedPaths.js:245:21:245:24 | path | +| normalizedPaths.js:236:7:236:47 | path | normalizedPaths.js:245:21:245:24 | path | +| normalizedPaths.js:236:7:236:47 | path | normalizedPaths.js:245:21:245:24 | path | +| normalizedPaths.js:236:7:236:47 | path | normalizedPaths.js:250:21:250:24 | path | +| normalizedPaths.js:236:7:236:47 | path | normalizedPaths.js:250:21:250:24 | path | +| normalizedPaths.js:236:7:236:47 | path | normalizedPaths.js:250:21:250:24 | path | +| normalizedPaths.js:236:7:236:47 | path | normalizedPaths.js:250:21:250:24 | path | +| normalizedPaths.js:236:7:236:47 | path | normalizedPaths.js:250:21:250:24 | path | +| normalizedPaths.js:236:7:236:47 | path | normalizedPaths.js:250:21:250:24 | path | +| normalizedPaths.js:236:7:236:47 | path | normalizedPaths.js:250:21:250:24 | path | +| normalizedPaths.js:236:7:236:47 | path | normalizedPaths.js:250:21:250:24 | path | +| normalizedPaths.js:236:14:236:47 | pathMod ... y.path) | normalizedPaths.js:236:7:236:47 | path | +| normalizedPaths.js:236:14:236:47 | pathMod ... y.path) | normalizedPaths.js:236:7:236:47 | path | +| normalizedPaths.js:236:14:236:47 | pathMod ... y.path) | normalizedPaths.js:236:7:236:47 | path | +| normalizedPaths.js:236:14:236:47 | pathMod ... y.path) | normalizedPaths.js:236:7:236:47 | path | +| normalizedPaths.js:236:33:236:46 | req.query.path | normalizedPaths.js:236:14:236:47 | pathMod ... y.path) | +| normalizedPaths.js:236:33:236:46 | req.query.path | normalizedPaths.js:236:14:236:47 | pathMod ... y.path) | +| normalizedPaths.js:236:33:236:46 | req.query.path | normalizedPaths.js:236:14:236:47 | pathMod ... y.path) | +| normalizedPaths.js:236:33:236:46 | req.query.path | normalizedPaths.js:236:14:236:47 | pathMod ... y.path) | +| normalizedPaths.js:236:33:236:46 | req.query.path | normalizedPaths.js:236:14:236:47 | pathMod ... y.path) | +| normalizedPaths.js:236:33:236:46 | req.query.path | normalizedPaths.js:236:14:236:47 | pathMod ... y.path) | +| normalizedPaths.js:236:33:236:46 | req.query.path | normalizedPaths.js:236:14:236:47 | pathMod ... y.path) | +| normalizedPaths.js:236:33:236:46 | req.query.path | normalizedPaths.js:236:14:236:47 | pathMod ... y.path) | | tainted-require.js:7:19:7:37 | req.param("module") | tainted-require.js:7:19:7:37 | req.param("module") | | tainted-sendFile.js:8:16:8:33 | req.param("gimme") | tainted-sendFile.js:8:16:8:33 | req.param("gimme") | | tainted-sendFile.js:10:16:10:33 | req.param("gimme") | tainted-sendFile.js:10:16:10:33 | req.param("gimme") | @@ -2930,6 +3392,550 @@ edges | tainted-sendFile.js:25:34:25:45 | req.params.x | tainted-sendFile.js:25:16:25:46 | path.jo ... rams.x) | | tainted-sendFile.js:25:34:25:45 | req.params.x | tainted-sendFile.js:25:16:25:46 | path.jo ... rams.x) | | tainted-sendFile.js:25:34:25:45 | req.params.x | tainted-sendFile.js:25:16:25:46 | path.jo ... rams.x) | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:8:18:8:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:8:18:8:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:8:18:8:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:8:18:8:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:8:18:8:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:8:18:8:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:8:18:8:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:8:18:8:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:8:18:8:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:8:18:8:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:8:18:8:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:8:18:8:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:8:18:8:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:8:18:8:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:8:18:8:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:8:18:8:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:9:18:9:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:9:18:9:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:9:18:9:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:9:18:9:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:9:18:9:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:9:18:9:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:9:18:9:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:9:18:9:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:9:18:9:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:9:18:9:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:9:18:9:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:9:18:9:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:9:18:9:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:9:18:9:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:9:18:9:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:9:18:9:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:10:18:10:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:10:18:10:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:10:18:10:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:10:18:10:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:10:18:10:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:10:18:10:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:10:18:10:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:10:18:10:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:10:18:10:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:10:18:10:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:10:18:10:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:10:18:10:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:10:18:10:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:10:18:10:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:10:18:10:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:10:18:10:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:11:18:11:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:11:18:11:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:11:18:11:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:11:18:11:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:11:18:11:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:11:18:11:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:11:18:11:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:11:18:11:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:11:18:11:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:11:18:11:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:11:18:11:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:11:18:11:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:11:18:11:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:11:18:11:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:11:18:11:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:11:18:11:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:17:18:17:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:17:18:17:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:17:18:17:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:17:18:17:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:17:18:17:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:17:18:17:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:17:18:17:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:17:18:17:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:17:18:17:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:17:18:17:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:17:18:17:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:17:18:17:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:17:18:17:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:17:18:17:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:17:18:17:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:17:18:17:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:18:18:18:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:18:18:18:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:18:18:18:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:18:18:18:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:18:18:18:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:18:18:18:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:18:18:18:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:18:18:18:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:18:18:18:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:18:18:18:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:18:18:18:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:18:18:18:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:18:18:18:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:18:18:18:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:18:18:18:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:18:18:18:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:24:18:24:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:24:18:24:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:24:18:24:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:24:18:24:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:24:18:24:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:24:18:24:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:24:18:24:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:24:18:24:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:24:18:24:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:24:18:24:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:24:18:24:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:24:18:24:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:24:18:24:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:24:18:24:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:24:18:24:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:24:18:24:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:26:18:26:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:26:18:26:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:26:18:26:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:26:18:26:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:26:18:26:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:26:18:26:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:26:18:26:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:26:18:26:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:26:18:26:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:26:18:26:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:26:18:26:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:26:18:26:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:26:18:26:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:26:18:26:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:26:18:26:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:26:18:26:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:27:18:27:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:27:18:27:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:27:18:27:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:27:18:27:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:27:18:27:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:27:18:27:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:27:18:27:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:27:18:27:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:27:18:27:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:27:18:27:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:27:18:27:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:27:18:27:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:27:18:27:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:27:18:27:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:27:18:27:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:27:18:27:21 | path | +| tainted-string-steps.js:6:14:6:37 | url.par ... , true) | tainted-string-steps.js:6:14:6:43 | url.par ... ).query | +| tainted-string-steps.js:6:14:6:37 | url.par ... , true) | tainted-string-steps.js:6:14:6:43 | url.par ... ).query | +| tainted-string-steps.js:6:14:6:37 | url.par ... , true) | tainted-string-steps.js:6:14:6:43 | url.par ... ).query | +| tainted-string-steps.js:6:14:6:37 | url.par ... , true) | tainted-string-steps.js:6:14:6:43 | url.par ... ).query | +| tainted-string-steps.js:6:14:6:37 | url.par ... , true) | tainted-string-steps.js:6:14:6:43 | url.par ... ).query | +| tainted-string-steps.js:6:14:6:37 | url.par ... , true) | tainted-string-steps.js:6:14:6:43 | url.par ... ).query | +| tainted-string-steps.js:6:14:6:37 | url.par ... , true) | tainted-string-steps.js:6:14:6:43 | url.par ... ).query | +| tainted-string-steps.js:6:14:6:37 | url.par ... , true) | tainted-string-steps.js:6:14:6:43 | url.par ... ).query | +| tainted-string-steps.js:6:14:6:37 | url.par ... , true) | tainted-string-steps.js:6:14:6:43 | url.par ... ).query | +| tainted-string-steps.js:6:14:6:37 | url.par ... , true) | tainted-string-steps.js:6:14:6:43 | url.par ... ).query | +| tainted-string-steps.js:6:14:6:37 | url.par ... , true) | tainted-string-steps.js:6:14:6:43 | url.par ... ).query | +| tainted-string-steps.js:6:14:6:37 | url.par ... , true) | tainted-string-steps.js:6:14:6:43 | url.par ... ).query | +| tainted-string-steps.js:6:14:6:37 | url.par ... , true) | tainted-string-steps.js:6:14:6:43 | url.par ... ).query | +| tainted-string-steps.js:6:14:6:37 | url.par ... , true) | tainted-string-steps.js:6:14:6:43 | url.par ... ).query | +| tainted-string-steps.js:6:14:6:37 | url.par ... , true) | tainted-string-steps.js:6:14:6:43 | url.par ... ).query | +| tainted-string-steps.js:6:14:6:37 | url.par ... , true) | tainted-string-steps.js:6:14:6:43 | url.par ... ).query | +| tainted-string-steps.js:6:14:6:43 | url.par ... ).query | tainted-string-steps.js:6:14:6:48 | url.par ... ry.path | +| tainted-string-steps.js:6:14:6:43 | url.par ... ).query | tainted-string-steps.js:6:14:6:48 | url.par ... ry.path | +| tainted-string-steps.js:6:14:6:43 | url.par ... ).query | tainted-string-steps.js:6:14:6:48 | url.par ... ry.path | +| tainted-string-steps.js:6:14:6:43 | url.par ... ).query | tainted-string-steps.js:6:14:6:48 | url.par ... ry.path | +| tainted-string-steps.js:6:14:6:43 | url.par ... ).query | tainted-string-steps.js:6:14:6:48 | url.par ... ry.path | +| tainted-string-steps.js:6:14:6:43 | url.par ... ).query | tainted-string-steps.js:6:14:6:48 | url.par ... ry.path | +| tainted-string-steps.js:6:14:6:43 | url.par ... ).query | tainted-string-steps.js:6:14:6:48 | url.par ... ry.path | +| tainted-string-steps.js:6:14:6:43 | url.par ... ).query | tainted-string-steps.js:6:14:6:48 | url.par ... ry.path | +| tainted-string-steps.js:6:14:6:43 | url.par ... ).query | tainted-string-steps.js:6:14:6:48 | url.par ... ry.path | +| tainted-string-steps.js:6:14:6:43 | url.par ... ).query | tainted-string-steps.js:6:14:6:48 | url.par ... ry.path | +| tainted-string-steps.js:6:14:6:43 | url.par ... ).query | tainted-string-steps.js:6:14:6:48 | url.par ... ry.path | +| tainted-string-steps.js:6:14:6:43 | url.par ... ).query | tainted-string-steps.js:6:14:6:48 | url.par ... ry.path | +| tainted-string-steps.js:6:14:6:43 | url.par ... ).query | tainted-string-steps.js:6:14:6:48 | url.par ... ry.path | +| tainted-string-steps.js:6:14:6:43 | url.par ... ).query | tainted-string-steps.js:6:14:6:48 | url.par ... ry.path | +| tainted-string-steps.js:6:14:6:43 | url.par ... ).query | tainted-string-steps.js:6:14:6:48 | url.par ... ry.path | +| tainted-string-steps.js:6:14:6:43 | url.par ... ).query | tainted-string-steps.js:6:14:6:48 | url.par ... ry.path | +| tainted-string-steps.js:6:14:6:48 | url.par ... ry.path | tainted-string-steps.js:6:7:6:48 | path | +| tainted-string-steps.js:6:14:6:48 | url.par ... ry.path | tainted-string-steps.js:6:7:6:48 | path | +| tainted-string-steps.js:6:14:6:48 | url.par ... ry.path | tainted-string-steps.js:6:7:6:48 | path | +| tainted-string-steps.js:6:14:6:48 | url.par ... ry.path | tainted-string-steps.js:6:7:6:48 | path | +| tainted-string-steps.js:6:14:6:48 | url.par ... ry.path | tainted-string-steps.js:6:7:6:48 | path | +| tainted-string-steps.js:6:14:6:48 | url.par ... ry.path | tainted-string-steps.js:6:7:6:48 | path | +| tainted-string-steps.js:6:14:6:48 | url.par ... ry.path | tainted-string-steps.js:6:7:6:48 | path | +| tainted-string-steps.js:6:14:6:48 | url.par ... ry.path | tainted-string-steps.js:6:7:6:48 | path | +| tainted-string-steps.js:6:14:6:48 | url.par ... ry.path | tainted-string-steps.js:6:7:6:48 | path | +| tainted-string-steps.js:6:14:6:48 | url.par ... ry.path | tainted-string-steps.js:6:7:6:48 | path | +| tainted-string-steps.js:6:14:6:48 | url.par ... ry.path | tainted-string-steps.js:6:7:6:48 | path | +| tainted-string-steps.js:6:14:6:48 | url.par ... ry.path | tainted-string-steps.js:6:7:6:48 | path | +| tainted-string-steps.js:6:14:6:48 | url.par ... ry.path | tainted-string-steps.js:6:7:6:48 | path | +| tainted-string-steps.js:6:14:6:48 | url.par ... ry.path | tainted-string-steps.js:6:7:6:48 | path | +| tainted-string-steps.js:6:14:6:48 | url.par ... ry.path | tainted-string-steps.js:6:7:6:48 | path | +| tainted-string-steps.js:6:14:6:48 | url.par ... ry.path | tainted-string-steps.js:6:7:6:48 | path | +| tainted-string-steps.js:6:24:6:30 | req.url | tainted-string-steps.js:6:14:6:37 | url.par ... , true) | +| tainted-string-steps.js:6:24:6:30 | req.url | tainted-string-steps.js:6:14:6:37 | url.par ... , true) | +| tainted-string-steps.js:6:24:6:30 | req.url | tainted-string-steps.js:6:14:6:37 | url.par ... , true) | +| tainted-string-steps.js:6:24:6:30 | req.url | tainted-string-steps.js:6:14:6:37 | url.par ... , true) | +| tainted-string-steps.js:6:24:6:30 | req.url | tainted-string-steps.js:6:14:6:37 | url.par ... , true) | +| tainted-string-steps.js:6:24:6:30 | req.url | tainted-string-steps.js:6:14:6:37 | url.par ... , true) | +| tainted-string-steps.js:6:24:6:30 | req.url | tainted-string-steps.js:6:14:6:37 | url.par ... , true) | +| tainted-string-steps.js:6:24:6:30 | req.url | tainted-string-steps.js:6:14:6:37 | url.par ... , true) | +| tainted-string-steps.js:6:24:6:30 | req.url | tainted-string-steps.js:6:14:6:37 | url.par ... , true) | +| tainted-string-steps.js:6:24:6:30 | req.url | tainted-string-steps.js:6:14:6:37 | url.par ... , true) | +| tainted-string-steps.js:6:24:6:30 | req.url | tainted-string-steps.js:6:14:6:37 | url.par ... , true) | +| tainted-string-steps.js:6:24:6:30 | req.url | tainted-string-steps.js:6:14:6:37 | url.par ... , true) | +| tainted-string-steps.js:6:24:6:30 | req.url | tainted-string-steps.js:6:14:6:37 | url.par ... , true) | +| tainted-string-steps.js:6:24:6:30 | req.url | tainted-string-steps.js:6:14:6:37 | url.par ... , true) | +| tainted-string-steps.js:6:24:6:30 | req.url | tainted-string-steps.js:6:14:6:37 | url.par ... , true) | +| tainted-string-steps.js:6:24:6:30 | req.url | tainted-string-steps.js:6:14:6:37 | url.par ... , true) | +| tainted-string-steps.js:6:24:6:30 | req.url | tainted-string-steps.js:6:14:6:37 | url.par ... , true) | +| tainted-string-steps.js:6:24:6:30 | req.url | tainted-string-steps.js:6:14:6:37 | url.par ... , true) | +| tainted-string-steps.js:6:24:6:30 | req.url | tainted-string-steps.js:6:14:6:37 | url.par ... , true) | +| tainted-string-steps.js:6:24:6:30 | req.url | tainted-string-steps.js:6:14:6:37 | url.par ... , true) | +| tainted-string-steps.js:6:24:6:30 | req.url | tainted-string-steps.js:6:14:6:37 | url.par ... , true) | +| tainted-string-steps.js:6:24:6:30 | req.url | tainted-string-steps.js:6:14:6:37 | url.par ... , true) | +| tainted-string-steps.js:6:24:6:30 | req.url | tainted-string-steps.js:6:14:6:37 | url.par ... , true) | +| tainted-string-steps.js:6:24:6:30 | req.url | tainted-string-steps.js:6:14:6:37 | url.par ... , true) | +| tainted-string-steps.js:6:24:6:30 | req.url | tainted-string-steps.js:6:14:6:37 | url.par ... , true) | +| tainted-string-steps.js:6:24:6:30 | req.url | tainted-string-steps.js:6:14:6:37 | url.par ... , true) | +| tainted-string-steps.js:6:24:6:30 | req.url | tainted-string-steps.js:6:14:6:37 | url.par ... , true) | +| tainted-string-steps.js:6:24:6:30 | req.url | tainted-string-steps.js:6:14:6:37 | url.par ... , true) | +| tainted-string-steps.js:6:24:6:30 | req.url | tainted-string-steps.js:6:14:6:37 | url.par ... , true) | +| tainted-string-steps.js:6:24:6:30 | req.url | tainted-string-steps.js:6:14:6:37 | url.par ... , true) | +| tainted-string-steps.js:6:24:6:30 | req.url | tainted-string-steps.js:6:14:6:37 | url.par ... , true) | +| tainted-string-steps.js:6:24:6:30 | req.url | tainted-string-steps.js:6:14:6:37 | url.par ... , true) | +| tainted-string-steps.js:8:18:8:21 | path | tainted-string-steps.js:8:18:8:34 | path.substring(4) | +| tainted-string-steps.js:8:18:8:21 | path | tainted-string-steps.js:8:18:8:34 | path.substring(4) | +| tainted-string-steps.js:8:18:8:21 | path | tainted-string-steps.js:8:18:8:34 | path.substring(4) | +| tainted-string-steps.js:8:18:8:21 | path | tainted-string-steps.js:8:18:8:34 | path.substring(4) | +| tainted-string-steps.js:8:18:8:21 | path | tainted-string-steps.js:8:18:8:34 | path.substring(4) | +| tainted-string-steps.js:8:18:8:21 | path | tainted-string-steps.js:8:18:8:34 | path.substring(4) | +| tainted-string-steps.js:8:18:8:21 | path | tainted-string-steps.js:8:18:8:34 | path.substring(4) | +| tainted-string-steps.js:8:18:8:21 | path | tainted-string-steps.js:8:18:8:34 | path.substring(4) | +| tainted-string-steps.js:8:18:8:21 | path | tainted-string-steps.js:8:18:8:34 | path.substring(4) | +| tainted-string-steps.js:8:18:8:21 | path | tainted-string-steps.js:8:18:8:34 | path.substring(4) | +| tainted-string-steps.js:8:18:8:21 | path | tainted-string-steps.js:8:18:8:34 | path.substring(4) | +| tainted-string-steps.js:8:18:8:21 | path | tainted-string-steps.js:8:18:8:34 | path.substring(4) | +| tainted-string-steps.js:8:18:8:21 | path | tainted-string-steps.js:8:18:8:34 | path.substring(4) | +| tainted-string-steps.js:8:18:8:21 | path | tainted-string-steps.js:8:18:8:34 | path.substring(4) | +| tainted-string-steps.js:8:18:8:21 | path | tainted-string-steps.js:8:18:8:34 | path.substring(4) | +| tainted-string-steps.js:8:18:8:21 | path | tainted-string-steps.js:8:18:8:34 | path.substring(4) | +| tainted-string-steps.js:8:18:8:21 | path | tainted-string-steps.js:8:18:8:34 | path.substring(4) | +| tainted-string-steps.js:8:18:8:21 | path | tainted-string-steps.js:8:18:8:34 | path.substring(4) | +| tainted-string-steps.js:8:18:8:21 | path | tainted-string-steps.js:8:18:8:34 | path.substring(4) | +| tainted-string-steps.js:8:18:8:21 | path | tainted-string-steps.js:8:18:8:34 | path.substring(4) | +| tainted-string-steps.js:8:18:8:21 | path | tainted-string-steps.js:8:18:8:34 | path.substring(4) | +| tainted-string-steps.js:8:18:8:21 | path | tainted-string-steps.js:8:18:8:34 | path.substring(4) | +| tainted-string-steps.js:8:18:8:21 | path | tainted-string-steps.js:8:18:8:34 | path.substring(4) | +| tainted-string-steps.js:8:18:8:21 | path | tainted-string-steps.js:8:18:8:34 | path.substring(4) | +| tainted-string-steps.js:8:18:8:21 | path | tainted-string-steps.js:8:18:8:34 | path.substring(4) | +| tainted-string-steps.js:8:18:8:21 | path | tainted-string-steps.js:8:18:8:34 | path.substring(4) | +| tainted-string-steps.js:8:18:8:21 | path | tainted-string-steps.js:8:18:8:34 | path.substring(4) | +| tainted-string-steps.js:8:18:8:21 | path | tainted-string-steps.js:8:18:8:34 | path.substring(4) | +| tainted-string-steps.js:8:18:8:21 | path | tainted-string-steps.js:8:18:8:34 | path.substring(4) | +| tainted-string-steps.js:8:18:8:21 | path | tainted-string-steps.js:8:18:8:34 | path.substring(4) | +| tainted-string-steps.js:8:18:8:21 | path | tainted-string-steps.js:8:18:8:34 | path.substring(4) | +| tainted-string-steps.js:8:18:8:21 | path | tainted-string-steps.js:8:18:8:34 | path.substring(4) | +| tainted-string-steps.js:9:18:9:21 | path | tainted-string-steps.js:9:18:9:37 | path.substring(0, i) | +| tainted-string-steps.js:9:18:9:21 | path | tainted-string-steps.js:9:18:9:37 | path.substring(0, i) | +| tainted-string-steps.js:9:18:9:21 | path | tainted-string-steps.js:9:18:9:37 | path.substring(0, i) | +| tainted-string-steps.js:9:18:9:21 | path | tainted-string-steps.js:9:18:9:37 | path.substring(0, i) | +| tainted-string-steps.js:9:18:9:21 | path | tainted-string-steps.js:9:18:9:37 | path.substring(0, i) | +| tainted-string-steps.js:9:18:9:21 | path | tainted-string-steps.js:9:18:9:37 | path.substring(0, i) | +| tainted-string-steps.js:9:18:9:21 | path | tainted-string-steps.js:9:18:9:37 | path.substring(0, i) | +| tainted-string-steps.js:9:18:9:21 | path | tainted-string-steps.js:9:18:9:37 | path.substring(0, i) | +| tainted-string-steps.js:9:18:9:21 | path | tainted-string-steps.js:9:18:9:37 | path.substring(0, i) | +| tainted-string-steps.js:9:18:9:21 | path | tainted-string-steps.js:9:18:9:37 | path.substring(0, i) | +| tainted-string-steps.js:9:18:9:21 | path | tainted-string-steps.js:9:18:9:37 | path.substring(0, i) | +| tainted-string-steps.js:9:18:9:21 | path | tainted-string-steps.js:9:18:9:37 | path.substring(0, i) | +| tainted-string-steps.js:9:18:9:21 | path | tainted-string-steps.js:9:18:9:37 | path.substring(0, i) | +| tainted-string-steps.js:9:18:9:21 | path | tainted-string-steps.js:9:18:9:37 | path.substring(0, i) | +| tainted-string-steps.js:9:18:9:21 | path | tainted-string-steps.js:9:18:9:37 | path.substring(0, i) | +| tainted-string-steps.js:9:18:9:21 | path | tainted-string-steps.js:9:18:9:37 | path.substring(0, i) | +| tainted-string-steps.js:9:18:9:21 | path | tainted-string-steps.js:9:18:9:37 | path.substring(0, i) | +| tainted-string-steps.js:9:18:9:21 | path | tainted-string-steps.js:9:18:9:37 | path.substring(0, i) | +| tainted-string-steps.js:9:18:9:21 | path | tainted-string-steps.js:9:18:9:37 | path.substring(0, i) | +| tainted-string-steps.js:9:18:9:21 | path | tainted-string-steps.js:9:18:9:37 | path.substring(0, i) | +| tainted-string-steps.js:9:18:9:21 | path | tainted-string-steps.js:9:18:9:37 | path.substring(0, i) | +| tainted-string-steps.js:9:18:9:21 | path | tainted-string-steps.js:9:18:9:37 | path.substring(0, i) | +| tainted-string-steps.js:9:18:9:21 | path | tainted-string-steps.js:9:18:9:37 | path.substring(0, i) | +| tainted-string-steps.js:9:18:9:21 | path | tainted-string-steps.js:9:18:9:37 | path.substring(0, i) | +| tainted-string-steps.js:9:18:9:21 | path | tainted-string-steps.js:9:18:9:37 | path.substring(0, i) | +| tainted-string-steps.js:9:18:9:21 | path | tainted-string-steps.js:9:18:9:37 | path.substring(0, i) | +| tainted-string-steps.js:9:18:9:21 | path | tainted-string-steps.js:9:18:9:37 | path.substring(0, i) | +| tainted-string-steps.js:9:18:9:21 | path | tainted-string-steps.js:9:18:9:37 | path.substring(0, i) | +| tainted-string-steps.js:9:18:9:21 | path | tainted-string-steps.js:9:18:9:37 | path.substring(0, i) | +| tainted-string-steps.js:9:18:9:21 | path | tainted-string-steps.js:9:18:9:37 | path.substring(0, i) | +| tainted-string-steps.js:9:18:9:21 | path | tainted-string-steps.js:9:18:9:37 | path.substring(0, i) | +| tainted-string-steps.js:9:18:9:21 | path | tainted-string-steps.js:9:18:9:37 | path.substring(0, i) | +| tainted-string-steps.js:10:18:10:21 | path | tainted-string-steps.js:10:18:10:31 | path.substr(4) | +| tainted-string-steps.js:10:18:10:21 | path | tainted-string-steps.js:10:18:10:31 | path.substr(4) | +| tainted-string-steps.js:10:18:10:21 | path | tainted-string-steps.js:10:18:10:31 | path.substr(4) | +| tainted-string-steps.js:10:18:10:21 | path | tainted-string-steps.js:10:18:10:31 | path.substr(4) | +| tainted-string-steps.js:10:18:10:21 | path | tainted-string-steps.js:10:18:10:31 | path.substr(4) | +| tainted-string-steps.js:10:18:10:21 | path | tainted-string-steps.js:10:18:10:31 | path.substr(4) | +| tainted-string-steps.js:10:18:10:21 | path | tainted-string-steps.js:10:18:10:31 | path.substr(4) | +| tainted-string-steps.js:10:18:10:21 | path | tainted-string-steps.js:10:18:10:31 | path.substr(4) | +| tainted-string-steps.js:10:18:10:21 | path | tainted-string-steps.js:10:18:10:31 | path.substr(4) | +| tainted-string-steps.js:10:18:10:21 | path | tainted-string-steps.js:10:18:10:31 | path.substr(4) | +| tainted-string-steps.js:10:18:10:21 | path | tainted-string-steps.js:10:18:10:31 | path.substr(4) | +| tainted-string-steps.js:10:18:10:21 | path | tainted-string-steps.js:10:18:10:31 | path.substr(4) | +| tainted-string-steps.js:10:18:10:21 | path | tainted-string-steps.js:10:18:10:31 | path.substr(4) | +| tainted-string-steps.js:10:18:10:21 | path | tainted-string-steps.js:10:18:10:31 | path.substr(4) | +| tainted-string-steps.js:10:18:10:21 | path | tainted-string-steps.js:10:18:10:31 | path.substr(4) | +| tainted-string-steps.js:10:18:10:21 | path | tainted-string-steps.js:10:18:10:31 | path.substr(4) | +| tainted-string-steps.js:10:18:10:21 | path | tainted-string-steps.js:10:18:10:31 | path.substr(4) | +| tainted-string-steps.js:10:18:10:21 | path | tainted-string-steps.js:10:18:10:31 | path.substr(4) | +| tainted-string-steps.js:10:18:10:21 | path | tainted-string-steps.js:10:18:10:31 | path.substr(4) | +| tainted-string-steps.js:10:18:10:21 | path | tainted-string-steps.js:10:18:10:31 | path.substr(4) | +| tainted-string-steps.js:10:18:10:21 | path | tainted-string-steps.js:10:18:10:31 | path.substr(4) | +| tainted-string-steps.js:10:18:10:21 | path | tainted-string-steps.js:10:18:10:31 | path.substr(4) | +| tainted-string-steps.js:10:18:10:21 | path | tainted-string-steps.js:10:18:10:31 | path.substr(4) | +| tainted-string-steps.js:10:18:10:21 | path | tainted-string-steps.js:10:18:10:31 | path.substr(4) | +| tainted-string-steps.js:10:18:10:21 | path | tainted-string-steps.js:10:18:10:31 | path.substr(4) | +| tainted-string-steps.js:10:18:10:21 | path | tainted-string-steps.js:10:18:10:31 | path.substr(4) | +| tainted-string-steps.js:10:18:10:21 | path | tainted-string-steps.js:10:18:10:31 | path.substr(4) | +| tainted-string-steps.js:10:18:10:21 | path | tainted-string-steps.js:10:18:10:31 | path.substr(4) | +| tainted-string-steps.js:10:18:10:21 | path | tainted-string-steps.js:10:18:10:31 | path.substr(4) | +| tainted-string-steps.js:10:18:10:21 | path | tainted-string-steps.js:10:18:10:31 | path.substr(4) | +| tainted-string-steps.js:10:18:10:21 | path | tainted-string-steps.js:10:18:10:31 | path.substr(4) | +| tainted-string-steps.js:10:18:10:21 | path | tainted-string-steps.js:10:18:10:31 | path.substr(4) | +| tainted-string-steps.js:11:18:11:21 | path | tainted-string-steps.js:11:18:11:30 | path.slice(4) | +| tainted-string-steps.js:11:18:11:21 | path | tainted-string-steps.js:11:18:11:30 | path.slice(4) | +| tainted-string-steps.js:11:18:11:21 | path | tainted-string-steps.js:11:18:11:30 | path.slice(4) | +| tainted-string-steps.js:11:18:11:21 | path | tainted-string-steps.js:11:18:11:30 | path.slice(4) | +| tainted-string-steps.js:11:18:11:21 | path | tainted-string-steps.js:11:18:11:30 | path.slice(4) | +| tainted-string-steps.js:11:18:11:21 | path | tainted-string-steps.js:11:18:11:30 | path.slice(4) | +| tainted-string-steps.js:11:18:11:21 | path | tainted-string-steps.js:11:18:11:30 | path.slice(4) | +| tainted-string-steps.js:11:18:11:21 | path | tainted-string-steps.js:11:18:11:30 | path.slice(4) | +| tainted-string-steps.js:11:18:11:21 | path | tainted-string-steps.js:11:18:11:30 | path.slice(4) | +| tainted-string-steps.js:11:18:11:21 | path | tainted-string-steps.js:11:18:11:30 | path.slice(4) | +| tainted-string-steps.js:11:18:11:21 | path | tainted-string-steps.js:11:18:11:30 | path.slice(4) | +| tainted-string-steps.js:11:18:11:21 | path | tainted-string-steps.js:11:18:11:30 | path.slice(4) | +| tainted-string-steps.js:11:18:11:21 | path | tainted-string-steps.js:11:18:11:30 | path.slice(4) | +| tainted-string-steps.js:11:18:11:21 | path | tainted-string-steps.js:11:18:11:30 | path.slice(4) | +| tainted-string-steps.js:11:18:11:21 | path | tainted-string-steps.js:11:18:11:30 | path.slice(4) | +| tainted-string-steps.js:11:18:11:21 | path | tainted-string-steps.js:11:18:11:30 | path.slice(4) | +| tainted-string-steps.js:11:18:11:21 | path | tainted-string-steps.js:11:18:11:30 | path.slice(4) | +| tainted-string-steps.js:11:18:11:21 | path | tainted-string-steps.js:11:18:11:30 | path.slice(4) | +| tainted-string-steps.js:11:18:11:21 | path | tainted-string-steps.js:11:18:11:30 | path.slice(4) | +| tainted-string-steps.js:11:18:11:21 | path | tainted-string-steps.js:11:18:11:30 | path.slice(4) | +| tainted-string-steps.js:11:18:11:21 | path | tainted-string-steps.js:11:18:11:30 | path.slice(4) | +| tainted-string-steps.js:11:18:11:21 | path | tainted-string-steps.js:11:18:11:30 | path.slice(4) | +| tainted-string-steps.js:11:18:11:21 | path | tainted-string-steps.js:11:18:11:30 | path.slice(4) | +| tainted-string-steps.js:11:18:11:21 | path | tainted-string-steps.js:11:18:11:30 | path.slice(4) | +| tainted-string-steps.js:11:18:11:21 | path | tainted-string-steps.js:11:18:11:30 | path.slice(4) | +| tainted-string-steps.js:11:18:11:21 | path | tainted-string-steps.js:11:18:11:30 | path.slice(4) | +| tainted-string-steps.js:11:18:11:21 | path | tainted-string-steps.js:11:18:11:30 | path.slice(4) | +| tainted-string-steps.js:11:18:11:21 | path | tainted-string-steps.js:11:18:11:30 | path.slice(4) | +| tainted-string-steps.js:11:18:11:21 | path | tainted-string-steps.js:11:18:11:30 | path.slice(4) | +| tainted-string-steps.js:11:18:11:21 | path | tainted-string-steps.js:11:18:11:30 | path.slice(4) | +| tainted-string-steps.js:11:18:11:21 | path | tainted-string-steps.js:11:18:11:30 | path.slice(4) | +| tainted-string-steps.js:11:18:11:21 | path | tainted-string-steps.js:11:18:11:30 | path.slice(4) | +| tainted-string-steps.js:17:18:17:21 | path | tainted-string-steps.js:17:18:17:28 | path.trim() | +| tainted-string-steps.js:17:18:17:21 | path | tainted-string-steps.js:17:18:17:28 | path.trim() | +| tainted-string-steps.js:17:18:17:21 | path | tainted-string-steps.js:17:18:17:28 | path.trim() | +| tainted-string-steps.js:17:18:17:21 | path | tainted-string-steps.js:17:18:17:28 | path.trim() | +| tainted-string-steps.js:17:18:17:21 | path | tainted-string-steps.js:17:18:17:28 | path.trim() | +| tainted-string-steps.js:17:18:17:21 | path | tainted-string-steps.js:17:18:17:28 | path.trim() | +| tainted-string-steps.js:17:18:17:21 | path | tainted-string-steps.js:17:18:17:28 | path.trim() | +| tainted-string-steps.js:17:18:17:21 | path | tainted-string-steps.js:17:18:17:28 | path.trim() | +| tainted-string-steps.js:17:18:17:21 | path | tainted-string-steps.js:17:18:17:28 | path.trim() | +| tainted-string-steps.js:17:18:17:21 | path | tainted-string-steps.js:17:18:17:28 | path.trim() | +| tainted-string-steps.js:17:18:17:21 | path | tainted-string-steps.js:17:18:17:28 | path.trim() | +| tainted-string-steps.js:17:18:17:21 | path | tainted-string-steps.js:17:18:17:28 | path.trim() | +| tainted-string-steps.js:17:18:17:21 | path | tainted-string-steps.js:17:18:17:28 | path.trim() | +| tainted-string-steps.js:17:18:17:21 | path | tainted-string-steps.js:17:18:17:28 | path.trim() | +| tainted-string-steps.js:17:18:17:21 | path | tainted-string-steps.js:17:18:17:28 | path.trim() | +| tainted-string-steps.js:17:18:17:21 | path | tainted-string-steps.js:17:18:17:28 | path.trim() | +| tainted-string-steps.js:17:18:17:21 | path | tainted-string-steps.js:17:18:17:28 | path.trim() | +| tainted-string-steps.js:17:18:17:21 | path | tainted-string-steps.js:17:18:17:28 | path.trim() | +| tainted-string-steps.js:17:18:17:21 | path | tainted-string-steps.js:17:18:17:28 | path.trim() | +| tainted-string-steps.js:17:18:17:21 | path | tainted-string-steps.js:17:18:17:28 | path.trim() | +| tainted-string-steps.js:17:18:17:21 | path | tainted-string-steps.js:17:18:17:28 | path.trim() | +| tainted-string-steps.js:17:18:17:21 | path | tainted-string-steps.js:17:18:17:28 | path.trim() | +| tainted-string-steps.js:17:18:17:21 | path | tainted-string-steps.js:17:18:17:28 | path.trim() | +| tainted-string-steps.js:17:18:17:21 | path | tainted-string-steps.js:17:18:17:28 | path.trim() | +| tainted-string-steps.js:17:18:17:21 | path | tainted-string-steps.js:17:18:17:28 | path.trim() | +| tainted-string-steps.js:17:18:17:21 | path | tainted-string-steps.js:17:18:17:28 | path.trim() | +| tainted-string-steps.js:17:18:17:21 | path | tainted-string-steps.js:17:18:17:28 | path.trim() | +| tainted-string-steps.js:17:18:17:21 | path | tainted-string-steps.js:17:18:17:28 | path.trim() | +| tainted-string-steps.js:17:18:17:21 | path | tainted-string-steps.js:17:18:17:28 | path.trim() | +| tainted-string-steps.js:17:18:17:21 | path | tainted-string-steps.js:17:18:17:28 | path.trim() | +| tainted-string-steps.js:17:18:17:21 | path | tainted-string-steps.js:17:18:17:28 | path.trim() | +| tainted-string-steps.js:17:18:17:21 | path | tainted-string-steps.js:17:18:17:28 | path.trim() | +| tainted-string-steps.js:18:18:18:21 | path | tainted-string-steps.js:18:18:18:35 | path.toLowerCase() | +| tainted-string-steps.js:18:18:18:21 | path | tainted-string-steps.js:18:18:18:35 | path.toLowerCase() | +| tainted-string-steps.js:18:18:18:21 | path | tainted-string-steps.js:18:18:18:35 | path.toLowerCase() | +| tainted-string-steps.js:18:18:18:21 | path | tainted-string-steps.js:18:18:18:35 | path.toLowerCase() | +| tainted-string-steps.js:18:18:18:21 | path | tainted-string-steps.js:18:18:18:35 | path.toLowerCase() | +| tainted-string-steps.js:18:18:18:21 | path | tainted-string-steps.js:18:18:18:35 | path.toLowerCase() | +| tainted-string-steps.js:18:18:18:21 | path | tainted-string-steps.js:18:18:18:35 | path.toLowerCase() | +| tainted-string-steps.js:18:18:18:21 | path | tainted-string-steps.js:18:18:18:35 | path.toLowerCase() | +| tainted-string-steps.js:18:18:18:21 | path | tainted-string-steps.js:18:18:18:35 | path.toLowerCase() | +| tainted-string-steps.js:18:18:18:21 | path | tainted-string-steps.js:18:18:18:35 | path.toLowerCase() | +| tainted-string-steps.js:18:18:18:21 | path | tainted-string-steps.js:18:18:18:35 | path.toLowerCase() | +| tainted-string-steps.js:18:18:18:21 | path | tainted-string-steps.js:18:18:18:35 | path.toLowerCase() | +| tainted-string-steps.js:18:18:18:21 | path | tainted-string-steps.js:18:18:18:35 | path.toLowerCase() | +| tainted-string-steps.js:18:18:18:21 | path | tainted-string-steps.js:18:18:18:35 | path.toLowerCase() | +| tainted-string-steps.js:18:18:18:21 | path | tainted-string-steps.js:18:18:18:35 | path.toLowerCase() | +| tainted-string-steps.js:18:18:18:21 | path | tainted-string-steps.js:18:18:18:35 | path.toLowerCase() | +| tainted-string-steps.js:18:18:18:21 | path | tainted-string-steps.js:18:18:18:35 | path.toLowerCase() | +| tainted-string-steps.js:18:18:18:21 | path | tainted-string-steps.js:18:18:18:35 | path.toLowerCase() | +| tainted-string-steps.js:18:18:18:21 | path | tainted-string-steps.js:18:18:18:35 | path.toLowerCase() | +| tainted-string-steps.js:18:18:18:21 | path | tainted-string-steps.js:18:18:18:35 | path.toLowerCase() | +| tainted-string-steps.js:18:18:18:21 | path | tainted-string-steps.js:18:18:18:35 | path.toLowerCase() | +| tainted-string-steps.js:18:18:18:21 | path | tainted-string-steps.js:18:18:18:35 | path.toLowerCase() | +| tainted-string-steps.js:18:18:18:21 | path | tainted-string-steps.js:18:18:18:35 | path.toLowerCase() | +| tainted-string-steps.js:18:18:18:21 | path | tainted-string-steps.js:18:18:18:35 | path.toLowerCase() | +| tainted-string-steps.js:18:18:18:21 | path | tainted-string-steps.js:18:18:18:35 | path.toLowerCase() | +| tainted-string-steps.js:18:18:18:21 | path | tainted-string-steps.js:18:18:18:35 | path.toLowerCase() | +| tainted-string-steps.js:18:18:18:21 | path | tainted-string-steps.js:18:18:18:35 | path.toLowerCase() | +| tainted-string-steps.js:18:18:18:21 | path | tainted-string-steps.js:18:18:18:35 | path.toLowerCase() | +| tainted-string-steps.js:18:18:18:21 | path | tainted-string-steps.js:18:18:18:35 | path.toLowerCase() | +| tainted-string-steps.js:18:18:18:21 | path | tainted-string-steps.js:18:18:18:35 | path.toLowerCase() | +| tainted-string-steps.js:18:18:18:21 | path | tainted-string-steps.js:18:18:18:35 | path.toLowerCase() | +| tainted-string-steps.js:18:18:18:21 | path | tainted-string-steps.js:18:18:18:35 | path.toLowerCase() | +| tainted-string-steps.js:24:18:24:21 | path | tainted-string-steps.js:24:18:24:32 | path.split("?") | +| tainted-string-steps.js:24:18:24:21 | path | tainted-string-steps.js:24:18:24:32 | path.split("?") | +| tainted-string-steps.js:24:18:24:21 | path | tainted-string-steps.js:24:18:24:32 | path.split("?") | +| tainted-string-steps.js:24:18:24:21 | path | tainted-string-steps.js:24:18:24:32 | path.split("?") | +| tainted-string-steps.js:24:18:24:21 | path | tainted-string-steps.js:24:18:24:32 | path.split("?") | +| tainted-string-steps.js:24:18:24:21 | path | tainted-string-steps.js:24:18:24:32 | path.split("?") | +| tainted-string-steps.js:24:18:24:21 | path | tainted-string-steps.js:24:18:24:32 | path.split("?") | +| tainted-string-steps.js:24:18:24:21 | path | tainted-string-steps.js:24:18:24:32 | path.split("?") | +| tainted-string-steps.js:24:18:24:21 | path | tainted-string-steps.js:24:18:24:32 | path.split("?") | +| tainted-string-steps.js:24:18:24:21 | path | tainted-string-steps.js:24:18:24:32 | path.split("?") | +| tainted-string-steps.js:24:18:24:21 | path | tainted-string-steps.js:24:18:24:32 | path.split("?") | +| tainted-string-steps.js:24:18:24:21 | path | tainted-string-steps.js:24:18:24:32 | path.split("?") | +| tainted-string-steps.js:24:18:24:21 | path | tainted-string-steps.js:24:18:24:32 | path.split("?") | +| tainted-string-steps.js:24:18:24:21 | path | tainted-string-steps.js:24:18:24:32 | path.split("?") | +| tainted-string-steps.js:24:18:24:21 | path | tainted-string-steps.js:24:18:24:32 | path.split("?") | +| tainted-string-steps.js:24:18:24:21 | path | tainted-string-steps.js:24:18:24:32 | path.split("?") | +| tainted-string-steps.js:24:18:24:32 | path.split("?") | tainted-string-steps.js:24:18:24:35 | path.split("?")[0] | +| tainted-string-steps.js:24:18:24:32 | path.split("?") | tainted-string-steps.js:24:18:24:35 | path.split("?")[0] | +| tainted-string-steps.js:24:18:24:32 | path.split("?") | tainted-string-steps.js:24:18:24:35 | path.split("?")[0] | +| tainted-string-steps.js:24:18:24:32 | path.split("?") | tainted-string-steps.js:24:18:24:35 | path.split("?")[0] | +| tainted-string-steps.js:24:18:24:32 | path.split("?") | tainted-string-steps.js:24:18:24:35 | path.split("?")[0] | +| tainted-string-steps.js:24:18:24:32 | path.split("?") | tainted-string-steps.js:24:18:24:35 | path.split("?")[0] | +| tainted-string-steps.js:24:18:24:32 | path.split("?") | tainted-string-steps.js:24:18:24:35 | path.split("?")[0] | +| tainted-string-steps.js:24:18:24:32 | path.split("?") | tainted-string-steps.js:24:18:24:35 | path.split("?")[0] | +| tainted-string-steps.js:24:18:24:32 | path.split("?") | tainted-string-steps.js:24:18:24:35 | path.split("?")[0] | +| tainted-string-steps.js:24:18:24:32 | path.split("?") | tainted-string-steps.js:24:18:24:35 | path.split("?")[0] | +| tainted-string-steps.js:24:18:24:32 | path.split("?") | tainted-string-steps.js:24:18:24:35 | path.split("?")[0] | +| tainted-string-steps.js:24:18:24:32 | path.split("?") | tainted-string-steps.js:24:18:24:35 | path.split("?")[0] | +| tainted-string-steps.js:24:18:24:32 | path.split("?") | tainted-string-steps.js:24:18:24:35 | path.split("?")[0] | +| tainted-string-steps.js:24:18:24:32 | path.split("?") | tainted-string-steps.js:24:18:24:35 | path.split("?")[0] | +| tainted-string-steps.js:24:18:24:32 | path.split("?") | tainted-string-steps.js:24:18:24:35 | path.split("?")[0] | +| tainted-string-steps.js:24:18:24:32 | path.split("?") | tainted-string-steps.js:24:18:24:35 | path.split("?")[0] | +| tainted-string-steps.js:24:18:24:32 | path.split("?") | tainted-string-steps.js:24:18:24:35 | path.split("?")[0] | +| tainted-string-steps.js:24:18:24:32 | path.split("?") | tainted-string-steps.js:24:18:24:35 | path.split("?")[0] | +| tainted-string-steps.js:24:18:24:32 | path.split("?") | tainted-string-steps.js:24:18:24:35 | path.split("?")[0] | +| tainted-string-steps.js:24:18:24:32 | path.split("?") | tainted-string-steps.js:24:18:24:35 | path.split("?")[0] | +| tainted-string-steps.js:24:18:24:32 | path.split("?") | tainted-string-steps.js:24:18:24:35 | path.split("?")[0] | +| tainted-string-steps.js:24:18:24:32 | path.split("?") | tainted-string-steps.js:24:18:24:35 | path.split("?")[0] | +| tainted-string-steps.js:24:18:24:32 | path.split("?") | tainted-string-steps.js:24:18:24:35 | path.split("?")[0] | +| tainted-string-steps.js:24:18:24:32 | path.split("?") | tainted-string-steps.js:24:18:24:35 | path.split("?")[0] | +| tainted-string-steps.js:24:18:24:32 | path.split("?") | tainted-string-steps.js:24:18:24:35 | path.split("?")[0] | +| tainted-string-steps.js:24:18:24:32 | path.split("?") | tainted-string-steps.js:24:18:24:35 | path.split("?")[0] | +| tainted-string-steps.js:24:18:24:32 | path.split("?") | tainted-string-steps.js:24:18:24:35 | path.split("?")[0] | +| tainted-string-steps.js:24:18:24:32 | path.split("?") | tainted-string-steps.js:24:18:24:35 | path.split("?")[0] | +| tainted-string-steps.js:24:18:24:32 | path.split("?") | tainted-string-steps.js:24:18:24:35 | path.split("?")[0] | +| tainted-string-steps.js:24:18:24:32 | path.split("?") | tainted-string-steps.js:24:18:24:35 | path.split("?")[0] | +| tainted-string-steps.js:24:18:24:32 | path.split("?") | tainted-string-steps.js:24:18:24:35 | path.split("?")[0] | +| tainted-string-steps.js:24:18:24:32 | path.split("?") | tainted-string-steps.js:24:18:24:35 | path.split("?")[0] | +| tainted-string-steps.js:26:18:26:21 | path | tainted-string-steps.js:26:18:26:36 | path.split(unknown) | +| tainted-string-steps.js:26:18:26:21 | path | tainted-string-steps.js:26:18:26:36 | path.split(unknown) | +| tainted-string-steps.js:26:18:26:21 | path | tainted-string-steps.js:26:18:26:36 | path.split(unknown) | +| tainted-string-steps.js:26:18:26:21 | path | tainted-string-steps.js:26:18:26:36 | path.split(unknown) | +| tainted-string-steps.js:26:18:26:21 | path | tainted-string-steps.js:26:18:26:36 | path.split(unknown) | +| tainted-string-steps.js:26:18:26:21 | path | tainted-string-steps.js:26:18:26:36 | path.split(unknown) | +| tainted-string-steps.js:26:18:26:21 | path | tainted-string-steps.js:26:18:26:36 | path.split(unknown) | +| tainted-string-steps.js:26:18:26:21 | path | tainted-string-steps.js:26:18:26:36 | path.split(unknown) | +| tainted-string-steps.js:26:18:26:21 | path | tainted-string-steps.js:26:18:26:36 | path.split(unknown) | +| tainted-string-steps.js:26:18:26:21 | path | tainted-string-steps.js:26:18:26:36 | path.split(unknown) | +| tainted-string-steps.js:26:18:26:21 | path | tainted-string-steps.js:26:18:26:36 | path.split(unknown) | +| tainted-string-steps.js:26:18:26:21 | path | tainted-string-steps.js:26:18:26:36 | path.split(unknown) | +| tainted-string-steps.js:26:18:26:21 | path | tainted-string-steps.js:26:18:26:36 | path.split(unknown) | +| tainted-string-steps.js:26:18:26:21 | path | tainted-string-steps.js:26:18:26:36 | path.split(unknown) | +| tainted-string-steps.js:26:18:26:21 | path | tainted-string-steps.js:26:18:26:36 | path.split(unknown) | +| tainted-string-steps.js:26:18:26:21 | path | tainted-string-steps.js:26:18:26:36 | path.split(unknown) | +| tainted-string-steps.js:26:18:26:36 | path.split(unknown) | tainted-string-steps.js:26:18:26:45 | path.sp ... hatever | +| tainted-string-steps.js:26:18:26:36 | path.split(unknown) | tainted-string-steps.js:26:18:26:45 | path.sp ... hatever | +| tainted-string-steps.js:26:18:26:36 | path.split(unknown) | tainted-string-steps.js:26:18:26:45 | path.sp ... hatever | +| tainted-string-steps.js:26:18:26:36 | path.split(unknown) | tainted-string-steps.js:26:18:26:45 | path.sp ... hatever | +| tainted-string-steps.js:26:18:26:36 | path.split(unknown) | tainted-string-steps.js:26:18:26:45 | path.sp ... hatever | +| tainted-string-steps.js:26:18:26:36 | path.split(unknown) | tainted-string-steps.js:26:18:26:45 | path.sp ... hatever | +| tainted-string-steps.js:26:18:26:36 | path.split(unknown) | tainted-string-steps.js:26:18:26:45 | path.sp ... hatever | +| tainted-string-steps.js:26:18:26:36 | path.split(unknown) | tainted-string-steps.js:26:18:26:45 | path.sp ... hatever | +| tainted-string-steps.js:26:18:26:36 | path.split(unknown) | tainted-string-steps.js:26:18:26:45 | path.sp ... hatever | +| tainted-string-steps.js:26:18:26:36 | path.split(unknown) | tainted-string-steps.js:26:18:26:45 | path.sp ... hatever | +| tainted-string-steps.js:26:18:26:36 | path.split(unknown) | tainted-string-steps.js:26:18:26:45 | path.sp ... hatever | +| tainted-string-steps.js:26:18:26:36 | path.split(unknown) | tainted-string-steps.js:26:18:26:45 | path.sp ... hatever | +| tainted-string-steps.js:26:18:26:36 | path.split(unknown) | tainted-string-steps.js:26:18:26:45 | path.sp ... hatever | +| tainted-string-steps.js:26:18:26:36 | path.split(unknown) | tainted-string-steps.js:26:18:26:45 | path.sp ... hatever | +| tainted-string-steps.js:26:18:26:36 | path.split(unknown) | tainted-string-steps.js:26:18:26:45 | path.sp ... hatever | +| tainted-string-steps.js:26:18:26:36 | path.split(unknown) | tainted-string-steps.js:26:18:26:45 | path.sp ... hatever | +| tainted-string-steps.js:26:18:26:36 | path.split(unknown) | tainted-string-steps.js:26:18:26:45 | path.sp ... hatever | +| tainted-string-steps.js:26:18:26:36 | path.split(unknown) | tainted-string-steps.js:26:18:26:45 | path.sp ... hatever | +| tainted-string-steps.js:26:18:26:36 | path.split(unknown) | tainted-string-steps.js:26:18:26:45 | path.sp ... hatever | +| tainted-string-steps.js:26:18:26:36 | path.split(unknown) | tainted-string-steps.js:26:18:26:45 | path.sp ... hatever | +| tainted-string-steps.js:26:18:26:36 | path.split(unknown) | tainted-string-steps.js:26:18:26:45 | path.sp ... hatever | +| tainted-string-steps.js:26:18:26:36 | path.split(unknown) | tainted-string-steps.js:26:18:26:45 | path.sp ... hatever | +| tainted-string-steps.js:26:18:26:36 | path.split(unknown) | tainted-string-steps.js:26:18:26:45 | path.sp ... hatever | +| tainted-string-steps.js:26:18:26:36 | path.split(unknown) | tainted-string-steps.js:26:18:26:45 | path.sp ... hatever | +| tainted-string-steps.js:26:18:26:36 | path.split(unknown) | tainted-string-steps.js:26:18:26:45 | path.sp ... hatever | +| tainted-string-steps.js:26:18:26:36 | path.split(unknown) | tainted-string-steps.js:26:18:26:45 | path.sp ... hatever | +| tainted-string-steps.js:26:18:26:36 | path.split(unknown) | tainted-string-steps.js:26:18:26:45 | path.sp ... hatever | +| tainted-string-steps.js:26:18:26:36 | path.split(unknown) | tainted-string-steps.js:26:18:26:45 | path.sp ... hatever | +| tainted-string-steps.js:26:18:26:36 | path.split(unknown) | tainted-string-steps.js:26:18:26:45 | path.sp ... hatever | +| tainted-string-steps.js:26:18:26:36 | path.split(unknown) | tainted-string-steps.js:26:18:26:45 | path.sp ... hatever | +| tainted-string-steps.js:26:18:26:36 | path.split(unknown) | tainted-string-steps.js:26:18:26:45 | path.sp ... hatever | +| tainted-string-steps.js:26:18:26:36 | path.split(unknown) | tainted-string-steps.js:26:18:26:45 | path.sp ... hatever | +| tainted-string-steps.js:27:18:27:21 | path | tainted-string-steps.js:27:18:27:36 | path.split(unknown) | +| tainted-string-steps.js:27:18:27:21 | path | tainted-string-steps.js:27:18:27:36 | path.split(unknown) | +| tainted-string-steps.js:27:18:27:21 | path | tainted-string-steps.js:27:18:27:36 | path.split(unknown) | +| tainted-string-steps.js:27:18:27:21 | path | tainted-string-steps.js:27:18:27:36 | path.split(unknown) | +| tainted-string-steps.js:27:18:27:21 | path | tainted-string-steps.js:27:18:27:36 | path.split(unknown) | +| tainted-string-steps.js:27:18:27:21 | path | tainted-string-steps.js:27:18:27:36 | path.split(unknown) | +| tainted-string-steps.js:27:18:27:21 | path | tainted-string-steps.js:27:18:27:36 | path.split(unknown) | +| tainted-string-steps.js:27:18:27:21 | path | tainted-string-steps.js:27:18:27:36 | path.split(unknown) | +| tainted-string-steps.js:27:18:27:21 | path | tainted-string-steps.js:27:18:27:36 | path.split(unknown) | +| tainted-string-steps.js:27:18:27:21 | path | tainted-string-steps.js:27:18:27:36 | path.split(unknown) | +| tainted-string-steps.js:27:18:27:21 | path | tainted-string-steps.js:27:18:27:36 | path.split(unknown) | +| tainted-string-steps.js:27:18:27:21 | path | tainted-string-steps.js:27:18:27:36 | path.split(unknown) | +| tainted-string-steps.js:27:18:27:21 | path | tainted-string-steps.js:27:18:27:36 | path.split(unknown) | +| tainted-string-steps.js:27:18:27:21 | path | tainted-string-steps.js:27:18:27:36 | path.split(unknown) | +| tainted-string-steps.js:27:18:27:21 | path | tainted-string-steps.js:27:18:27:36 | path.split(unknown) | +| tainted-string-steps.js:27:18:27:21 | path | tainted-string-steps.js:27:18:27:36 | path.split(unknown) | +| tainted-string-steps.js:27:18:27:21 | path | tainted-string-steps.js:27:18:27:36 | path.split(unknown) | +| tainted-string-steps.js:27:18:27:21 | path | tainted-string-steps.js:27:18:27:36 | path.split(unknown) | +| tainted-string-steps.js:27:18:27:21 | path | tainted-string-steps.js:27:18:27:36 | path.split(unknown) | +| tainted-string-steps.js:27:18:27:21 | path | tainted-string-steps.js:27:18:27:36 | path.split(unknown) | +| tainted-string-steps.js:27:18:27:21 | path | tainted-string-steps.js:27:18:27:36 | path.split(unknown) | +| tainted-string-steps.js:27:18:27:21 | path | tainted-string-steps.js:27:18:27:36 | path.split(unknown) | +| tainted-string-steps.js:27:18:27:21 | path | tainted-string-steps.js:27:18:27:36 | path.split(unknown) | +| tainted-string-steps.js:27:18:27:21 | path | tainted-string-steps.js:27:18:27:36 | path.split(unknown) | +| tainted-string-steps.js:27:18:27:21 | path | tainted-string-steps.js:27:18:27:36 | path.split(unknown) | +| tainted-string-steps.js:27:18:27:21 | path | tainted-string-steps.js:27:18:27:36 | path.split(unknown) | +| tainted-string-steps.js:27:18:27:21 | path | tainted-string-steps.js:27:18:27:36 | path.split(unknown) | +| tainted-string-steps.js:27:18:27:21 | path | tainted-string-steps.js:27:18:27:36 | path.split(unknown) | +| tainted-string-steps.js:27:18:27:21 | path | tainted-string-steps.js:27:18:27:36 | path.split(unknown) | +| tainted-string-steps.js:27:18:27:21 | path | tainted-string-steps.js:27:18:27:36 | path.split(unknown) | +| tainted-string-steps.js:27:18:27:21 | path | tainted-string-steps.js:27:18:27:36 | path.split(unknown) | +| tainted-string-steps.js:27:18:27:21 | path | tainted-string-steps.js:27:18:27:36 | path.split(unknown) | | torrents.js:5:6:5:38 | name | torrents.js:6:24:6:27 | name | | torrents.js:5:6:5:38 | name | torrents.js:6:24:6:27 | name | | torrents.js:5:6:5:38 | name | torrents.js:6:24:6:27 | name | @@ -3016,11 +4022,23 @@ edges | normalizedPaths.js:210:21:210:34 | normalizedPath | normalizedPaths.js:174:14:174:27 | req.query.path | normalizedPaths.js:210:21:210:34 | normalizedPath | This path depends on $@. | normalizedPaths.js:174:14:174:27 | req.query.path | a user-provided value | | normalizedPaths.js:222:21:222:24 | path | normalizedPaths.js:214:35:214:48 | req.query.path | normalizedPaths.js:222:21:222:24 | path | This path depends on $@. | normalizedPaths.js:214:35:214:48 | req.query.path | a user-provided value | | normalizedPaths.js:228:21:228:24 | path | normalizedPaths.js:226:35:226:48 | req.query.path | normalizedPaths.js:228:21:228:24 | path | This path depends on $@. | normalizedPaths.js:226:35:226:48 | req.query.path | a user-provided value | +| normalizedPaths.js:238:19:238:22 | path | normalizedPaths.js:236:33:236:46 | req.query.path | normalizedPaths.js:238:19:238:22 | path | This path depends on $@. | normalizedPaths.js:236:33:236:46 | req.query.path | a user-provided value | +| normalizedPaths.js:245:21:245:24 | path | normalizedPaths.js:236:33:236:46 | req.query.path | normalizedPaths.js:245:21:245:24 | path | This path depends on $@. | normalizedPaths.js:236:33:236:46 | req.query.path | a user-provided value | +| normalizedPaths.js:250:21:250:24 | path | normalizedPaths.js:236:33:236:46 | req.query.path | normalizedPaths.js:250:21:250:24 | path | This path depends on $@. | normalizedPaths.js:236:33:236:46 | req.query.path | a user-provided value | | tainted-require.js:7:19:7:37 | req.param("module") | tainted-require.js:7:19:7:37 | req.param("module") | tainted-require.js:7:19:7:37 | req.param("module") | This path depends on $@. | tainted-require.js:7:19:7:37 | req.param("module") | a user-provided value | | tainted-sendFile.js:8:16:8:33 | req.param("gimme") | tainted-sendFile.js:8:16:8:33 | req.param("gimme") | tainted-sendFile.js:8:16:8:33 | req.param("gimme") | This path depends on $@. | tainted-sendFile.js:8:16:8:33 | req.param("gimme") | a user-provided value | | tainted-sendFile.js:10:16:10:33 | req.param("gimme") | tainted-sendFile.js:10:16:10:33 | req.param("gimme") | tainted-sendFile.js:10:16:10:33 | req.param("gimme") | This path depends on $@. | tainted-sendFile.js:10:16:10:33 | req.param("gimme") | a user-provided value | | tainted-sendFile.js:18:43:18:58 | req.param("dir") | tainted-sendFile.js:18:43:18:58 | req.param("dir") | tainted-sendFile.js:18:43:18:58 | req.param("dir") | This path depends on $@. | tainted-sendFile.js:18:43:18:58 | req.param("dir") | a user-provided value | | tainted-sendFile.js:24:16:24:49 | path.re ... rams.x) | tainted-sendFile.js:24:37:24:48 | req.params.x | tainted-sendFile.js:24:16:24:49 | path.re ... rams.x) | This path depends on $@. | tainted-sendFile.js:24:37:24:48 | req.params.x | a user-provided value | | tainted-sendFile.js:25:16:25:46 | path.jo ... rams.x) | tainted-sendFile.js:25:34:25:45 | req.params.x | tainted-sendFile.js:25:16:25:46 | path.jo ... rams.x) | This path depends on $@. | tainted-sendFile.js:25:34:25:45 | req.params.x | a user-provided value | +| tainted-string-steps.js:8:18:8:34 | path.substring(4) | tainted-string-steps.js:6:24:6:30 | req.url | tainted-string-steps.js:8:18:8:34 | path.substring(4) | This path depends on $@. | tainted-string-steps.js:6:24:6:30 | req.url | a user-provided value | +| tainted-string-steps.js:9:18:9:37 | path.substring(0, i) | tainted-string-steps.js:6:24:6:30 | req.url | tainted-string-steps.js:9:18:9:37 | path.substring(0, i) | This path depends on $@. | tainted-string-steps.js:6:24:6:30 | req.url | a user-provided value | +| tainted-string-steps.js:10:18:10:31 | path.substr(4) | tainted-string-steps.js:6:24:6:30 | req.url | tainted-string-steps.js:10:18:10:31 | path.substr(4) | This path depends on $@. | tainted-string-steps.js:6:24:6:30 | req.url | a user-provided value | +| tainted-string-steps.js:11:18:11:30 | path.slice(4) | tainted-string-steps.js:6:24:6:30 | req.url | tainted-string-steps.js:11:18:11:30 | path.slice(4) | This path depends on $@. | tainted-string-steps.js:6:24:6:30 | req.url | a user-provided value | +| tainted-string-steps.js:17:18:17:28 | path.trim() | tainted-string-steps.js:6:24:6:30 | req.url | tainted-string-steps.js:17:18:17:28 | path.trim() | This path depends on $@. | tainted-string-steps.js:6:24:6:30 | req.url | a user-provided value | +| tainted-string-steps.js:18:18:18:35 | path.toLowerCase() | tainted-string-steps.js:6:24:6:30 | req.url | tainted-string-steps.js:18:18:18:35 | path.toLowerCase() | This path depends on $@. | tainted-string-steps.js:6:24:6:30 | req.url | a user-provided value | +| tainted-string-steps.js:24:18:24:35 | path.split("?")[0] | tainted-string-steps.js:6:24:6:30 | req.url | tainted-string-steps.js:24:18:24:35 | path.split("?")[0] | This path depends on $@. | tainted-string-steps.js:6:24:6:30 | req.url | a user-provided value | +| tainted-string-steps.js:26:18:26:45 | path.sp ... hatever | tainted-string-steps.js:6:24:6:30 | req.url | tainted-string-steps.js:26:18:26:45 | path.sp ... hatever | This path depends on $@. | tainted-string-steps.js:6:24:6:30 | req.url | a user-provided value | +| tainted-string-steps.js:27:18:27:36 | path.split(unknown) | tainted-string-steps.js:6:24:6:30 | req.url | tainted-string-steps.js:27:18:27:36 | path.split(unknown) | This path depends on $@. | tainted-string-steps.js:6:24:6:30 | req.url | a user-provided value | | torrents.js:7:25:7:27 | loc | torrents.js:5:13:5:38 | parseTo ... t).name | torrents.js:7:25:7:27 | loc | This path depends on $@. | torrents.js:5:13:5:38 | parseTo ... t).name | a user-provided value | | views.js:1:43:1:55 | req.params[0] | views.js:1:43:1:55 | req.params[0] | views.js:1:43:1:55 | req.params[0] | This path depends on $@. | views.js:1:43:1:55 | req.params[0] | a user-provided value | diff --git a/javascript/ql/test/query-tests/Security/CWE-022/TaintedPath/normalizedPaths.js b/javascript/ql/test/query-tests/Security/CWE-022/TaintedPath/normalizedPaths.js index cc55a3422dc..c0be777dd84 100644 --- a/javascript/ql/test/query-tests/Security/CWE-022/TaintedPath/normalizedPaths.js +++ b/javascript/ql/test/query-tests/Security/CWE-022/TaintedPath/normalizedPaths.js @@ -231,3 +231,21 @@ app.get('/replace', (req, res) => { fs.readFileSync(path); // OK } }); + +app.get('/resolve-path', (req, res) => { + let path = pathModule.resolve(req.query.path); + + fs.readFileSync(path); // NOT OK + + var self = something(); + + if (path.substring(0, self.dir.length) === self.dir) + fs.readFileSync(path); // OK + else + fs.readFileSync(path); // NOT OK - wrong polarity + + if (path.slice(0, self.dir.length) === self.dir) + fs.readFileSync(path); // OK + else + fs.readFileSync(path); // NOT OK - wrong polarity +}); diff --git a/javascript/ql/test/query-tests/Security/CWE-022/TaintedPath/tainted-string-steps.js b/javascript/ql/test/query-tests/Security/CWE-022/TaintedPath/tainted-string-steps.js new file mode 100644 index 00000000000..6651463ee52 --- /dev/null +++ b/javascript/ql/test/query-tests/Security/CWE-022/TaintedPath/tainted-string-steps.js @@ -0,0 +1,30 @@ +var fs = require('fs'), + http = require('http'), + url = require('url'); + +var server = http.createServer(function(req, res) { + let path = url.parse(req.url, true).query.path; + fs.readFileSync(path.substring(i, j)); // OK + fs.readFileSync(path.substring(4)); // NOT OK + fs.readFileSync(path.substring(0, i)); // NOT OK + fs.readFileSync(path.substr(4)); // NOT OK + fs.readFileSync(path.slice(4)); // NOT OK + + fs.readFileSync(path.concat(unknown)); // NOT OK -- but not yet flagged + fs.readFileSync(unknown.concat(path)); // NOT OK -- but not yet flagged + fs.readFileSync(unknown.concat(unknown, path)); // NOT OK -- but not yet flagged + + fs.readFileSync(path.trim()); // NOT OK + fs.readFileSync(path.toLowerCase()); // NOT OK + + fs.readFileSync(path.split('/')); // OK -- for now + fs.readFileSync(path.split('/')[0]); // OK -- for now + fs.readFileSync(path.split('/')[i]); // OK -- for now + fs.readFileSync(path.split(/\//)[i]); // OK -- for now + fs.readFileSync(path.split("?")[0]); // NOT OK + fs.readFileSync(path.split(unknown)[i]); // NOT OK -- but not yet flagged + fs.readFileSync(path.split(unknown).whatever); // OK -- but still flagged + fs.readFileSync(path.split(unknown)); // NOT OK +}); + +server.listen(); diff --git a/javascript/ql/test/query-tests/Security/CWE-079/ReflectedXss.expected b/javascript/ql/test/query-tests/Security/CWE-079/ReflectedXss.expected index bcf54b75580..e283a5676fa 100644 --- a/javascript/ql/test/query-tests/Security/CWE-079/ReflectedXss.expected +++ b/javascript/ql/test/query-tests/Security/CWE-079/ReflectedXss.expected @@ -9,6 +9,9 @@ nodes | etherpad.js:9:16:9:53 | req.que ... e + ")" | | etherpad.js:11:12:11:19 | response | | etherpad.js:11:12:11:19 | response | +| exception-xss.js:190:12:190:24 | req.params.id | +| exception-xss.js:190:12:190:24 | req.params.id | +| exception-xss.js:190:12:190:24 | req.params.id | | formatting.js:4:9:4:29 | evil | | formatting.js:4:16:4:29 | req.query.evil | | formatting.js:4:16:4:29 | req.query.evil | @@ -77,6 +80,7 @@ edges | etherpad.js:9:16:9:30 | req.query.jsonp | etherpad.js:9:16:9:53 | req.que ... e + ")" | | etherpad.js:9:16:9:30 | req.query.jsonp | etherpad.js:9:16:9:53 | req.que ... e + ")" | | etherpad.js:9:16:9:53 | req.que ... e + ")" | etherpad.js:9:5:9:53 | response | +| exception-xss.js:190:12:190:24 | req.params.id | exception-xss.js:190:12:190:24 | req.params.id | | formatting.js:4:9:4:29 | evil | formatting.js:6:43:6:46 | evil | | formatting.js:4:9:4:29 | evil | formatting.js:7:49:7:52 | evil | | formatting.js:4:16:4:29 | req.query.evil | formatting.js:4:9:4:29 | evil | @@ -131,6 +135,7 @@ edges #select | ReflectedXss.js:8:14:8:45 | "Unknow ... rams.id | ReflectedXss.js:8:33:8:45 | req.params.id | ReflectedXss.js:8:14:8:45 | "Unknow ... rams.id | Cross-site scripting vulnerability due to $@. | ReflectedXss.js:8:33:8:45 | req.params.id | user-provided value | | etherpad.js:11:12:11:19 | response | etherpad.js:9:16:9:30 | req.query.jsonp | etherpad.js:11:12:11:19 | response | Cross-site scripting vulnerability due to $@. | etherpad.js:9:16:9:30 | req.query.jsonp | user-provided value | +| exception-xss.js:190:12:190:24 | req.params.id | exception-xss.js:190:12:190:24 | req.params.id | exception-xss.js:190:12:190:24 | req.params.id | Cross-site scripting vulnerability due to $@. | exception-xss.js:190:12:190:24 | req.params.id | user-provided value | | formatting.js:6:14:6:47 | util.fo ... , evil) | formatting.js:4:16:4:29 | req.query.evil | formatting.js:6:14:6:47 | util.fo ... , evil) | Cross-site scripting vulnerability due to $@. | formatting.js:4:16:4:29 | req.query.evil | user-provided value | | formatting.js:7:14:7:53 | require ... , evil) | formatting.js:4:16:4:29 | req.query.evil | formatting.js:7:14:7:53 | require ... , evil) | Cross-site scripting vulnerability due to $@. | formatting.js:4:16:4:29 | req.query.evil | user-provided value | | partial.js:10:14:10:18 | x + y | partial.js:13:42:13:48 | req.url | partial.js:10:14:10:18 | x + y | Cross-site scripting vulnerability due to $@. | partial.js:13:42:13:48 | req.url | user-provided value | diff --git a/javascript/ql/test/query-tests/Security/CWE-079/ReflectedXssWithCustomSanitizer.expected b/javascript/ql/test/query-tests/Security/CWE-079/ReflectedXssWithCustomSanitizer.expected index 72c9ff0819f..560dca58553 100644 --- a/javascript/ql/test/query-tests/Security/CWE-079/ReflectedXssWithCustomSanitizer.expected +++ b/javascript/ql/test/query-tests/Security/CWE-079/ReflectedXssWithCustomSanitizer.expected @@ -1,4 +1,5 @@ | ReflectedXss.js:8:14:8:45 | "Unknow ... rams.id | Cross-site scripting vulnerability due to $@. | ReflectedXss.js:8:33:8:45 | req.params.id | user-provided value | +| exception-xss.js:190:12:190:24 | req.params.id | Cross-site scripting vulnerability due to $@. | exception-xss.js:190:12:190:24 | req.params.id | user-provided value | | formatting.js:6:14:6:47 | util.fo ... , evil) | Cross-site scripting vulnerability due to $@. | formatting.js:4:16:4:29 | req.query.evil | user-provided value | | formatting.js:7:14:7:53 | require ... , evil) | Cross-site scripting vulnerability due to $@. | formatting.js:4:16:4:29 | req.query.evil | user-provided value | | partial.js:10:14:10:18 | x + y | Cross-site scripting vulnerability due to $@. | partial.js:13:42:13:48 | req.url | user-provided value | diff --git a/javascript/ql/test/query-tests/Security/CWE-079/exception-xss.js b/javascript/ql/test/query-tests/Security/CWE-079/exception-xss.js index a9cc01a0a76..005c5889edd 100644 --- a/javascript/ql/test/query-tests/Security/CWE-079/exception-xss.js +++ b/javascript/ql/test/query-tests/Security/CWE-079/exception-xss.js @@ -183,4 +183,34 @@ app.get('/user/:id', function (req, res) { } $('myId').html(res); // NOT OK! }); -}); \ No newline at end of file +}); + +app.get('/user/:id', function (req, res) { + try { + res.send(req.params.id); + } catch(err) { + res.send(err); // OK (the above `res.send()` is already reported by js/xss) + } +}); + +var fs = require("fs"); + +(function () { + var foo = document.location.search; + + try { + // A series of functions does not throw tainted exceptions. + Object.assign(foo, foo) + _.pick(foo, foo); + [foo, foo].join(join); + $.val(foo); + JSON.parse(foo); + /bla/.test(foo); + console.log(foo); + log.info(foo); + localStorage.setItem(foo); + } catch (e) { + $('myId').html(e); // OK + } + +})(); \ No newline at end of file diff --git a/javascript/ql/test/query-tests/Security/CWE-079/tst.js b/javascript/ql/test/query-tests/Security/CWE-079/tst.js index 9a0d34b277a..1ec5610ff83 100644 --- a/javascript/ql/test/query-tests/Security/CWE-079/tst.js +++ b/javascript/ql/test/query-tests/Security/CWE-079/tst.js @@ -318,3 +318,10 @@ function basicExceptions() { function handlebarsSafeString() { return new Handlebars.SafeString(location); // NOT OK! } + +function test2() { + var target = document.location.search + + // OK + $('myId').html(target.length) +} \ No newline at end of file diff --git a/javascript/ql/test/query-tests/Security/CWE-400/PrototypePollutionUtility.expected b/javascript/ql/test/query-tests/Security/CWE-400/PrototypePollutionUtility.expected index fe8fb9351d0..9d80066ebfb 100644 --- a/javascript/ql/test/query-tests/Security/CWE-400/PrototypePollutionUtility.expected +++ b/javascript/ql/test/query-tests/Security/CWE-400/PrototypePollutionUtility.expected @@ -302,24 +302,6 @@ nodes | PrototypePollutionUtility/tests.js:121:24:121:31 | src[key] | | PrototypePollutionUtility/tests.js:121:28:121:30 | key | | PrototypePollutionUtility/tests.js:121:28:121:30 | key | -| PrototypePollutionUtility/tests.js:127:14:127:16 | key | -| PrototypePollutionUtility/tests.js:127:14:127:16 | key | -| PrototypePollutionUtility/tests.js:127:14:127:16 | key | -| PrototypePollutionUtility/tests.js:128:13:128:15 | key | -| PrototypePollutionUtility/tests.js:128:13:128:15 | key | -| PrototypePollutionUtility/tests.js:128:13:128:15 | key | -| PrototypePollutionUtility/tests.js:128:20:128:27 | src[key] | -| PrototypePollutionUtility/tests.js:128:20:128:27 | src[key] | -| PrototypePollutionUtility/tests.js:128:20:128:27 | src[key] | -| PrototypePollutionUtility/tests.js:128:20:128:27 | src[key] | -| PrototypePollutionUtility/tests.js:128:24:128:26 | key | -| PrototypePollutionUtility/tests.js:128:24:128:26 | key | -| PrototypePollutionUtility/tests.js:143:14:143:16 | key | -| PrototypePollutionUtility/tests.js:143:14:143:16 | key | -| PrototypePollutionUtility/tests.js:143:14:143:16 | key | -| PrototypePollutionUtility/tests.js:144:16:144:18 | key | -| PrototypePollutionUtility/tests.js:144:16:144:18 | key | -| PrototypePollutionUtility/tests.js:144:16:144:18 | key | | PrototypePollutionUtility/tests.js:149:31:149:33 | dst | | PrototypePollutionUtility/tests.js:149:31:149:33 | dst | | PrototypePollutionUtility/tests.js:149:31:149:33 | dst | @@ -548,9 +530,6 @@ nodes | PrototypePollutionUtility/tests.js:213:29:213:32 | key2 | | PrototypePollutionUtility/tests.js:213:35:213:39 | value | | PrototypePollutionUtility/tests.js:213:35:213:39 | value | -| PrototypePollutionUtility/tests.js:215:13:215:16 | key1 | -| PrototypePollutionUtility/tests.js:215:13:215:16 | key1 | -| PrototypePollutionUtility/tests.js:215:13:215:16 | key1 | | PrototypePollutionUtility/tests.js:217:5:217:13 | map[key1] | | PrototypePollutionUtility/tests.js:217:5:217:13 | map[key1] | | PrototypePollutionUtility/tests.js:217:5:217:13 | map[key1] | @@ -585,9 +564,6 @@ nodes | PrototypePollutionUtility/tests.js:229:32:229:35 | key2 | | PrototypePollutionUtility/tests.js:229:38:229:42 | value | | PrototypePollutionUtility/tests.js:229:38:229:42 | value | -| PrototypePollutionUtility/tests.js:231:13:231:16 | key1 | -| PrototypePollutionUtility/tests.js:231:13:231:16 | key1 | -| PrototypePollutionUtility/tests.js:231:13:231:16 | key1 | | PrototypePollutionUtility/tests.js:233:5:233:13 | map[key1] | | PrototypePollutionUtility/tests.js:233:5:233:13 | map[key1] | | PrototypePollutionUtility/tests.js:233:5:233:13 | map[key1] | @@ -616,28 +592,6 @@ nodes | PrototypePollutionUtility/tests.js:240:36:240:44 | data[key] | | PrototypePollutionUtility/tests.js:240:41:240:43 | key | | PrototypePollutionUtility/tests.js:240:41:240:43 | key | -| PrototypePollutionUtility/tests.js:252:29:252:31 | src | -| PrototypePollutionUtility/tests.js:252:29:252:31 | src | -| PrototypePollutionUtility/tests.js:255:14:255:16 | key | -| PrototypePollutionUtility/tests.js:255:14:255:16 | key | -| PrototypePollutionUtility/tests.js:255:14:255:16 | key | -| PrototypePollutionUtility/tests.js:257:20:257:22 | key | -| PrototypePollutionUtility/tests.js:257:20:257:22 | key | -| PrototypePollutionUtility/tests.js:257:20:257:22 | key | -| PrototypePollutionUtility/tests.js:257:27:257:59 | mergeWi ... c[key]) | -| PrototypePollutionUtility/tests.js:257:27:257:59 | mergeWi ... c[key]) | -| PrototypePollutionUtility/tests.js:257:27:257:59 | mergeWi ... c[key]) | -| PrototypePollutionUtility/tests.js:257:27:257:59 | mergeWi ... c[key]) | -| PrototypePollutionUtility/tests.js:257:27:257:59 | mergeWi ... c[key]) | -| PrototypePollutionUtility/tests.js:257:51:257:53 | src | -| PrototypePollutionUtility/tests.js:257:51:257:53 | src | -| PrototypePollutionUtility/tests.js:257:51:257:58 | src[key] | -| PrototypePollutionUtility/tests.js:257:51:257:58 | src[key] | -| PrototypePollutionUtility/tests.js:257:51:257:58 | src[key] | -| PrototypePollutionUtility/tests.js:257:51:257:58 | src[key] | -| PrototypePollutionUtility/tests.js:257:51:257:58 | src[key] | -| PrototypePollutionUtility/tests.js:257:55:257:57 | key | -| PrototypePollutionUtility/tests.js:257:55:257:57 | key | | PrototypePollutionUtility/tests.js:263:27:263:29 | dst | | PrototypePollutionUtility/tests.js:263:27:263:29 | dst | | PrototypePollutionUtility/tests.js:265:13:265:26 | key | @@ -707,42 +661,6 @@ nodes | PrototypePollutionUtility/tests.js:280:24:280:31 | src[key] | | PrototypePollutionUtility/tests.js:280:28:280:30 | key | | PrototypePollutionUtility/tests.js:280:28:280:30 | key | -| PrototypePollutionUtility/tests.js:285:28:285:30 | src | -| PrototypePollutionUtility/tests.js:285:28:285:30 | src | -| PrototypePollutionUtility/tests.js:285:33:285:36 | path | -| PrototypePollutionUtility/tests.js:285:33:285:36 | path | -| PrototypePollutionUtility/tests.js:286:14:286:16 | key | -| PrototypePollutionUtility/tests.js:286:14:286:16 | key | -| PrototypePollutionUtility/tests.js:286:14:286:16 | key | -| PrototypePollutionUtility/tests.js:289:40:289:42 | src | -| PrototypePollutionUtility/tests.js:289:40:289:42 | src | -| PrototypePollutionUtility/tests.js:289:40:289:47 | src[key] | -| PrototypePollutionUtility/tests.js:289:40:289:47 | src[key] | -| PrototypePollutionUtility/tests.js:289:40:289:47 | src[key] | -| PrototypePollutionUtility/tests.js:289:40:289:47 | src[key] | -| PrototypePollutionUtility/tests.js:289:40:289:47 | src[key] | -| PrototypePollutionUtility/tests.js:289:44:289:46 | key | -| PrototypePollutionUtility/tests.js:289:44:289:46 | key | -| PrototypePollutionUtility/tests.js:289:50:289:78 | path ? ... y : key | -| PrototypePollutionUtility/tests.js:289:50:289:78 | path ? ... y : key | -| PrototypePollutionUtility/tests.js:289:76:289:78 | key | -| PrototypePollutionUtility/tests.js:289:76:289:78 | key | -| PrototypePollutionUtility/tests.js:292:24:292:27 | path | -| PrototypePollutionUtility/tests.js:292:24:292:27 | path | -| PrototypePollutionUtility/tests.js:292:24:292:27 | path | -| PrototypePollutionUtility/tests.js:293:30:293:32 | key | -| PrototypePollutionUtility/tests.js:293:30:293:32 | key | -| PrototypePollutionUtility/tests.js:293:30:293:32 | key | -| PrototypePollutionUtility/tests.js:293:37:293:39 | src | -| PrototypePollutionUtility/tests.js:293:37:293:39 | src | -| PrototypePollutionUtility/tests.js:293:37:293:44 | src[key] | -| PrototypePollutionUtility/tests.js:293:37:293:44 | src[key] | -| PrototypePollutionUtility/tests.js:293:37:293:44 | src[key] | -| PrototypePollutionUtility/tests.js:293:37:293:44 | src[key] | -| PrototypePollutionUtility/tests.js:293:37:293:44 | src[key] | -| PrototypePollutionUtility/tests.js:293:37:293:44 | src[key] | -| PrototypePollutionUtility/tests.js:293:41:293:43 | key | -| PrototypePollutionUtility/tests.js:293:41:293:43 | key | | PrototypePollutionUtility/tests.js:301:27:301:29 | dst | | PrototypePollutionUtility/tests.js:301:27:301:29 | dst | | PrototypePollutionUtility/tests.js:301:32:301:34 | src | @@ -848,6 +766,193 @@ nodes | PrototypePollutionUtility/tests.js:357:31:357:41 | source[key] | | PrototypePollutionUtility/tests.js:357:31:357:41 | source[key] | | PrototypePollutionUtility/tests.js:357:38:357:40 | key | +| PrototypePollutionUtility/tests.js:365:14:365:16 | key | +| PrototypePollutionUtility/tests.js:365:14:365:16 | key | +| PrototypePollutionUtility/tests.js:365:14:365:16 | key | +| PrototypePollutionUtility/tests.js:367:22:367:24 | key | +| PrototypePollutionUtility/tests.js:367:22:367:24 | key | +| PrototypePollutionUtility/tests.js:367:27:367:34 | obj[key] | +| PrototypePollutionUtility/tests.js:367:27:367:34 | obj[key] | +| PrototypePollutionUtility/tests.js:367:27:367:34 | obj[key] | +| PrototypePollutionUtility/tests.js:367:31:367:33 | key | +| PrototypePollutionUtility/tests.js:367:31:367:33 | key | +| PrototypePollutionUtility/tests.js:372:29:372:31 | dst | +| PrototypePollutionUtility/tests.js:372:29:372:31 | dst | +| PrototypePollutionUtility/tests.js:372:34:372:36 | src | +| PrototypePollutionUtility/tests.js:372:34:372:36 | src | +| PrototypePollutionUtility/tests.js:373:22:373:24 | key | +| PrototypePollutionUtility/tests.js:373:22:373:24 | key | +| PrototypePollutionUtility/tests.js:375:32:375:34 | dst | +| PrototypePollutionUtility/tests.js:375:32:375:34 | dst | +| PrototypePollutionUtility/tests.js:375:32:375:39 | dst[key] | +| PrototypePollutionUtility/tests.js:375:32:375:39 | dst[key] | +| PrototypePollutionUtility/tests.js:375:36:375:38 | key | +| PrototypePollutionUtility/tests.js:375:36:375:38 | key | +| PrototypePollutionUtility/tests.js:375:42:375:44 | src | +| PrototypePollutionUtility/tests.js:375:42:375:44 | src | +| PrototypePollutionUtility/tests.js:375:42:375:49 | src[key] | +| PrototypePollutionUtility/tests.js:375:42:375:49 | src[key] | +| PrototypePollutionUtility/tests.js:375:46:375:48 | key | +| PrototypePollutionUtility/tests.js:375:46:375:48 | key | +| PrototypePollutionUtility/tests.js:377:13:377:15 | dst | +| PrototypePollutionUtility/tests.js:377:13:377:15 | dst | +| PrototypePollutionUtility/tests.js:377:13:377:15 | dst | +| PrototypePollutionUtility/tests.js:377:17:377:19 | key | +| PrototypePollutionUtility/tests.js:377:17:377:19 | key | +| PrototypePollutionUtility/tests.js:377:17:377:19 | key | +| PrototypePollutionUtility/tests.js:377:24:377:26 | src | +| PrototypePollutionUtility/tests.js:377:24:377:26 | src | +| PrototypePollutionUtility/tests.js:377:24:377:31 | src[key] | +| PrototypePollutionUtility/tests.js:377:24:377:31 | src[key] | +| PrototypePollutionUtility/tests.js:377:24:377:31 | src[key] | +| PrototypePollutionUtility/tests.js:377:28:377:30 | key | +| PrototypePollutionUtility/tests.js:377:28:377:30 | key | +| PrototypePollutionUtility/tests.js:382:30:382:32 | dst | +| PrototypePollutionUtility/tests.js:382:30:382:32 | dst | +| PrototypePollutionUtility/tests.js:382:35:382:37 | src | +| PrototypePollutionUtility/tests.js:382:35:382:37 | src | +| PrototypePollutionUtility/tests.js:383:17:383:19 | src | +| PrototypePollutionUtility/tests.js:383:17:383:19 | src | +| PrototypePollutionUtility/tests.js:383:23:383:25 | key | +| PrototypePollutionUtility/tests.js:383:23:383:25 | key | +| PrototypePollutionUtility/tests.js:383:28:383:32 | value | +| PrototypePollutionUtility/tests.js:383:28:383:32 | value | +| PrototypePollutionUtility/tests.js:385:33:385:35 | dst | +| PrototypePollutionUtility/tests.js:385:33:385:35 | dst | +| PrototypePollutionUtility/tests.js:385:33:385:40 | dst[key] | +| PrototypePollutionUtility/tests.js:385:33:385:40 | dst[key] | +| PrototypePollutionUtility/tests.js:385:37:385:39 | key | +| PrototypePollutionUtility/tests.js:385:37:385:39 | key | +| PrototypePollutionUtility/tests.js:385:43:385:47 | value | +| PrototypePollutionUtility/tests.js:385:43:385:47 | value | +| PrototypePollutionUtility/tests.js:387:13:387:15 | dst | +| PrototypePollutionUtility/tests.js:387:13:387:15 | dst | +| PrototypePollutionUtility/tests.js:387:13:387:15 | dst | +| PrototypePollutionUtility/tests.js:387:17:387:19 | key | +| PrototypePollutionUtility/tests.js:387:17:387:19 | key | +| PrototypePollutionUtility/tests.js:387:17:387:19 | key | +| PrototypePollutionUtility/tests.js:387:24:387:28 | value | +| PrototypePollutionUtility/tests.js:387:24:387:28 | value | +| PrototypePollutionUtility/tests.js:387:24:387:28 | value | +| PrototypePollutionUtility/tests.js:396:31:396:33 | dst | +| PrototypePollutionUtility/tests.js:396:31:396:33 | dst | +| PrototypePollutionUtility/tests.js:396:36:396:38 | src | +| PrototypePollutionUtility/tests.js:396:36:396:38 | src | +| PrototypePollutionUtility/tests.js:397:14:397:16 | key | +| PrototypePollutionUtility/tests.js:397:14:397:16 | key | +| PrototypePollutionUtility/tests.js:397:14:397:16 | key | +| PrototypePollutionUtility/tests.js:398:13:398:41 | value | +| PrototypePollutionUtility/tests.js:398:13:398:41 | value | +| PrototypePollutionUtility/tests.js:398:13:398:41 | value | +| PrototypePollutionUtility/tests.js:398:13:398:41 | value | +| PrototypePollutionUtility/tests.js:398:21:398:41 | wrapped ... c, key) | +| PrototypePollutionUtility/tests.js:398:21:398:41 | wrapped ... c, key) | +| PrototypePollutionUtility/tests.js:398:21:398:41 | wrapped ... c, key) | +| PrototypePollutionUtility/tests.js:398:21:398:41 | wrapped ... c, key) | +| PrototypePollutionUtility/tests.js:398:21:398:41 | wrapped ... c, key) | +| PrototypePollutionUtility/tests.js:398:33:398:35 | src | +| PrototypePollutionUtility/tests.js:398:33:398:35 | src | +| PrototypePollutionUtility/tests.js:398:38:398:40 | key | +| PrototypePollutionUtility/tests.js:398:38:398:40 | key | +| PrototypePollutionUtility/tests.js:399:13:399:42 | target | +| PrototypePollutionUtility/tests.js:399:13:399:42 | target | +| PrototypePollutionUtility/tests.js:399:13:399:42 | target | +| PrototypePollutionUtility/tests.js:399:13:399:42 | target | +| PrototypePollutionUtility/tests.js:399:22:399:42 | wrapped ... t, key) | +| PrototypePollutionUtility/tests.js:399:22:399:42 | wrapped ... t, key) | +| PrototypePollutionUtility/tests.js:399:22:399:42 | wrapped ... t, key) | +| PrototypePollutionUtility/tests.js:399:22:399:42 | wrapped ... t, key) | +| PrototypePollutionUtility/tests.js:399:34:399:36 | dst | +| PrototypePollutionUtility/tests.js:399:34:399:36 | dst | +| PrototypePollutionUtility/tests.js:399:39:399:41 | key | +| PrototypePollutionUtility/tests.js:399:39:399:41 | key | +| PrototypePollutionUtility/tests.js:401:34:401:39 | target | +| PrototypePollutionUtility/tests.js:401:34:401:39 | target | +| PrototypePollutionUtility/tests.js:401:34:401:39 | target | +| PrototypePollutionUtility/tests.js:401:34:401:39 | target | +| PrototypePollutionUtility/tests.js:401:42:401:46 | value | +| PrototypePollutionUtility/tests.js:401:42:401:46 | value | +| PrototypePollutionUtility/tests.js:401:42:401:46 | value | +| PrototypePollutionUtility/tests.js:401:42:401:46 | value | +| PrototypePollutionUtility/tests.js:403:13:403:15 | dst | +| PrototypePollutionUtility/tests.js:403:13:403:15 | dst | +| PrototypePollutionUtility/tests.js:403:13:403:15 | dst | +| PrototypePollutionUtility/tests.js:403:17:403:19 | key | +| PrototypePollutionUtility/tests.js:403:17:403:19 | key | +| PrototypePollutionUtility/tests.js:403:17:403:19 | key | +| PrototypePollutionUtility/tests.js:403:24:403:28 | value | +| PrototypePollutionUtility/tests.js:403:24:403:28 | value | +| PrototypePollutionUtility/tests.js:403:24:403:28 | value | +| PrototypePollutionUtility/tests.js:403:24:403:28 | value | +| PrototypePollutionUtility/tests.js:403:24:403:28 | value | +| PrototypePollutionUtility/tests.js:413:34:413:36 | dst | +| PrototypePollutionUtility/tests.js:413:39:413:41 | src | +| PrototypePollutionUtility/tests.js:413:39:413:41 | src | +| PrototypePollutionUtility/tests.js:414:14:414:16 | key | +| PrototypePollutionUtility/tests.js:414:14:414:16 | key | +| PrototypePollutionUtility/tests.js:414:14:414:16 | key | +| PrototypePollutionUtility/tests.js:415:13:415:44 | value | +| PrototypePollutionUtility/tests.js:415:13:415:44 | value | +| PrototypePollutionUtility/tests.js:415:13:415:44 | value | +| PrototypePollutionUtility/tests.js:415:13:415:44 | value | +| PrototypePollutionUtility/tests.js:415:21:415:44 | almostS ... c, key) | +| PrototypePollutionUtility/tests.js:415:21:415:44 | almostS ... c, key) | +| PrototypePollutionUtility/tests.js:415:21:415:44 | almostS ... c, key) | +| PrototypePollutionUtility/tests.js:415:21:415:44 | almostS ... c, key) | +| PrototypePollutionUtility/tests.js:415:21:415:44 | almostS ... c, key) | +| PrototypePollutionUtility/tests.js:415:36:415:38 | src | +| PrototypePollutionUtility/tests.js:415:36:415:38 | src | +| PrototypePollutionUtility/tests.js:415:41:415:43 | key | +| PrototypePollutionUtility/tests.js:416:13:416:45 | target | +| PrototypePollutionUtility/tests.js:416:13:416:45 | target | +| PrototypePollutionUtility/tests.js:416:22:416:45 | almostS ... t, key) | +| PrototypePollutionUtility/tests.js:416:22:416:45 | almostS ... t, key) | +| PrototypePollutionUtility/tests.js:416:37:416:39 | dst | +| PrototypePollutionUtility/tests.js:416:42:416:44 | key | +| PrototypePollutionUtility/tests.js:418:37:418:42 | target | +| PrototypePollutionUtility/tests.js:418:37:418:42 | target | +| PrototypePollutionUtility/tests.js:418:45:418:49 | value | +| PrototypePollutionUtility/tests.js:418:45:418:49 | value | +| PrototypePollutionUtility/tests.js:418:45:418:49 | value | +| PrototypePollutionUtility/tests.js:418:45:418:49 | value | +| PrototypePollutionUtility/tests.js:420:13:420:15 | dst | +| PrototypePollutionUtility/tests.js:420:13:420:15 | dst | +| PrototypePollutionUtility/tests.js:420:17:420:19 | key | +| PrototypePollutionUtility/tests.js:420:17:420:19 | key | +| PrototypePollutionUtility/tests.js:420:17:420:19 | key | +| PrototypePollutionUtility/tests.js:420:24:420:28 | value | +| PrototypePollutionUtility/tests.js:420:24:420:28 | value | +| PrototypePollutionUtility/tests.js:420:24:420:28 | value | +| PrototypePollutionUtility/tests.js:420:24:420:28 | value | +| PrototypePollutionUtility/tests.js:420:24:420:28 | value | +| PrototypePollutionUtility/tests.js:430:33:430:35 | src | +| PrototypePollutionUtility/tests.js:430:33:430:35 | src | +| PrototypePollutionUtility/tests.js:431:14:431:16 | key | +| PrototypePollutionUtility/tests.js:431:14:431:16 | key | +| PrototypePollutionUtility/tests.js:431:14:431:16 | key | +| PrototypePollutionUtility/tests.js:432:13:432:38 | value | +| PrototypePollutionUtility/tests.js:432:13:432:38 | value | +| PrototypePollutionUtility/tests.js:432:13:432:38 | value | +| PrototypePollutionUtility/tests.js:432:13:432:38 | value | +| PrototypePollutionUtility/tests.js:432:21:432:38 | safeRead(src, key) | +| PrototypePollutionUtility/tests.js:432:21:432:38 | safeRead(src, key) | +| PrototypePollutionUtility/tests.js:432:21:432:38 | safeRead(src, key) | +| PrototypePollutionUtility/tests.js:432:21:432:38 | safeRead(src, key) | +| PrototypePollutionUtility/tests.js:432:21:432:38 | safeRead(src, key) | +| PrototypePollutionUtility/tests.js:432:30:432:32 | src | +| PrototypePollutionUtility/tests.js:432:30:432:32 | src | +| PrototypePollutionUtility/tests.js:435:39:435:43 | value | +| PrototypePollutionUtility/tests.js:435:39:435:43 | value | +| PrototypePollutionUtility/tests.js:435:39:435:43 | value | +| PrototypePollutionUtility/tests.js:435:39:435:43 | value | +| PrototypePollutionUtility/tests.js:437:17:437:19 | key | +| PrototypePollutionUtility/tests.js:437:17:437:19 | key | +| PrototypePollutionUtility/tests.js:437:17:437:19 | key | +| PrototypePollutionUtility/tests.js:437:24:437:28 | value | +| PrototypePollutionUtility/tests.js:437:24:437:28 | value | +| PrototypePollutionUtility/tests.js:437:24:437:28 | value | +| PrototypePollutionUtility/tests.js:437:24:437:28 | value | +| PrototypePollutionUtility/tests.js:437:24:437:28 | value | | examples/PrototypePollutionUtility.js:1:16:1:18 | dst | | examples/PrototypePollutionUtility.js:1:16:1:18 | dst | | examples/PrototypePollutionUtility.js:1:21:1:23 | src | @@ -1317,29 +1422,6 @@ edges | PrototypePollutionUtility/tests.js:121:28:121:30 | key | PrototypePollutionUtility/tests.js:121:24:121:31 | src[key] | | PrototypePollutionUtility/tests.js:121:28:121:30 | key | PrototypePollutionUtility/tests.js:121:24:121:31 | src[key] | | PrototypePollutionUtility/tests.js:121:28:121:30 | key | PrototypePollutionUtility/tests.js:121:24:121:31 | src[key] | -| PrototypePollutionUtility/tests.js:127:14:127:16 | key | PrototypePollutionUtility/tests.js:128:13:128:15 | key | -| PrototypePollutionUtility/tests.js:127:14:127:16 | key | PrototypePollutionUtility/tests.js:128:13:128:15 | key | -| PrototypePollutionUtility/tests.js:127:14:127:16 | key | PrototypePollutionUtility/tests.js:128:13:128:15 | key | -| PrototypePollutionUtility/tests.js:127:14:127:16 | key | PrototypePollutionUtility/tests.js:128:13:128:15 | key | -| PrototypePollutionUtility/tests.js:127:14:127:16 | key | PrototypePollutionUtility/tests.js:128:13:128:15 | key | -| PrototypePollutionUtility/tests.js:127:14:127:16 | key | PrototypePollutionUtility/tests.js:128:13:128:15 | key | -| PrototypePollutionUtility/tests.js:127:14:127:16 | key | PrototypePollutionUtility/tests.js:128:13:128:15 | key | -| PrototypePollutionUtility/tests.js:127:14:127:16 | key | PrototypePollutionUtility/tests.js:128:24:128:26 | key | -| PrototypePollutionUtility/tests.js:127:14:127:16 | key | PrototypePollutionUtility/tests.js:128:24:128:26 | key | -| PrototypePollutionUtility/tests.js:127:14:127:16 | key | PrototypePollutionUtility/tests.js:128:24:128:26 | key | -| PrototypePollutionUtility/tests.js:127:14:127:16 | key | PrototypePollutionUtility/tests.js:128:24:128:26 | key | -| PrototypePollutionUtility/tests.js:128:20:128:27 | src[key] | PrototypePollutionUtility/tests.js:128:20:128:27 | src[key] | -| PrototypePollutionUtility/tests.js:128:24:128:26 | key | PrototypePollutionUtility/tests.js:128:20:128:27 | src[key] | -| PrototypePollutionUtility/tests.js:128:24:128:26 | key | PrototypePollutionUtility/tests.js:128:20:128:27 | src[key] | -| PrototypePollutionUtility/tests.js:128:24:128:26 | key | PrototypePollutionUtility/tests.js:128:20:128:27 | src[key] | -| PrototypePollutionUtility/tests.js:128:24:128:26 | key | PrototypePollutionUtility/tests.js:128:20:128:27 | src[key] | -| PrototypePollutionUtility/tests.js:143:14:143:16 | key | PrototypePollutionUtility/tests.js:144:16:144:18 | key | -| PrototypePollutionUtility/tests.js:143:14:143:16 | key | PrototypePollutionUtility/tests.js:144:16:144:18 | key | -| PrototypePollutionUtility/tests.js:143:14:143:16 | key | PrototypePollutionUtility/tests.js:144:16:144:18 | key | -| PrototypePollutionUtility/tests.js:143:14:143:16 | key | PrototypePollutionUtility/tests.js:144:16:144:18 | key | -| PrototypePollutionUtility/tests.js:143:14:143:16 | key | PrototypePollutionUtility/tests.js:144:16:144:18 | key | -| PrototypePollutionUtility/tests.js:143:14:143:16 | key | PrototypePollutionUtility/tests.js:144:16:144:18 | key | -| PrototypePollutionUtility/tests.js:143:14:143:16 | key | PrototypePollutionUtility/tests.js:144:16:144:18 | key | | PrototypePollutionUtility/tests.js:149:31:149:33 | dst | PrototypePollutionUtility/tests.js:152:22:152:24 | dst | | PrototypePollutionUtility/tests.js:149:31:149:33 | dst | PrototypePollutionUtility/tests.js:152:22:152:24 | dst | | PrototypePollutionUtility/tests.js:149:31:149:33 | dst | PrototypePollutionUtility/tests.js:152:22:152:24 | dst | @@ -1615,10 +1697,6 @@ edges | PrototypePollutionUtility/tests.js:208:32:208:38 | keys[i] | PrototypePollutionUtility/tests.js:208:28:208:39 | src[keys[i]] | | PrototypePollutionUtility/tests.js:208:32:208:38 | keys[i] | PrototypePollutionUtility/tests.js:208:28:208:39 | src[keys[i]] | | PrototypePollutionUtility/tests.js:208:32:208:38 | keys[i] | PrototypePollutionUtility/tests.js:208:28:208:39 | src[keys[i]] | -| PrototypePollutionUtility/tests.js:213:23:213:26 | key1 | PrototypePollutionUtility/tests.js:215:13:215:16 | key1 | -| PrototypePollutionUtility/tests.js:213:23:213:26 | key1 | PrototypePollutionUtility/tests.js:215:13:215:16 | key1 | -| PrototypePollutionUtility/tests.js:213:23:213:26 | key1 | PrototypePollutionUtility/tests.js:215:13:215:16 | key1 | -| PrototypePollutionUtility/tests.js:213:23:213:26 | key1 | PrototypePollutionUtility/tests.js:215:13:215:16 | key1 | | PrototypePollutionUtility/tests.js:213:23:213:26 | key1 | PrototypePollutionUtility/tests.js:217:9:217:12 | key1 | | PrototypePollutionUtility/tests.js:213:23:213:26 | key1 | PrototypePollutionUtility/tests.js:217:9:217:12 | key1 | | PrototypePollutionUtility/tests.js:213:29:213:32 | key2 | PrototypePollutionUtility/tests.js:217:15:217:18 | key2 | @@ -1665,10 +1743,6 @@ edges | PrototypePollutionUtility/tests.js:225:33:225:41 | data[key] | PrototypePollutionUtility/tests.js:213:35:213:39 | value | | PrototypePollutionUtility/tests.js:225:38:225:40 | key | PrototypePollutionUtility/tests.js:225:33:225:41 | data[key] | | PrototypePollutionUtility/tests.js:225:38:225:40 | key | PrototypePollutionUtility/tests.js:225:33:225:41 | data[key] | -| PrototypePollutionUtility/tests.js:229:26:229:29 | key1 | PrototypePollutionUtility/tests.js:231:13:231:16 | key1 | -| PrototypePollutionUtility/tests.js:229:26:229:29 | key1 | PrototypePollutionUtility/tests.js:231:13:231:16 | key1 | -| PrototypePollutionUtility/tests.js:229:26:229:29 | key1 | PrototypePollutionUtility/tests.js:231:13:231:16 | key1 | -| PrototypePollutionUtility/tests.js:229:26:229:29 | key1 | PrototypePollutionUtility/tests.js:231:13:231:16 | key1 | | PrototypePollutionUtility/tests.js:229:26:229:29 | key1 | PrototypePollutionUtility/tests.js:233:9:233:12 | key1 | | PrototypePollutionUtility/tests.js:229:26:229:29 | key1 | PrototypePollutionUtility/tests.js:233:9:233:12 | key1 | | PrototypePollutionUtility/tests.js:229:32:229:35 | key2 | PrototypePollutionUtility/tests.js:233:15:233:18 | key2 | @@ -1715,40 +1789,6 @@ edges | PrototypePollutionUtility/tests.js:240:36:240:44 | data[key] | PrototypePollutionUtility/tests.js:229:38:229:42 | value | | PrototypePollutionUtility/tests.js:240:41:240:43 | key | PrototypePollutionUtility/tests.js:240:36:240:44 | data[key] | | PrototypePollutionUtility/tests.js:240:41:240:43 | key | PrototypePollutionUtility/tests.js:240:36:240:44 | data[key] | -| PrototypePollutionUtility/tests.js:252:29:252:31 | src | PrototypePollutionUtility/tests.js:257:51:257:53 | src | -| PrototypePollutionUtility/tests.js:252:29:252:31 | src | PrototypePollutionUtility/tests.js:257:51:257:53 | src | -| PrototypePollutionUtility/tests.js:255:14:255:16 | key | PrototypePollutionUtility/tests.js:257:20:257:22 | key | -| PrototypePollutionUtility/tests.js:255:14:255:16 | key | PrototypePollutionUtility/tests.js:257:20:257:22 | key | -| PrototypePollutionUtility/tests.js:255:14:255:16 | key | PrototypePollutionUtility/tests.js:257:20:257:22 | key | -| PrototypePollutionUtility/tests.js:255:14:255:16 | key | PrototypePollutionUtility/tests.js:257:20:257:22 | key | -| PrototypePollutionUtility/tests.js:255:14:255:16 | key | PrototypePollutionUtility/tests.js:257:20:257:22 | key | -| PrototypePollutionUtility/tests.js:255:14:255:16 | key | PrototypePollutionUtility/tests.js:257:20:257:22 | key | -| PrototypePollutionUtility/tests.js:255:14:255:16 | key | PrototypePollutionUtility/tests.js:257:20:257:22 | key | -| PrototypePollutionUtility/tests.js:255:14:255:16 | key | PrototypePollutionUtility/tests.js:257:55:257:57 | key | -| PrototypePollutionUtility/tests.js:255:14:255:16 | key | PrototypePollutionUtility/tests.js:257:55:257:57 | key | -| PrototypePollutionUtility/tests.js:255:14:255:16 | key | PrototypePollutionUtility/tests.js:257:55:257:57 | key | -| PrototypePollutionUtility/tests.js:255:14:255:16 | key | PrototypePollutionUtility/tests.js:257:55:257:57 | key | -| PrototypePollutionUtility/tests.js:257:51:257:53 | src | PrototypePollutionUtility/tests.js:257:51:257:58 | src[key] | -| PrototypePollutionUtility/tests.js:257:51:257:53 | src | PrototypePollutionUtility/tests.js:257:51:257:58 | src[key] | -| PrototypePollutionUtility/tests.js:257:51:257:58 | src[key] | PrototypePollutionUtility/tests.js:252:29:252:31 | src | -| PrototypePollutionUtility/tests.js:257:51:257:58 | src[key] | PrototypePollutionUtility/tests.js:252:29:252:31 | src | -| PrototypePollutionUtility/tests.js:257:51:257:58 | src[key] | PrototypePollutionUtility/tests.js:252:29:252:31 | src | -| PrototypePollutionUtility/tests.js:257:51:257:58 | src[key] | PrototypePollutionUtility/tests.js:252:29:252:31 | src | -| PrototypePollutionUtility/tests.js:257:51:257:58 | src[key] | PrototypePollutionUtility/tests.js:252:29:252:31 | src | -| PrototypePollutionUtility/tests.js:257:51:257:58 | src[key] | PrototypePollutionUtility/tests.js:252:29:252:31 | src | -| PrototypePollutionUtility/tests.js:257:51:257:58 | src[key] | PrototypePollutionUtility/tests.js:257:27:257:59 | mergeWi ... c[key]) | -| PrototypePollutionUtility/tests.js:257:51:257:58 | src[key] | PrototypePollutionUtility/tests.js:257:27:257:59 | mergeWi ... c[key]) | -| PrototypePollutionUtility/tests.js:257:51:257:58 | src[key] | PrototypePollutionUtility/tests.js:257:27:257:59 | mergeWi ... c[key]) | -| PrototypePollutionUtility/tests.js:257:51:257:58 | src[key] | PrototypePollutionUtility/tests.js:257:27:257:59 | mergeWi ... c[key]) | -| PrototypePollutionUtility/tests.js:257:51:257:58 | src[key] | PrototypePollutionUtility/tests.js:257:27:257:59 | mergeWi ... c[key]) | -| PrototypePollutionUtility/tests.js:257:51:257:58 | src[key] | PrototypePollutionUtility/tests.js:257:27:257:59 | mergeWi ... c[key]) | -| PrototypePollutionUtility/tests.js:257:51:257:58 | src[key] | PrototypePollutionUtility/tests.js:257:27:257:59 | mergeWi ... c[key]) | -| PrototypePollutionUtility/tests.js:257:51:257:58 | src[key] | PrototypePollutionUtility/tests.js:257:27:257:59 | mergeWi ... c[key]) | -| PrototypePollutionUtility/tests.js:257:51:257:58 | src[key] | PrototypePollutionUtility/tests.js:257:27:257:59 | mergeWi ... c[key]) | -| PrototypePollutionUtility/tests.js:257:51:257:58 | src[key] | PrototypePollutionUtility/tests.js:257:27:257:59 | mergeWi ... c[key]) | -| PrototypePollutionUtility/tests.js:257:51:257:58 | src[key] | PrototypePollutionUtility/tests.js:257:27:257:59 | mergeWi ... c[key]) | -| PrototypePollutionUtility/tests.js:257:55:257:57 | key | PrototypePollutionUtility/tests.js:257:51:257:58 | src[key] | -| PrototypePollutionUtility/tests.js:257:55:257:57 | key | PrototypePollutionUtility/tests.js:257:51:257:58 | src[key] | | PrototypePollutionUtility/tests.js:263:27:263:29 | dst | PrototypePollutionUtility/tests.js:268:30:268:32 | dst | | PrototypePollutionUtility/tests.js:263:27:263:29 | dst | PrototypePollutionUtility/tests.js:268:30:268:32 | dst | | PrototypePollutionUtility/tests.js:263:27:263:29 | dst | PrototypePollutionUtility/tests.js:270:13:270:15 | dst | @@ -1837,56 +1877,6 @@ edges | PrototypePollutionUtility/tests.js:280:28:280:30 | key | PrototypePollutionUtility/tests.js:280:24:280:31 | src[key] | | PrototypePollutionUtility/tests.js:280:28:280:30 | key | PrototypePollutionUtility/tests.js:280:24:280:31 | src[key] | | PrototypePollutionUtility/tests.js:280:28:280:30 | key | PrototypePollutionUtility/tests.js:280:24:280:31 | src[key] | -| PrototypePollutionUtility/tests.js:285:28:285:30 | src | PrototypePollutionUtility/tests.js:289:40:289:42 | src | -| PrototypePollutionUtility/tests.js:285:28:285:30 | src | PrototypePollutionUtility/tests.js:289:40:289:42 | src | -| PrototypePollutionUtility/tests.js:285:28:285:30 | src | PrototypePollutionUtility/tests.js:293:37:293:39 | src | -| PrototypePollutionUtility/tests.js:285:28:285:30 | src | PrototypePollutionUtility/tests.js:293:37:293:39 | src | -| PrototypePollutionUtility/tests.js:285:33:285:36 | path | PrototypePollutionUtility/tests.js:292:24:292:27 | path | -| PrototypePollutionUtility/tests.js:285:33:285:36 | path | PrototypePollutionUtility/tests.js:292:24:292:27 | path | -| PrototypePollutionUtility/tests.js:285:33:285:36 | path | PrototypePollutionUtility/tests.js:292:24:292:27 | path | -| PrototypePollutionUtility/tests.js:285:33:285:36 | path | PrototypePollutionUtility/tests.js:292:24:292:27 | path | -| PrototypePollutionUtility/tests.js:286:14:286:16 | key | PrototypePollutionUtility/tests.js:289:44:289:46 | key | -| PrototypePollutionUtility/tests.js:286:14:286:16 | key | PrototypePollutionUtility/tests.js:289:44:289:46 | key | -| PrototypePollutionUtility/tests.js:286:14:286:16 | key | PrototypePollutionUtility/tests.js:289:44:289:46 | key | -| PrototypePollutionUtility/tests.js:286:14:286:16 | key | PrototypePollutionUtility/tests.js:289:44:289:46 | key | -| PrototypePollutionUtility/tests.js:286:14:286:16 | key | PrototypePollutionUtility/tests.js:289:76:289:78 | key | -| PrototypePollutionUtility/tests.js:286:14:286:16 | key | PrototypePollutionUtility/tests.js:289:76:289:78 | key | -| PrototypePollutionUtility/tests.js:286:14:286:16 | key | PrototypePollutionUtility/tests.js:289:76:289:78 | key | -| PrototypePollutionUtility/tests.js:286:14:286:16 | key | PrototypePollutionUtility/tests.js:289:76:289:78 | key | -| PrototypePollutionUtility/tests.js:286:14:286:16 | key | PrototypePollutionUtility/tests.js:293:30:293:32 | key | -| PrototypePollutionUtility/tests.js:286:14:286:16 | key | PrototypePollutionUtility/tests.js:293:30:293:32 | key | -| PrototypePollutionUtility/tests.js:286:14:286:16 | key | PrototypePollutionUtility/tests.js:293:30:293:32 | key | -| PrototypePollutionUtility/tests.js:286:14:286:16 | key | PrototypePollutionUtility/tests.js:293:30:293:32 | key | -| PrototypePollutionUtility/tests.js:286:14:286:16 | key | PrototypePollutionUtility/tests.js:293:30:293:32 | key | -| PrototypePollutionUtility/tests.js:286:14:286:16 | key | PrototypePollutionUtility/tests.js:293:30:293:32 | key | -| PrototypePollutionUtility/tests.js:286:14:286:16 | key | PrototypePollutionUtility/tests.js:293:30:293:32 | key | -| PrototypePollutionUtility/tests.js:286:14:286:16 | key | PrototypePollutionUtility/tests.js:293:41:293:43 | key | -| PrototypePollutionUtility/tests.js:286:14:286:16 | key | PrototypePollutionUtility/tests.js:293:41:293:43 | key | -| PrototypePollutionUtility/tests.js:286:14:286:16 | key | PrototypePollutionUtility/tests.js:293:41:293:43 | key | -| PrototypePollutionUtility/tests.js:286:14:286:16 | key | PrototypePollutionUtility/tests.js:293:41:293:43 | key | -| PrototypePollutionUtility/tests.js:289:40:289:42 | src | PrototypePollutionUtility/tests.js:289:40:289:47 | src[key] | -| PrototypePollutionUtility/tests.js:289:40:289:42 | src | PrototypePollutionUtility/tests.js:289:40:289:47 | src[key] | -| PrototypePollutionUtility/tests.js:289:40:289:47 | src[key] | PrototypePollutionUtility/tests.js:285:28:285:30 | src | -| PrototypePollutionUtility/tests.js:289:40:289:47 | src[key] | PrototypePollutionUtility/tests.js:285:28:285:30 | src | -| PrototypePollutionUtility/tests.js:289:40:289:47 | src[key] | PrototypePollutionUtility/tests.js:285:28:285:30 | src | -| PrototypePollutionUtility/tests.js:289:40:289:47 | src[key] | PrototypePollutionUtility/tests.js:285:28:285:30 | src | -| PrototypePollutionUtility/tests.js:289:40:289:47 | src[key] | PrototypePollutionUtility/tests.js:285:28:285:30 | src | -| PrototypePollutionUtility/tests.js:289:40:289:47 | src[key] | PrototypePollutionUtility/tests.js:285:28:285:30 | src | -| PrototypePollutionUtility/tests.js:289:44:289:46 | key | PrototypePollutionUtility/tests.js:289:40:289:47 | src[key] | -| PrototypePollutionUtility/tests.js:289:44:289:46 | key | PrototypePollutionUtility/tests.js:289:40:289:47 | src[key] | -| PrototypePollutionUtility/tests.js:289:50:289:78 | path ? ... y : key | PrototypePollutionUtility/tests.js:285:33:285:36 | path | -| PrototypePollutionUtility/tests.js:289:50:289:78 | path ? ... y : key | PrototypePollutionUtility/tests.js:285:33:285:36 | path | -| PrototypePollutionUtility/tests.js:289:76:289:78 | key | PrototypePollutionUtility/tests.js:289:50:289:78 | path ? ... y : key | -| PrototypePollutionUtility/tests.js:289:76:289:78 | key | PrototypePollutionUtility/tests.js:289:50:289:78 | path ? ... y : key | -| PrototypePollutionUtility/tests.js:293:37:293:39 | src | PrototypePollutionUtility/tests.js:293:37:293:44 | src[key] | -| PrototypePollutionUtility/tests.js:293:37:293:39 | src | PrototypePollutionUtility/tests.js:293:37:293:44 | src[key] | -| PrototypePollutionUtility/tests.js:293:37:293:39 | src | PrototypePollutionUtility/tests.js:293:37:293:44 | src[key] | -| PrototypePollutionUtility/tests.js:293:37:293:39 | src | PrototypePollutionUtility/tests.js:293:37:293:44 | src[key] | -| PrototypePollutionUtility/tests.js:293:37:293:44 | src[key] | PrototypePollutionUtility/tests.js:293:37:293:44 | src[key] | -| PrototypePollutionUtility/tests.js:293:41:293:43 | key | PrototypePollutionUtility/tests.js:293:37:293:44 | src[key] | -| PrototypePollutionUtility/tests.js:293:41:293:43 | key | PrototypePollutionUtility/tests.js:293:37:293:44 | src[key] | -| PrototypePollutionUtility/tests.js:293:41:293:43 | key | PrototypePollutionUtility/tests.js:293:37:293:44 | src[key] | -| PrototypePollutionUtility/tests.js:293:41:293:43 | key | PrototypePollutionUtility/tests.js:293:37:293:44 | src[key] | | PrototypePollutionUtility/tests.js:301:27:301:29 | dst | PrototypePollutionUtility/tests.js:306:34:306:36 | dst | | PrototypePollutionUtility/tests.js:301:27:301:29 | dst | PrototypePollutionUtility/tests.js:306:34:306:36 | dst | | PrototypePollutionUtility/tests.js:301:27:301:29 | dst | PrototypePollutionUtility/tests.js:308:17:308:19 | dst | @@ -2017,6 +2007,241 @@ edges | PrototypePollutionUtility/tests.js:357:31:357:41 | source[key] | PrototypePollutionUtility/tests.js:357:31:357:41 | source[key] | | PrototypePollutionUtility/tests.js:357:38:357:40 | key | PrototypePollutionUtility/tests.js:357:31:357:41 | source[key] | | PrototypePollutionUtility/tests.js:357:38:357:40 | key | PrototypePollutionUtility/tests.js:357:31:357:41 | source[key] | +| PrototypePollutionUtility/tests.js:365:14:365:16 | key | PrototypePollutionUtility/tests.js:367:22:367:24 | key | +| PrototypePollutionUtility/tests.js:365:14:365:16 | key | PrototypePollutionUtility/tests.js:367:22:367:24 | key | +| PrototypePollutionUtility/tests.js:365:14:365:16 | key | PrototypePollutionUtility/tests.js:367:22:367:24 | key | +| PrototypePollutionUtility/tests.js:365:14:365:16 | key | PrototypePollutionUtility/tests.js:367:22:367:24 | key | +| PrototypePollutionUtility/tests.js:365:14:365:16 | key | PrototypePollutionUtility/tests.js:367:31:367:33 | key | +| PrototypePollutionUtility/tests.js:365:14:365:16 | key | PrototypePollutionUtility/tests.js:367:31:367:33 | key | +| PrototypePollutionUtility/tests.js:365:14:365:16 | key | PrototypePollutionUtility/tests.js:367:31:367:33 | key | +| PrototypePollutionUtility/tests.js:365:14:365:16 | key | PrototypePollutionUtility/tests.js:367:31:367:33 | key | +| PrototypePollutionUtility/tests.js:367:22:367:24 | key | PrototypePollutionUtility/tests.js:373:22:373:24 | key | +| PrototypePollutionUtility/tests.js:367:22:367:24 | key | PrototypePollutionUtility/tests.js:373:22:373:24 | key | +| PrototypePollutionUtility/tests.js:367:22:367:24 | key | PrototypePollutionUtility/tests.js:383:23:383:25 | key | +| PrototypePollutionUtility/tests.js:367:22:367:24 | key | PrototypePollutionUtility/tests.js:383:23:383:25 | key | +| PrototypePollutionUtility/tests.js:367:27:367:34 | obj[key] | PrototypePollutionUtility/tests.js:383:28:383:32 | value | +| PrototypePollutionUtility/tests.js:367:27:367:34 | obj[key] | PrototypePollutionUtility/tests.js:383:28:383:32 | value | +| PrototypePollutionUtility/tests.js:367:27:367:34 | obj[key] | PrototypePollutionUtility/tests.js:383:28:383:32 | value | +| PrototypePollutionUtility/tests.js:367:27:367:34 | obj[key] | PrototypePollutionUtility/tests.js:383:28:383:32 | value | +| PrototypePollutionUtility/tests.js:367:31:367:33 | key | PrototypePollutionUtility/tests.js:367:27:367:34 | obj[key] | +| PrototypePollutionUtility/tests.js:367:31:367:33 | key | PrototypePollutionUtility/tests.js:367:27:367:34 | obj[key] | +| PrototypePollutionUtility/tests.js:372:29:372:31 | dst | PrototypePollutionUtility/tests.js:375:32:375:34 | dst | +| PrototypePollutionUtility/tests.js:372:29:372:31 | dst | PrototypePollutionUtility/tests.js:375:32:375:34 | dst | +| PrototypePollutionUtility/tests.js:372:29:372:31 | dst | PrototypePollutionUtility/tests.js:377:13:377:15 | dst | +| PrototypePollutionUtility/tests.js:372:29:372:31 | dst | PrototypePollutionUtility/tests.js:377:13:377:15 | dst | +| PrototypePollutionUtility/tests.js:372:29:372:31 | dst | PrototypePollutionUtility/tests.js:377:13:377:15 | dst | +| PrototypePollutionUtility/tests.js:372:29:372:31 | dst | PrototypePollutionUtility/tests.js:377:13:377:15 | dst | +| PrototypePollutionUtility/tests.js:372:34:372:36 | src | PrototypePollutionUtility/tests.js:375:42:375:44 | src | +| PrototypePollutionUtility/tests.js:372:34:372:36 | src | PrototypePollutionUtility/tests.js:375:42:375:44 | src | +| PrototypePollutionUtility/tests.js:372:34:372:36 | src | PrototypePollutionUtility/tests.js:377:24:377:26 | src | +| PrototypePollutionUtility/tests.js:372:34:372:36 | src | PrototypePollutionUtility/tests.js:377:24:377:26 | src | +| PrototypePollutionUtility/tests.js:373:22:373:24 | key | PrototypePollutionUtility/tests.js:375:36:375:38 | key | +| PrototypePollutionUtility/tests.js:373:22:373:24 | key | PrototypePollutionUtility/tests.js:375:36:375:38 | key | +| PrototypePollutionUtility/tests.js:373:22:373:24 | key | PrototypePollutionUtility/tests.js:375:46:375:48 | key | +| PrototypePollutionUtility/tests.js:373:22:373:24 | key | PrototypePollutionUtility/tests.js:375:46:375:48 | key | +| PrototypePollutionUtility/tests.js:373:22:373:24 | key | PrototypePollutionUtility/tests.js:377:17:377:19 | key | +| PrototypePollutionUtility/tests.js:373:22:373:24 | key | PrototypePollutionUtility/tests.js:377:17:377:19 | key | +| PrototypePollutionUtility/tests.js:373:22:373:24 | key | PrototypePollutionUtility/tests.js:377:17:377:19 | key | +| PrototypePollutionUtility/tests.js:373:22:373:24 | key | PrototypePollutionUtility/tests.js:377:17:377:19 | key | +| PrototypePollutionUtility/tests.js:373:22:373:24 | key | PrototypePollutionUtility/tests.js:377:28:377:30 | key | +| PrototypePollutionUtility/tests.js:373:22:373:24 | key | PrototypePollutionUtility/tests.js:377:28:377:30 | key | +| PrototypePollutionUtility/tests.js:375:32:375:34 | dst | PrototypePollutionUtility/tests.js:375:32:375:39 | dst[key] | +| PrototypePollutionUtility/tests.js:375:32:375:34 | dst | PrototypePollutionUtility/tests.js:375:32:375:39 | dst[key] | +| PrototypePollutionUtility/tests.js:375:32:375:39 | dst[key] | PrototypePollutionUtility/tests.js:372:29:372:31 | dst | +| PrototypePollutionUtility/tests.js:375:32:375:39 | dst[key] | PrototypePollutionUtility/tests.js:372:29:372:31 | dst | +| PrototypePollutionUtility/tests.js:375:36:375:38 | key | PrototypePollutionUtility/tests.js:375:32:375:39 | dst[key] | +| PrototypePollutionUtility/tests.js:375:36:375:38 | key | PrototypePollutionUtility/tests.js:375:32:375:39 | dst[key] | +| PrototypePollutionUtility/tests.js:375:42:375:44 | src | PrototypePollutionUtility/tests.js:375:42:375:49 | src[key] | +| PrototypePollutionUtility/tests.js:375:42:375:44 | src | PrototypePollutionUtility/tests.js:375:42:375:49 | src[key] | +| PrototypePollutionUtility/tests.js:375:42:375:49 | src[key] | PrototypePollutionUtility/tests.js:372:34:372:36 | src | +| PrototypePollutionUtility/tests.js:375:42:375:49 | src[key] | PrototypePollutionUtility/tests.js:372:34:372:36 | src | +| PrototypePollutionUtility/tests.js:375:46:375:48 | key | PrototypePollutionUtility/tests.js:375:42:375:49 | src[key] | +| PrototypePollutionUtility/tests.js:375:46:375:48 | key | PrototypePollutionUtility/tests.js:375:42:375:49 | src[key] | +| PrototypePollutionUtility/tests.js:377:24:377:26 | src | PrototypePollutionUtility/tests.js:377:24:377:31 | src[key] | +| PrototypePollutionUtility/tests.js:377:24:377:26 | src | PrototypePollutionUtility/tests.js:377:24:377:31 | src[key] | +| PrototypePollutionUtility/tests.js:377:24:377:26 | src | PrototypePollutionUtility/tests.js:377:24:377:31 | src[key] | +| PrototypePollutionUtility/tests.js:377:24:377:26 | src | PrototypePollutionUtility/tests.js:377:24:377:31 | src[key] | +| PrototypePollutionUtility/tests.js:377:28:377:30 | key | PrototypePollutionUtility/tests.js:377:24:377:31 | src[key] | +| PrototypePollutionUtility/tests.js:377:28:377:30 | key | PrototypePollutionUtility/tests.js:377:24:377:31 | src[key] | +| PrototypePollutionUtility/tests.js:377:28:377:30 | key | PrototypePollutionUtility/tests.js:377:24:377:31 | src[key] | +| PrototypePollutionUtility/tests.js:377:28:377:30 | key | PrototypePollutionUtility/tests.js:377:24:377:31 | src[key] | +| PrototypePollutionUtility/tests.js:382:30:382:32 | dst | PrototypePollutionUtility/tests.js:385:33:385:35 | dst | +| PrototypePollutionUtility/tests.js:382:30:382:32 | dst | PrototypePollutionUtility/tests.js:385:33:385:35 | dst | +| PrototypePollutionUtility/tests.js:382:30:382:32 | dst | PrototypePollutionUtility/tests.js:387:13:387:15 | dst | +| PrototypePollutionUtility/tests.js:382:30:382:32 | dst | PrototypePollutionUtility/tests.js:387:13:387:15 | dst | +| PrototypePollutionUtility/tests.js:382:30:382:32 | dst | PrototypePollutionUtility/tests.js:387:13:387:15 | dst | +| PrototypePollutionUtility/tests.js:382:30:382:32 | dst | PrototypePollutionUtility/tests.js:387:13:387:15 | dst | +| PrototypePollutionUtility/tests.js:382:35:382:37 | src | PrototypePollutionUtility/tests.js:383:17:383:19 | src | +| PrototypePollutionUtility/tests.js:382:35:382:37 | src | PrototypePollutionUtility/tests.js:383:17:383:19 | src | +| PrototypePollutionUtility/tests.js:383:17:383:19 | src | PrototypePollutionUtility/tests.js:383:28:383:32 | value | +| PrototypePollutionUtility/tests.js:383:17:383:19 | src | PrototypePollutionUtility/tests.js:383:28:383:32 | value | +| PrototypePollutionUtility/tests.js:383:23:383:25 | key | PrototypePollutionUtility/tests.js:385:37:385:39 | key | +| PrototypePollutionUtility/tests.js:383:23:383:25 | key | PrototypePollutionUtility/tests.js:385:37:385:39 | key | +| PrototypePollutionUtility/tests.js:383:23:383:25 | key | PrototypePollutionUtility/tests.js:387:17:387:19 | key | +| PrototypePollutionUtility/tests.js:383:23:383:25 | key | PrototypePollutionUtility/tests.js:387:17:387:19 | key | +| PrototypePollutionUtility/tests.js:383:23:383:25 | key | PrototypePollutionUtility/tests.js:387:17:387:19 | key | +| PrototypePollutionUtility/tests.js:383:23:383:25 | key | PrototypePollutionUtility/tests.js:387:17:387:19 | key | +| PrototypePollutionUtility/tests.js:383:28:383:32 | value | PrototypePollutionUtility/tests.js:385:43:385:47 | value | +| PrototypePollutionUtility/tests.js:383:28:383:32 | value | PrototypePollutionUtility/tests.js:385:43:385:47 | value | +| PrototypePollutionUtility/tests.js:383:28:383:32 | value | PrototypePollutionUtility/tests.js:387:24:387:28 | value | +| PrototypePollutionUtility/tests.js:383:28:383:32 | value | PrototypePollutionUtility/tests.js:387:24:387:28 | value | +| PrototypePollutionUtility/tests.js:383:28:383:32 | value | PrototypePollutionUtility/tests.js:387:24:387:28 | value | +| PrototypePollutionUtility/tests.js:383:28:383:32 | value | PrototypePollutionUtility/tests.js:387:24:387:28 | value | +| PrototypePollutionUtility/tests.js:385:33:385:35 | dst | PrototypePollutionUtility/tests.js:385:33:385:40 | dst[key] | +| PrototypePollutionUtility/tests.js:385:33:385:35 | dst | PrototypePollutionUtility/tests.js:385:33:385:40 | dst[key] | +| PrototypePollutionUtility/tests.js:385:33:385:40 | dst[key] | PrototypePollutionUtility/tests.js:382:30:382:32 | dst | +| PrototypePollutionUtility/tests.js:385:33:385:40 | dst[key] | PrototypePollutionUtility/tests.js:382:30:382:32 | dst | +| PrototypePollutionUtility/tests.js:385:37:385:39 | key | PrototypePollutionUtility/tests.js:385:33:385:40 | dst[key] | +| PrototypePollutionUtility/tests.js:385:37:385:39 | key | PrototypePollutionUtility/tests.js:385:33:385:40 | dst[key] | +| PrototypePollutionUtility/tests.js:385:43:385:47 | value | PrototypePollutionUtility/tests.js:382:35:382:37 | src | +| PrototypePollutionUtility/tests.js:385:43:385:47 | value | PrototypePollutionUtility/tests.js:382:35:382:37 | src | +| PrototypePollutionUtility/tests.js:396:31:396:33 | dst | PrototypePollutionUtility/tests.js:399:34:399:36 | dst | +| PrototypePollutionUtility/tests.js:396:31:396:33 | dst | PrototypePollutionUtility/tests.js:399:34:399:36 | dst | +| PrototypePollutionUtility/tests.js:396:31:396:33 | dst | PrototypePollutionUtility/tests.js:403:13:403:15 | dst | +| PrototypePollutionUtility/tests.js:396:31:396:33 | dst | PrototypePollutionUtility/tests.js:403:13:403:15 | dst | +| PrototypePollutionUtility/tests.js:396:31:396:33 | dst | PrototypePollutionUtility/tests.js:403:13:403:15 | dst | +| PrototypePollutionUtility/tests.js:396:31:396:33 | dst | PrototypePollutionUtility/tests.js:403:13:403:15 | dst | +| PrototypePollutionUtility/tests.js:396:36:396:38 | src | PrototypePollutionUtility/tests.js:398:33:398:35 | src | +| PrototypePollutionUtility/tests.js:396:36:396:38 | src | PrototypePollutionUtility/tests.js:398:33:398:35 | src | +| PrototypePollutionUtility/tests.js:397:14:397:16 | key | PrototypePollutionUtility/tests.js:398:38:398:40 | key | +| PrototypePollutionUtility/tests.js:397:14:397:16 | key | PrototypePollutionUtility/tests.js:398:38:398:40 | key | +| PrototypePollutionUtility/tests.js:397:14:397:16 | key | PrototypePollutionUtility/tests.js:398:38:398:40 | key | +| PrototypePollutionUtility/tests.js:397:14:397:16 | key | PrototypePollutionUtility/tests.js:398:38:398:40 | key | +| PrototypePollutionUtility/tests.js:397:14:397:16 | key | PrototypePollutionUtility/tests.js:399:39:399:41 | key | +| PrototypePollutionUtility/tests.js:397:14:397:16 | key | PrototypePollutionUtility/tests.js:399:39:399:41 | key | +| PrototypePollutionUtility/tests.js:397:14:397:16 | key | PrototypePollutionUtility/tests.js:399:39:399:41 | key | +| PrototypePollutionUtility/tests.js:397:14:397:16 | key | PrototypePollutionUtility/tests.js:399:39:399:41 | key | +| PrototypePollutionUtility/tests.js:397:14:397:16 | key | PrototypePollutionUtility/tests.js:403:17:403:19 | key | +| PrototypePollutionUtility/tests.js:397:14:397:16 | key | PrototypePollutionUtility/tests.js:403:17:403:19 | key | +| PrototypePollutionUtility/tests.js:397:14:397:16 | key | PrototypePollutionUtility/tests.js:403:17:403:19 | key | +| PrototypePollutionUtility/tests.js:397:14:397:16 | key | PrototypePollutionUtility/tests.js:403:17:403:19 | key | +| PrototypePollutionUtility/tests.js:397:14:397:16 | key | PrototypePollutionUtility/tests.js:403:17:403:19 | key | +| PrototypePollutionUtility/tests.js:397:14:397:16 | key | PrototypePollutionUtility/tests.js:403:17:403:19 | key | +| PrototypePollutionUtility/tests.js:397:14:397:16 | key | PrototypePollutionUtility/tests.js:403:17:403:19 | key | +| PrototypePollutionUtility/tests.js:398:13:398:41 | value | PrototypePollutionUtility/tests.js:401:42:401:46 | value | +| PrototypePollutionUtility/tests.js:398:13:398:41 | value | PrototypePollutionUtility/tests.js:401:42:401:46 | value | +| PrototypePollutionUtility/tests.js:398:13:398:41 | value | PrototypePollutionUtility/tests.js:401:42:401:46 | value | +| PrototypePollutionUtility/tests.js:398:13:398:41 | value | PrototypePollutionUtility/tests.js:401:42:401:46 | value | +| PrototypePollutionUtility/tests.js:398:13:398:41 | value | PrototypePollutionUtility/tests.js:403:24:403:28 | value | +| PrototypePollutionUtility/tests.js:398:13:398:41 | value | PrototypePollutionUtility/tests.js:403:24:403:28 | value | +| PrototypePollutionUtility/tests.js:398:13:398:41 | value | PrototypePollutionUtility/tests.js:403:24:403:28 | value | +| PrototypePollutionUtility/tests.js:398:13:398:41 | value | PrototypePollutionUtility/tests.js:403:24:403:28 | value | +| PrototypePollutionUtility/tests.js:398:13:398:41 | value | PrototypePollutionUtility/tests.js:403:24:403:28 | value | +| PrototypePollutionUtility/tests.js:398:13:398:41 | value | PrototypePollutionUtility/tests.js:403:24:403:28 | value | +| PrototypePollutionUtility/tests.js:398:13:398:41 | value | PrototypePollutionUtility/tests.js:403:24:403:28 | value | +| PrototypePollutionUtility/tests.js:398:13:398:41 | value | PrototypePollutionUtility/tests.js:403:24:403:28 | value | +| PrototypePollutionUtility/tests.js:398:21:398:41 | wrapped ... c, key) | PrototypePollutionUtility/tests.js:398:13:398:41 | value | +| PrototypePollutionUtility/tests.js:398:21:398:41 | wrapped ... c, key) | PrototypePollutionUtility/tests.js:398:13:398:41 | value | +| PrototypePollutionUtility/tests.js:398:21:398:41 | wrapped ... c, key) | PrototypePollutionUtility/tests.js:398:13:398:41 | value | +| PrototypePollutionUtility/tests.js:398:21:398:41 | wrapped ... c, key) | PrototypePollutionUtility/tests.js:398:13:398:41 | value | +| PrototypePollutionUtility/tests.js:398:21:398:41 | wrapped ... c, key) | PrototypePollutionUtility/tests.js:398:13:398:41 | value | +| PrototypePollutionUtility/tests.js:398:21:398:41 | wrapped ... c, key) | PrototypePollutionUtility/tests.js:398:13:398:41 | value | +| PrototypePollutionUtility/tests.js:398:33:398:35 | src | PrototypePollutionUtility/tests.js:398:21:398:41 | wrapped ... c, key) | +| PrototypePollutionUtility/tests.js:398:33:398:35 | src | PrototypePollutionUtility/tests.js:398:21:398:41 | wrapped ... c, key) | +| PrototypePollutionUtility/tests.js:398:38:398:40 | key | PrototypePollutionUtility/tests.js:398:21:398:41 | wrapped ... c, key) | +| PrototypePollutionUtility/tests.js:398:38:398:40 | key | PrototypePollutionUtility/tests.js:398:21:398:41 | wrapped ... c, key) | +| PrototypePollutionUtility/tests.js:399:13:399:42 | target | PrototypePollutionUtility/tests.js:401:34:401:39 | target | +| PrototypePollutionUtility/tests.js:399:13:399:42 | target | PrototypePollutionUtility/tests.js:401:34:401:39 | target | +| PrototypePollutionUtility/tests.js:399:13:399:42 | target | PrototypePollutionUtility/tests.js:401:34:401:39 | target | +| PrototypePollutionUtility/tests.js:399:13:399:42 | target | PrototypePollutionUtility/tests.js:401:34:401:39 | target | +| PrototypePollutionUtility/tests.js:399:22:399:42 | wrapped ... t, key) | PrototypePollutionUtility/tests.js:399:13:399:42 | target | +| PrototypePollutionUtility/tests.js:399:22:399:42 | wrapped ... t, key) | PrototypePollutionUtility/tests.js:399:13:399:42 | target | +| PrototypePollutionUtility/tests.js:399:22:399:42 | wrapped ... t, key) | PrototypePollutionUtility/tests.js:399:13:399:42 | target | +| PrototypePollutionUtility/tests.js:399:22:399:42 | wrapped ... t, key) | PrototypePollutionUtility/tests.js:399:13:399:42 | target | +| PrototypePollutionUtility/tests.js:399:34:399:36 | dst | PrototypePollutionUtility/tests.js:399:22:399:42 | wrapped ... t, key) | +| PrototypePollutionUtility/tests.js:399:34:399:36 | dst | PrototypePollutionUtility/tests.js:399:22:399:42 | wrapped ... t, key) | +| PrototypePollutionUtility/tests.js:399:39:399:41 | key | PrototypePollutionUtility/tests.js:399:22:399:42 | wrapped ... t, key) | +| PrototypePollutionUtility/tests.js:399:39:399:41 | key | PrototypePollutionUtility/tests.js:399:22:399:42 | wrapped ... t, key) | +| PrototypePollutionUtility/tests.js:401:34:401:39 | target | PrototypePollutionUtility/tests.js:396:31:396:33 | dst | +| PrototypePollutionUtility/tests.js:401:34:401:39 | target | PrototypePollutionUtility/tests.js:396:31:396:33 | dst | +| PrototypePollutionUtility/tests.js:401:34:401:39 | target | PrototypePollutionUtility/tests.js:396:31:396:33 | dst | +| PrototypePollutionUtility/tests.js:401:34:401:39 | target | PrototypePollutionUtility/tests.js:396:31:396:33 | dst | +| PrototypePollutionUtility/tests.js:401:42:401:46 | value | PrototypePollutionUtility/tests.js:396:36:396:38 | src | +| PrototypePollutionUtility/tests.js:401:42:401:46 | value | PrototypePollutionUtility/tests.js:396:36:396:38 | src | +| PrototypePollutionUtility/tests.js:401:42:401:46 | value | PrototypePollutionUtility/tests.js:396:36:396:38 | src | +| PrototypePollutionUtility/tests.js:401:42:401:46 | value | PrototypePollutionUtility/tests.js:396:36:396:38 | src | +| PrototypePollutionUtility/tests.js:413:34:413:36 | dst | PrototypePollutionUtility/tests.js:416:37:416:39 | dst | +| PrototypePollutionUtility/tests.js:413:34:413:36 | dst | PrototypePollutionUtility/tests.js:420:13:420:15 | dst | +| PrototypePollutionUtility/tests.js:413:34:413:36 | dst | PrototypePollutionUtility/tests.js:420:13:420:15 | dst | +| PrototypePollutionUtility/tests.js:413:39:413:41 | src | PrototypePollutionUtility/tests.js:415:36:415:38 | src | +| PrototypePollutionUtility/tests.js:413:39:413:41 | src | PrototypePollutionUtility/tests.js:415:36:415:38 | src | +| PrototypePollutionUtility/tests.js:414:14:414:16 | key | PrototypePollutionUtility/tests.js:415:41:415:43 | key | +| PrototypePollutionUtility/tests.js:414:14:414:16 | key | PrototypePollutionUtility/tests.js:415:41:415:43 | key | +| PrototypePollutionUtility/tests.js:414:14:414:16 | key | PrototypePollutionUtility/tests.js:416:42:416:44 | key | +| PrototypePollutionUtility/tests.js:414:14:414:16 | key | PrototypePollutionUtility/tests.js:416:42:416:44 | key | +| PrototypePollutionUtility/tests.js:414:14:414:16 | key | PrototypePollutionUtility/tests.js:420:17:420:19 | key | +| PrototypePollutionUtility/tests.js:414:14:414:16 | key | PrototypePollutionUtility/tests.js:420:17:420:19 | key | +| PrototypePollutionUtility/tests.js:414:14:414:16 | key | PrototypePollutionUtility/tests.js:420:17:420:19 | key | +| PrototypePollutionUtility/tests.js:414:14:414:16 | key | PrototypePollutionUtility/tests.js:420:17:420:19 | key | +| PrototypePollutionUtility/tests.js:414:14:414:16 | key | PrototypePollutionUtility/tests.js:420:17:420:19 | key | +| PrototypePollutionUtility/tests.js:414:14:414:16 | key | PrototypePollutionUtility/tests.js:420:17:420:19 | key | +| PrototypePollutionUtility/tests.js:414:14:414:16 | key | PrototypePollutionUtility/tests.js:420:17:420:19 | key | +| PrototypePollutionUtility/tests.js:415:13:415:44 | value | PrototypePollutionUtility/tests.js:418:45:418:49 | value | +| PrototypePollutionUtility/tests.js:415:13:415:44 | value | PrototypePollutionUtility/tests.js:418:45:418:49 | value | +| PrototypePollutionUtility/tests.js:415:13:415:44 | value | PrototypePollutionUtility/tests.js:418:45:418:49 | value | +| PrototypePollutionUtility/tests.js:415:13:415:44 | value | PrototypePollutionUtility/tests.js:418:45:418:49 | value | +| PrototypePollutionUtility/tests.js:415:13:415:44 | value | PrototypePollutionUtility/tests.js:420:24:420:28 | value | +| PrototypePollutionUtility/tests.js:415:13:415:44 | value | PrototypePollutionUtility/tests.js:420:24:420:28 | value | +| PrototypePollutionUtility/tests.js:415:13:415:44 | value | PrototypePollutionUtility/tests.js:420:24:420:28 | value | +| PrototypePollutionUtility/tests.js:415:13:415:44 | value | PrototypePollutionUtility/tests.js:420:24:420:28 | value | +| PrototypePollutionUtility/tests.js:415:13:415:44 | value | PrototypePollutionUtility/tests.js:420:24:420:28 | value | +| PrototypePollutionUtility/tests.js:415:13:415:44 | value | PrototypePollutionUtility/tests.js:420:24:420:28 | value | +| PrototypePollutionUtility/tests.js:415:13:415:44 | value | PrototypePollutionUtility/tests.js:420:24:420:28 | value | +| PrototypePollutionUtility/tests.js:415:13:415:44 | value | PrototypePollutionUtility/tests.js:420:24:420:28 | value | +| PrototypePollutionUtility/tests.js:415:21:415:44 | almostS ... c, key) | PrototypePollutionUtility/tests.js:415:13:415:44 | value | +| PrototypePollutionUtility/tests.js:415:21:415:44 | almostS ... c, key) | PrototypePollutionUtility/tests.js:415:13:415:44 | value | +| PrototypePollutionUtility/tests.js:415:21:415:44 | almostS ... c, key) | PrototypePollutionUtility/tests.js:415:13:415:44 | value | +| PrototypePollutionUtility/tests.js:415:21:415:44 | almostS ... c, key) | PrototypePollutionUtility/tests.js:415:13:415:44 | value | +| PrototypePollutionUtility/tests.js:415:21:415:44 | almostS ... c, key) | PrototypePollutionUtility/tests.js:415:13:415:44 | value | +| PrototypePollutionUtility/tests.js:415:21:415:44 | almostS ... c, key) | PrototypePollutionUtility/tests.js:415:13:415:44 | value | +| PrototypePollutionUtility/tests.js:415:36:415:38 | src | PrototypePollutionUtility/tests.js:415:21:415:44 | almostS ... c, key) | +| PrototypePollutionUtility/tests.js:415:36:415:38 | src | PrototypePollutionUtility/tests.js:415:21:415:44 | almostS ... c, key) | +| PrototypePollutionUtility/tests.js:415:41:415:43 | key | PrototypePollutionUtility/tests.js:415:21:415:44 | almostS ... c, key) | +| PrototypePollutionUtility/tests.js:416:13:416:45 | target | PrototypePollutionUtility/tests.js:418:37:418:42 | target | +| PrototypePollutionUtility/tests.js:416:13:416:45 | target | PrototypePollutionUtility/tests.js:418:37:418:42 | target | +| PrototypePollutionUtility/tests.js:416:22:416:45 | almostS ... t, key) | PrototypePollutionUtility/tests.js:416:13:416:45 | target | +| PrototypePollutionUtility/tests.js:416:22:416:45 | almostS ... t, key) | PrototypePollutionUtility/tests.js:416:13:416:45 | target | +| PrototypePollutionUtility/tests.js:416:37:416:39 | dst | PrototypePollutionUtility/tests.js:416:22:416:45 | almostS ... t, key) | +| PrototypePollutionUtility/tests.js:416:42:416:44 | key | PrototypePollutionUtility/tests.js:416:22:416:45 | almostS ... t, key) | +| PrototypePollutionUtility/tests.js:418:37:418:42 | target | PrototypePollutionUtility/tests.js:413:34:413:36 | dst | +| PrototypePollutionUtility/tests.js:418:37:418:42 | target | PrototypePollutionUtility/tests.js:413:34:413:36 | dst | +| PrototypePollutionUtility/tests.js:418:45:418:49 | value | PrototypePollutionUtility/tests.js:413:39:413:41 | src | +| PrototypePollutionUtility/tests.js:418:45:418:49 | value | PrototypePollutionUtility/tests.js:413:39:413:41 | src | +| PrototypePollutionUtility/tests.js:418:45:418:49 | value | PrototypePollutionUtility/tests.js:413:39:413:41 | src | +| PrototypePollutionUtility/tests.js:418:45:418:49 | value | PrototypePollutionUtility/tests.js:413:39:413:41 | src | +| PrototypePollutionUtility/tests.js:430:33:430:35 | src | PrototypePollutionUtility/tests.js:432:30:432:32 | src | +| PrototypePollutionUtility/tests.js:430:33:430:35 | src | PrototypePollutionUtility/tests.js:432:30:432:32 | src | +| PrototypePollutionUtility/tests.js:431:14:431:16 | key | PrototypePollutionUtility/tests.js:437:17:437:19 | key | +| PrototypePollutionUtility/tests.js:431:14:431:16 | key | PrototypePollutionUtility/tests.js:437:17:437:19 | key | +| PrototypePollutionUtility/tests.js:431:14:431:16 | key | PrototypePollutionUtility/tests.js:437:17:437:19 | key | +| PrototypePollutionUtility/tests.js:431:14:431:16 | key | PrototypePollutionUtility/tests.js:437:17:437:19 | key | +| PrototypePollutionUtility/tests.js:431:14:431:16 | key | PrototypePollutionUtility/tests.js:437:17:437:19 | key | +| PrototypePollutionUtility/tests.js:431:14:431:16 | key | PrototypePollutionUtility/tests.js:437:17:437:19 | key | +| PrototypePollutionUtility/tests.js:431:14:431:16 | key | PrototypePollutionUtility/tests.js:437:17:437:19 | key | +| PrototypePollutionUtility/tests.js:432:13:432:38 | value | PrototypePollutionUtility/tests.js:435:39:435:43 | value | +| PrototypePollutionUtility/tests.js:432:13:432:38 | value | PrototypePollutionUtility/tests.js:435:39:435:43 | value | +| PrototypePollutionUtility/tests.js:432:13:432:38 | value | PrototypePollutionUtility/tests.js:435:39:435:43 | value | +| PrototypePollutionUtility/tests.js:432:13:432:38 | value | PrototypePollutionUtility/tests.js:435:39:435:43 | value | +| PrototypePollutionUtility/tests.js:432:13:432:38 | value | PrototypePollutionUtility/tests.js:437:24:437:28 | value | +| PrototypePollutionUtility/tests.js:432:13:432:38 | value | PrototypePollutionUtility/tests.js:437:24:437:28 | value | +| PrototypePollutionUtility/tests.js:432:13:432:38 | value | PrototypePollutionUtility/tests.js:437:24:437:28 | value | +| PrototypePollutionUtility/tests.js:432:13:432:38 | value | PrototypePollutionUtility/tests.js:437:24:437:28 | value | +| PrototypePollutionUtility/tests.js:432:13:432:38 | value | PrototypePollutionUtility/tests.js:437:24:437:28 | value | +| PrototypePollutionUtility/tests.js:432:13:432:38 | value | PrototypePollutionUtility/tests.js:437:24:437:28 | value | +| PrototypePollutionUtility/tests.js:432:13:432:38 | value | PrototypePollutionUtility/tests.js:437:24:437:28 | value | +| PrototypePollutionUtility/tests.js:432:13:432:38 | value | PrototypePollutionUtility/tests.js:437:24:437:28 | value | +| PrototypePollutionUtility/tests.js:432:21:432:38 | safeRead(src, key) | PrototypePollutionUtility/tests.js:432:13:432:38 | value | +| PrototypePollutionUtility/tests.js:432:21:432:38 | safeRead(src, key) | PrototypePollutionUtility/tests.js:432:13:432:38 | value | +| PrototypePollutionUtility/tests.js:432:21:432:38 | safeRead(src, key) | PrototypePollutionUtility/tests.js:432:13:432:38 | value | +| PrototypePollutionUtility/tests.js:432:21:432:38 | safeRead(src, key) | PrototypePollutionUtility/tests.js:432:13:432:38 | value | +| PrototypePollutionUtility/tests.js:432:21:432:38 | safeRead(src, key) | PrototypePollutionUtility/tests.js:432:13:432:38 | value | +| PrototypePollutionUtility/tests.js:432:21:432:38 | safeRead(src, key) | PrototypePollutionUtility/tests.js:432:13:432:38 | value | +| PrototypePollutionUtility/tests.js:432:30:432:32 | src | PrototypePollutionUtility/tests.js:432:21:432:38 | safeRead(src, key) | +| PrototypePollutionUtility/tests.js:432:30:432:32 | src | PrototypePollutionUtility/tests.js:432:21:432:38 | safeRead(src, key) | +| PrototypePollutionUtility/tests.js:435:39:435:43 | value | PrototypePollutionUtility/tests.js:430:33:430:35 | src | +| PrototypePollutionUtility/tests.js:435:39:435:43 | value | PrototypePollutionUtility/tests.js:430:33:430:35 | src | +| PrototypePollutionUtility/tests.js:435:39:435:43 | value | PrototypePollutionUtility/tests.js:430:33:430:35 | src | +| PrototypePollutionUtility/tests.js:435:39:435:43 | value | PrototypePollutionUtility/tests.js:430:33:430:35 | src | | examples/PrototypePollutionUtility.js:1:16:1:18 | dst | examples/PrototypePollutionUtility.js:5:19:5:21 | dst | | examples/PrototypePollutionUtility.js:1:16:1:18 | dst | examples/PrototypePollutionUtility.js:5:19:5:21 | dst | | examples/PrototypePollutionUtility.js:1:16:1:18 | dst | examples/PrototypePollutionUtility.js:7:13:7:15 | dst | @@ -2136,4 +2361,7 @@ edges | PrototypePollutionUtility/tests.js:280:13:280:15 | dst | PrototypePollutionUtility/tests.js:276:34:276:36 | key | PrototypePollutionUtility/tests.js:280:13:280:15 | dst | Properties are copied from $@ to $@ without guarding against prototype pollution. | PrototypePollutionUtility/tests.js:276:21:276:23 | src | src | PrototypePollutionUtility/tests.js:280:13:280:15 | dst | dst | | PrototypePollutionUtility/tests.js:308:17:308:19 | dst | PrototypePollutionUtility/tests.js:302:14:302:16 | key | PrototypePollutionUtility/tests.js:308:17:308:19 | dst | Properties are copied from $@ to $@ without guarding against prototype pollution. | PrototypePollutionUtility/tests.js:302:21:302:23 | src | src | PrototypePollutionUtility/tests.js:308:17:308:19 | dst | dst | | PrototypePollutionUtility/tests.js:322:17:322:19 | dst | PrototypePollutionUtility/tests.js:315:14:315:16 | key | PrototypePollutionUtility/tests.js:322:17:322:19 | dst | Properties are copied from $@ to $@ without guarding against prototype pollution. | PrototypePollutionUtility/tests.js:315:21:315:23 | src | src | PrototypePollutionUtility/tests.js:322:17:322:19 | dst | dst | +| PrototypePollutionUtility/tests.js:387:13:387:15 | dst | PrototypePollutionUtility/tests.js:365:14:365:16 | key | PrototypePollutionUtility/tests.js:387:13:387:15 | dst | Properties are copied from $@ to $@ without guarding against prototype pollution. | PrototypePollutionUtility/tests.js:365:21:365:23 | obj | obj | PrototypePollutionUtility/tests.js:387:13:387:15 | dst | dst | +| PrototypePollutionUtility/tests.js:403:13:403:15 | dst | PrototypePollutionUtility/tests.js:397:14:397:16 | key | PrototypePollutionUtility/tests.js:403:13:403:15 | dst | Properties are copied from $@ to $@ without guarding against prototype pollution. | PrototypePollutionUtility/tests.js:397:21:397:23 | src | src | PrototypePollutionUtility/tests.js:403:13:403:15 | dst | dst | +| PrototypePollutionUtility/tests.js:420:13:420:15 | dst | PrototypePollutionUtility/tests.js:414:14:414:16 | key | PrototypePollutionUtility/tests.js:420:13:420:15 | dst | Properties are copied from $@ to $@ without guarding against prototype pollution. | PrototypePollutionUtility/tests.js:414:21:414:23 | src | src | PrototypePollutionUtility/tests.js:420:13:420:15 | dst | dst | | examples/PrototypePollutionUtility.js:7:13:7:15 | dst | examples/PrototypePollutionUtility.js:2:14:2:16 | key | examples/PrototypePollutionUtility.js:7:13:7:15 | dst | Properties are copied from $@ to $@ without guarding against prototype pollution. | examples/PrototypePollutionUtility.js:2:21:2:23 | src | src | examples/PrototypePollutionUtility.js:7:13:7:15 | dst | dst | diff --git a/javascript/ql/test/query-tests/Security/CWE-400/PrototypePollutionUtility/tests.js b/javascript/ql/test/query-tests/Security/CWE-400/PrototypePollutionUtility/tests.js index 57f080770d9..0c59890bd9c 100644 --- a/javascript/ql/test/query-tests/Security/CWE-400/PrototypePollutionUtility/tests.js +++ b/javascript/ql/test/query-tests/Security/CWE-400/PrototypePollutionUtility/tests.js @@ -360,3 +360,81 @@ function mergePlainObjectsOnly(target, source) { } return target; } + +function forEachProp(obj, callback) { + for (let key in obj) { + if (obj.hasOwnProperty(key)) { + callback(key, obj[key]); + } + } +} + +function mergeUsingCallback(dst, src) { + forEachProp(src, key => { + if (dst[key]) { + mergeUsingCallback(dst[key], src[key]); + } else { + dst[key] = src[key]; // NOT OK - but not currently flagged + } + }); +} + +function mergeUsingCallback2(dst, src) { + forEachProp(src, (key, value) => { + if (dst[key]) { + mergeUsingCallback2(dst[key], value); + } else { + dst[key] = value; // NOT OK + } + }); +} + +function wrappedRead(obj, key) { + return obj[key]; +} + +function copyUsingWrappedRead(dst, src) { + for (let key in src) { + let value = wrappedRead(src, key); + let target = wrappedRead(dst, key); + if (target) { + copyUsingWrappedRead(target, value); + } else { + dst[key] = value; // NOT OK + } + } +} + +function almostSafeRead(obj, key) { + if (key === '__proto__') return undefined; + return obj[key]; +} + +function copyUsingAlmostSafeRead(dst, src) { + for (let key in src) { + let value = almostSafeRead(src, key); + let target = almostSafeRead(dst, key); + if (target) { + copyUsingAlmostSafeRead(target, value); + } else { + dst[key] = value; // NOT OK + } + } +} + +function safeRead(obj, key) { + if (key === '__proto__' || key === 'constructor') return undefined; + return obj[key]; +} + +function copyUsingSafeRead(dst, src) { + for (let key in src) { + let value = safeRead(src, key); + let target = safeRead(dst, key); + if (target) { + copyUsingSafeRead(target, value); + } else { + dst[key] = value; // OK + } + } +} diff --git a/javascript/upgrades/96b0a386b6fb8da2b5f2514f26154a10c906f9c5/old.dbscheme b/javascript/upgrades/96b0a386b6fb8da2b5f2514f26154a10c906f9c5/old.dbscheme new file mode 100644 index 00000000000..96b0a386b6f --- /dev/null +++ b/javascript/upgrades/96b0a386b6fb8da2b5f2514f26154a10c906f9c5/old.dbscheme @@ -0,0 +1,1186 @@ +/*** Standard fragments ***/ + +/** Files and folders **/ + +@location = @location_default; + +locations_default(unique int id: @location_default, + int file: @file ref, + int beginLine: int ref, + int beginColumn: int ref, + int endLine: int ref, + int endColumn: int ref + ); + +@sourceline = @locatable; + +numlines(int element_id: @sourceline ref, + int num_lines: int ref, + int num_code: int ref, + int num_comment: int ref + ); + + +/* + fromSource(0) = unknown, + fromSource(1) = from source, + fromSource(2) = from library +*/ +files(unique int id: @file, + varchar(900) name: string ref, + varchar(900) simple: string ref, + varchar(900) ext: string ref, + int fromSource: int ref); + +folders(unique int id: @folder, + varchar(900) name: string ref, + varchar(900) simple: string ref); + + +@container = @folder | @file ; + + +containerparent(int parent: @container ref, + unique int child: @container ref); + +/** Duplicate code **/ + +duplicateCode( + unique int id : @duplication, + varchar(900) relativePath : string ref, + int equivClass : int ref); + +similarCode( + unique int id : @similarity, + varchar(900) relativePath : string ref, + int equivClass : int ref); + +@duplication_or_similarity = @duplication | @similarity; + +tokens( + int id : @duplication_or_similarity ref, + int offset : int ref, + int beginLine : int ref, + int beginColumn : int ref, + int endLine : int ref, + int endColumn : int ref); + +/** External data **/ + +externalData( + int id : @externalDataElement, + varchar(900) path : string ref, + int column: int ref, + varchar(900) value : string ref +); + +snapshotDate(unique date snapshotDate : date ref); + +sourceLocationPrefix(varchar(900) prefix : string ref); + +/** Version control data **/ + +svnentries( + int id : @svnentry, + varchar(500) revision : string ref, + varchar(500) author : string ref, + date revisionDate : date ref, + int changeSize : int ref +); + +svnaffectedfiles( + int id : @svnentry ref, + int file : @file ref, + varchar(500) action : string ref +); + +svnentrymsg( + int id : @svnentry ref, + varchar(500) message : string ref +); + +svnchurn( + int commit : @svnentry ref, + int file : @file ref, + int addedLines : int ref, + int deletedLines : int ref +); + + +/*** JavaScript-specific part ***/ + +filetype( + int file: @file ref, + string filetype: string ref +) + +// top-level code fragments +toplevels (unique int id: @toplevel, + int kind: int ref); + +isExterns (int toplevel: @toplevel ref); + +case @toplevel.kind of + 0 = @script +| 1 = @inline_script +| 2 = @event_handler +| 3 = @javascript_url; + +isModule (int tl: @toplevel ref); +isNodejs (int tl: @toplevel ref); +isES2015Module (int tl: @toplevel ref); +isClosureModule (int tl: @toplevel ref); + +// statements +#keyset[parent, idx] +stmts (unique int id: @stmt, + int kind: int ref, + int parent: @stmtparent ref, + int idx: int ref, + varchar(900) tostring: string ref); + +stmtContainers (unique int stmt: @stmt ref, + int container: @stmt_container ref); + +jumpTargets (unique int jump: @stmt ref, + int target: @stmt ref); + +@stmtparent = @stmt | @toplevel | @functionexpr | @arrowfunctionexpr; +@stmt_container = @toplevel | @function | @namespacedeclaration | @externalmoduledeclaration | @globalaugmentationdeclaration; + +case @stmt.kind of + 0 = @emptystmt +| 1 = @blockstmt +| 2 = @exprstmt +| 3 = @ifstmt +| 4 = @labeledstmt +| 5 = @breakstmt +| 6 = @continuestmt +| 7 = @withstmt +| 8 = @switchstmt +| 9 = @returnstmt +| 10 = @throwstmt +| 11 = @trystmt +| 12 = @whilestmt +| 13 = @dowhilestmt +| 14 = @forstmt +| 15 = @forinstmt +| 16 = @debuggerstmt +| 17 = @functiondeclstmt +| 18 = @vardeclstmt +| 19 = @case +| 20 = @catchclause +| 21 = @forofstmt +| 22 = @constdeclstmt +| 23 = @letstmt +| 24 = @legacy_letstmt +| 25 = @foreachstmt +| 26 = @classdeclstmt +| 27 = @importdeclaration +| 28 = @exportalldeclaration +| 29 = @exportdefaultdeclaration +| 30 = @exportnameddeclaration +| 31 = @namespacedeclaration +| 32 = @importequalsdeclaration +| 33 = @exportassigndeclaration +| 34 = @interfacedeclaration +| 35 = @typealiasdeclaration +| 36 = @enumdeclaration +| 37 = @externalmoduledeclaration +| 38 = @exportasnamespacedeclaration +| 39 = @globalaugmentationdeclaration +; + +@declstmt = @vardeclstmt | @constdeclstmt | @letstmt | @legacy_letstmt; + +@exportdeclaration = @exportalldeclaration | @exportdefaultdeclaration | @exportnameddeclaration; + +@namespacedefinition = @namespacedeclaration | @enumdeclaration; +@typedefinition = @classdefinition | @interfacedeclaration | @enumdeclaration | @typealiasdeclaration | @enum_member; + +isInstantiated(unique int decl: @namespacedeclaration ref); + +@declarablenode = @declstmt | @namespacedeclaration | @classdeclstmt | @functiondeclstmt | @enumdeclaration | @externalmoduledeclaration | @globalaugmentationdeclaration | @field; +hasDeclareKeyword(unique int stmt: @declarablenode ref); + +isForAwaitOf(unique int forof: @forofstmt ref); + +// expressions +#keyset[parent, idx] +exprs (unique int id: @expr, + int kind: int ref, + int parent: @exprparent ref, + int idx: int ref, + varchar(900) tostring: string ref); + +literals (varchar(900) value: string ref, + varchar(900) raw: string ref, + unique int expr: @exprortype ref); + +enclosingStmt (unique int expr: @exprortype ref, + int stmt: @stmt ref); + +exprContainers (unique int expr: @exprortype ref, + int container: @stmt_container ref); + +arraySize (unique int ae: @arraylike ref, + int sz: int ref); + +isDelegating (int yield: @yieldexpr ref); + +@exprorstmt = @expr | @stmt; +@exprortype = @expr | @typeexpr; +@exprparent = @exprorstmt | @property | @functiontypeexpr; +@arraylike = @arrayexpr | @arraypattern; +@type_annotation = @typeexpr | @jsdoc_type_expr; + +case @expr.kind of + 0 = @label +| 1 = @nullliteral +| 2 = @booleanliteral +| 3 = @numberliteral +| 4 = @stringliteral +| 5 = @regexpliteral +| 6 = @thisexpr +| 7 = @arrayexpr +| 8 = @objexpr +| 9 = @functionexpr +| 10 = @seqexpr +| 11 = @conditionalexpr +| 12 = @newexpr +| 13 = @callexpr +| 14 = @dotexpr +| 15 = @indexexpr +| 16 = @negexpr +| 17 = @plusexpr +| 18 = @lognotexpr +| 19 = @bitnotexpr +| 20 = @typeofexpr +| 21 = @voidexpr +| 22 = @deleteexpr +| 23 = @eqexpr +| 24 = @neqexpr +| 25 = @eqqexpr +| 26 = @neqqexpr +| 27 = @ltexpr +| 28 = @leexpr +| 29 = @gtexpr +| 30 = @geexpr +| 31 = @lshiftexpr +| 32 = @rshiftexpr +| 33 = @urshiftexpr +| 34 = @addexpr +| 35 = @subexpr +| 36 = @mulexpr +| 37 = @divexpr +| 38 = @modexpr +| 39 = @bitorexpr +| 40 = @xorexpr +| 41 = @bitandexpr +| 42 = @inexpr +| 43 = @instanceofexpr +| 44 = @logandexpr +| 45 = @logorexpr +| 47 = @assignexpr +| 48 = @assignaddexpr +| 49 = @assignsubexpr +| 50 = @assignmulexpr +| 51 = @assigndivexpr +| 52 = @assignmodexpr +| 53 = @assignlshiftexpr +| 54 = @assignrshiftexpr +| 55 = @assignurshiftexpr +| 56 = @assignorexpr +| 57 = @assignxorexpr +| 58 = @assignandexpr +| 59 = @preincexpr +| 60 = @postincexpr +| 61 = @predecexpr +| 62 = @postdecexpr +| 63 = @parexpr +| 64 = @vardeclarator +| 65 = @arrowfunctionexpr +| 66 = @spreadelement +| 67 = @arraypattern +| 68 = @objectpattern +| 69 = @yieldexpr +| 70 = @taggedtemplateexpr +| 71 = @templateliteral +| 72 = @templateelement +| 73 = @arraycomprehensionexpr +| 74 = @generatorexpr +| 75 = @forincomprehensionblock +| 76 = @forofcomprehensionblock +| 77 = @legacy_letexpr +| 78 = @vardecl +| 79 = @proper_varaccess +| 80 = @classexpr +| 81 = @superexpr +| 82 = @newtargetexpr +| 83 = @namedimportspecifier +| 84 = @importdefaultspecifier +| 85 = @importnamespacespecifier +| 86 = @namedexportspecifier +| 87 = @expexpr +| 88 = @assignexpexpr +| 89 = @jsxelement +| 90 = @jsxqualifiedname +| 91 = @jsxemptyexpr +| 92 = @awaitexpr +| 93 = @functionsentexpr +| 94 = @decorator +| 95 = @exportdefaultspecifier +| 96 = @exportnamespacespecifier +| 97 = @bindexpr +| 98 = @externalmodulereference +| 99 = @dynamicimport +| 100 = @expressionwithtypearguments +| 101 = @prefixtypeassertion +| 102 = @astypeassertion +| 103 = @export_varaccess +| 104 = @decorator_list +| 105 = @non_null_assertion +| 106 = @bigintliteral +| 107 = @nullishcoalescingexpr +| 108 = @e4x_xml_anyname +| 109 = @e4x_xml_static_attribute_selector +| 110 = @e4x_xml_dynamic_attribute_selector +| 111 = @e4x_xml_filter_expression +| 112 = @e4x_xml_static_qualident +| 113 = @e4x_xml_dynamic_qualident +| 114 = @e4x_xml_dotdotexpr +| 115 = @importmetaexpr +; + +@varaccess = @proper_varaccess | @export_varaccess; +@varref = @vardecl | @varaccess; + +@identifier = @label | @varref | @typeidentifier; + +@literal = @nullliteral | @booleanliteral | @numberliteral | @stringliteral | @regexpliteral | @bigintliteral; + +@propaccess = @dotexpr | @indexexpr; + +@invokeexpr = @newexpr | @callexpr; + +@unaryexpr = @negexpr | @plusexpr | @lognotexpr | @bitnotexpr | @typeofexpr | @voidexpr | @deleteexpr | @spreadelement; + +@equalitytest = @eqexpr | @neqexpr | @eqqexpr | @neqqexpr; + +@comparison = @equalitytest | @ltexpr | @leexpr | @gtexpr | @geexpr; + +@binaryexpr = @comparison | @lshiftexpr | @rshiftexpr | @urshiftexpr | @addexpr | @subexpr | @mulexpr | @divexpr | @modexpr | @expexpr | @bitorexpr | @xorexpr | @bitandexpr | @inexpr | @instanceofexpr | @logandexpr | @logorexpr | @nullishcoalescingexpr; + +@assignment = @assignexpr | @assignaddexpr | @assignsubexpr | @assignmulexpr | @assigndivexpr | @assignmodexpr | @assignexpexpr | @assignlshiftexpr | @assignrshiftexpr | @assignurshiftexpr | @assignorexpr | @assignxorexpr | @assignandexpr; + +@updateexpr = @preincexpr | @postincexpr | @predecexpr | @postdecexpr; + +@pattern = @varref | @arraypattern | @objectpattern; + +@comprehensionexpr = @arraycomprehensionexpr | @generatorexpr; + +@comprehensionblock = @forincomprehensionblock | @forofcomprehensionblock; + +@importspecifier = @namedimportspecifier | @importdefaultspecifier | @importnamespacespecifier; + +@exportspecifier = @namedexportspecifier | @exportdefaultspecifier | @exportnamespacespecifier; + +@typeassertion = @astypeassertion | @prefixtypeassertion; + +@classdefinition = @classdeclstmt | @classexpr; +@interfacedefinition = @interfacedeclaration | @interfacetypeexpr; +@classorinterface = @classdefinition | @interfacedefinition; + +@lexical_decl = @vardecl | @typedecl; +@lexical_access = @varaccess | @localtypeaccess | @localvartypeaccess | @localnamespaceaccess; +@lexical_ref = @lexical_decl | @lexical_access; + +@e4x_xml_attribute_selector = @e4x_xml_static_attribute_selector | @e4x_xml_dynamic_attribute_selector; +@e4x_xml_qualident = @e4x_xml_static_qualident | @e4x_xml_dynamic_qualident; + +// scopes +scopes (unique int id: @scope, + int kind: int ref); + +case @scope.kind of + 0 = @globalscope +| 1 = @functionscope +| 2 = @catchscope +| 3 = @modulescope +| 4 = @blockscope +| 5 = @forscope +| 6 = @forinscope // for-of scopes work the same as for-in scopes +| 7 = @comprehensionblockscope +| 8 = @classexprscope +| 9 = @namespacescope +| 10 = @classdeclscope +| 11 = @interfacescope +| 12 = @typealiasscope +| 13 = @mappedtypescope +| 14 = @enumscope +| 15 = @externalmodulescope +| 16 = @conditionaltypescope; + +scopenodes (unique int node: @ast_node ref, + int scope: @scope ref); + +scopenesting (unique int inner: @scope ref, + int outer: @scope ref); + +// functions +@function = @functiondeclstmt | @functionexpr | @arrowfunctionexpr; + +@parameterized = @function | @catchclause; +@type_parameterized = @function | @classorinterface | @typealiasdeclaration | @mappedtypeexpr | @infertypeexpr; + +isGenerator (int fun: @function ref); +hasRestParameter (int fun: @function ref); +isAsync (int fun: @function ref); + +// variables and lexically scoped type names +#keyset[scope, name] +variables (unique int id: @variable, + varchar(900) name: string ref, + int scope: @scope ref); + +#keyset[scope, name] +local_type_names (unique int id: @local_type_name, + varchar(900) name: string ref, + int scope: @scope ref); + +#keyset[scope, name] +local_namespace_names (unique int id: @local_namespace_name, + varchar(900) name: string ref, + int scope: @scope ref); + +isArgumentsObject (int id: @variable ref); + +@lexical_name = @variable | @local_type_name | @local_namespace_name; + +@bind_id = @varaccess | @localvartypeaccess; +bind (unique int id: @bind_id ref, + int decl: @variable ref); + +decl (unique int id: @vardecl ref, + int decl: @variable ref); + +@typebind_id = @localtypeaccess | @export_varaccess; +typebind (unique int id: @typebind_id ref, + int decl: @local_type_name ref); + +@typedecl_id = @typedecl | @vardecl; +typedecl (unique int id: @typedecl_id ref, + int decl: @local_type_name ref); + +namespacedecl (unique int id: @vardecl ref, + int decl: @local_namespace_name ref); + +@namespacebind_id = @localnamespaceaccess | @export_varaccess; +namespacebind (unique int id: @namespacebind_id ref, + int decl: @local_namespace_name ref); + + +// properties in object literals, property patterns in object patterns, and method declarations in classes +#keyset[parent, index] +properties (unique int id: @property, + int parent: @property_parent ref, + int index: int ref, + int kind: int ref, + varchar(900) tostring: string ref); + +case @property.kind of + 0 = @value_property +| 1 = @property_getter +| 2 = @property_setter +| 3 = @jsx_attribute +| 4 = @function_call_signature +| 5 = @constructor_call_signature +| 6 = @index_signature +| 7 = @enum_member +| 8 = @proper_field +| 9 = @parameter_field +; + +@property_parent = @objexpr | @objectpattern | @classdefinition | @jsxelement | @interfacedefinition | @enumdeclaration; +@property_accessor = @property_getter | @property_setter; +@call_signature = @function_call_signature | @constructor_call_signature; +@field = @proper_field | @parameter_field; +@field_or_vardeclarator = @field | @vardeclarator; + +isComputed (int id: @property ref); +isMethod (int id: @property ref); +isStatic (int id: @property ref); +isAbstractMember (int id: @property ref); +isConstEnum (int id: @enumdeclaration ref); +isAbstractClass (int id: @classdeclstmt ref); + +hasPublicKeyword (int id: @property ref); +hasPrivateKeyword (int id: @property ref); +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( + unique int field: @parameter_field ref, + int constructor: @functionexpr ref, + int param_index: int ref +); + +// types +#keyset[parent, idx] +typeexprs ( + unique int id: @typeexpr, + int kind: int ref, + int parent: @typeexpr_parent ref, + int idx: int ref, + varchar(900) tostring: string ref +); + +case @typeexpr.kind of + 0 = @localtypeaccess +| 1 = @typedecl +| 2 = @keywordtypeexpr +| 3 = @stringliteraltypeexpr +| 4 = @numberliteraltypeexpr +| 5 = @booleanliteraltypeexpr +| 6 = @arraytypeexpr +| 7 = @uniontypeexpr +| 8 = @indexedaccesstypeexpr +| 9 = @intersectiontypeexpr +| 10 = @parenthesizedtypeexpr +| 11 = @tupletypeexpr +| 12 = @keyoftypeexpr +| 13 = @qualifiedtypeaccess +| 14 = @generictypeexpr +| 15 = @typelabel +| 16 = @typeoftypeexpr +| 17 = @localvartypeaccess +| 18 = @qualifiedvartypeaccess +| 19 = @thisvartypeaccess +| 20 = @predicatetypeexpr +| 21 = @interfacetypeexpr +| 22 = @typeparameter +| 23 = @plainfunctiontypeexpr +| 24 = @constructortypeexpr +| 25 = @localnamespaceaccess +| 26 = @qualifiednamespaceaccess +| 27 = @mappedtypeexpr +| 28 = @conditionaltypeexpr +| 29 = @infertypeexpr +| 30 = @importtypeaccess +| 31 = @importnamespaceaccess +| 32 = @importvartypeaccess +| 33 = @optionaltypeexpr +| 34 = @resttypeexpr +| 35 = @bigintliteraltypeexpr +| 36 = @readonlytypeexpr +; + +@typeref = @typeaccess | @typedecl; +@typeidentifier = @typedecl | @localtypeaccess | @typelabel | @localvartypeaccess | @localnamespaceaccess; +@typeexpr_parent = @expr | @stmt | @property | @typeexpr; +@literaltypeexpr = @stringliteraltypeexpr | @numberliteraltypeexpr | @booleanliteraltypeexpr | @bigintliteraltypeexpr; +@typeaccess = @localtypeaccess | @qualifiedtypeaccess | @importtypeaccess; +@vartypeaccess = @localvartypeaccess | @qualifiedvartypeaccess | @thisvartypeaccess | @importvartypeaccess; +@namespaceaccess = @localnamespaceaccess | @qualifiednamespaceaccess | @importnamespaceaccess; +@importtypeexpr = @importtypeaccess | @importnamespaceaccess | @importvartypeaccess; + +@functiontypeexpr = @plainfunctiontypeexpr | @constructortypeexpr; + +// types +types ( + unique int id: @type, + int kind: int ref, + varchar(900) tostring: string ref +); + +#keyset[parent, idx] +type_child ( + int child: @type ref, + int parent: @type ref, + int idx: int ref +); + +case @type.kind of + 0 = @anytype +| 1 = @stringtype +| 2 = @numbertype +| 3 = @uniontype +| 4 = @truetype +| 5 = @falsetype +| 6 = @typereference +| 7 = @objecttype +| 8 = @canonicaltypevariabletype +| 9 = @typeoftype +| 10 = @voidtype +| 11 = @undefinedtype +| 12 = @nulltype +| 13 = @nevertype +| 14 = @plainsymboltype +| 15 = @uniquesymboltype +| 16 = @objectkeywordtype +| 17 = @intersectiontype +| 18 = @tupletype +| 19 = @lexicaltypevariabletype +| 20 = @thistype +| 21 = @numberliteraltype +| 22 = @stringliteraltype +| 23 = @unknowntype +| 24 = @biginttype +| 25 = @bigintliteraltype +; + +@booleanliteraltype = @truetype | @falsetype; +@symboltype = @plainsymboltype | @uniquesymboltype; +@unionorintersectiontype = @uniontype | @intersectiontype; +@typevariabletype = @canonicaltypevariabletype | @lexicaltypevariabletype; + +hasAssertsKeyword(int node: @predicatetypeexpr ref); + +@typed_ast_node = @expr | @typeexpr | @function; +ast_node_type( + unique int node: @typed_ast_node ref, + int typ: @type ref); + +declared_function_signature( + unique int node: @function ref, + int sig: @signature_type ref +); + +invoke_expr_signature( + unique int node: @invokeexpr ref, + int sig: @signature_type ref +); + +invoke_expr_overload_index( + unique int node: @invokeexpr ref, + int index: int ref +); + +symbols ( + unique int id: @symbol, + int kind: int ref, + varchar(900) name: string ref +); + +symbol_parent ( + unique int symbol: @symbol ref, + int parent: @symbol ref +); + +symbol_module ( + int symbol: @symbol ref, + varchar(900) moduleName: string ref +); + +symbol_global ( + int symbol: @symbol ref, + varchar(900) globalName: string ref +); + +case @symbol.kind of + 0 = @root_symbol +| 1 = @member_symbol +| 2 = @other_symbol +; + +@type_with_symbol = @typereference | @typevariabletype | @typeoftype | @uniquesymboltype; +@ast_node_with_symbol = @typedefinition | @namespacedefinition | @toplevel | @typeaccess | @namespaceaccess | @vardecl | @function | @invokeexpr; + +ast_node_symbol( + unique int node: @ast_node_with_symbol ref, + int symbol: @symbol ref); + +type_symbol( + unique int typ: @type_with_symbol ref, + int symbol: @symbol ref); + +#keyset[typ, name] +type_property( + int typ: @type ref, + 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( + unique int typ: @type_with_literal_value ref, + varchar(900) value: string ref); + +signature_types ( + unique int id: @signature_type, + int kind: int ref, + varchar(900) tostring: string ref, + int type_parameters: int ref, + 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 +; + +#keyset[typ, kind, index] +type_contains_signature ( + int typ: @type ref, + int kind: int ref, // constructor/call/index + int index: int ref, // ordering of overloaded signatures + int sig: @signature_type ref +); + +#keyset[parent, index] +signature_contains_type ( + int child: @type ref, + int parent: @signature_type ref, + int index: int ref +); + +#keyset[sig, index] +signature_parameter_name ( + int sig: @signature_type ref, + int index: int ref, + varchar(900) name: string ref +); + +number_index_type ( + unique int baseType: @type ref, + int propertyType: @type ref +); + +string_index_type ( + unique int baseType: @type ref, + int propertyType: @type ref +); + +base_type_names( + int typeName: @symbol ref, + int baseTypeName: @symbol ref +); + +self_types( + int typeName: @symbol ref, + int selfType: @typereference ref +); + +tuple_type_min_length( + unique int typ: @type ref, + int minLength: int ref +); + +tuple_type_rest( + unique int typ: @type ref +); + +// comments +comments (unique int id: @comment, + int kind: int ref, + int toplevel: @toplevel ref, + varchar(900) text: string ref, + varchar(900) tostring: string ref); + +case @comment.kind of + 0 = @slashslashcomment +| 1 = @slashstarcomment +| 2 = @doccomment +| 3 = @htmlcommentstart +| 4 = @htmlcommentend; + +@htmlcomment = @htmlcommentstart | @htmlcommentend; +@linecomment = @slashslashcomment | @htmlcomment; +@blockcomment = @slashstarcomment | @doccomment; + +// source lines +lines (unique int id: @line, + int toplevel: @toplevel ref, + varchar(900) text: string ref, + varchar(2) terminator: string ref); +indentation (int file: @file ref, + int lineno: int ref, + varchar(1) indentChar: string ref, + int indentDepth: int ref); + +// JavaScript parse errors +jsParseErrors (unique int id: @js_parse_error, + int toplevel: @toplevel ref, + varchar(900) message: string ref, + varchar(900) line: string ref); + +// regular expressions +#keyset[parent, idx] +regexpterm (unique int id: @regexpterm, + int kind: int ref, + int parent: @regexpparent ref, + int idx: int ref, + varchar(900) tostring: string ref); + +@regexpparent = @regexpterm | @regexpliteral | @stringliteral; + +case @regexpterm.kind of + 0 = @regexp_alt +| 1 = @regexp_seq +| 2 = @regexp_caret +| 3 = @regexp_dollar +| 4 = @regexp_wordboundary +| 5 = @regexp_nonwordboundary +| 6 = @regexp_positive_lookahead +| 7 = @regexp_negative_lookahead +| 8 = @regexp_star +| 9 = @regexp_plus +| 10 = @regexp_opt +| 11 = @regexp_range +| 12 = @regexp_dot +| 13 = @regexp_group +| 14 = @regexp_normal_constant +| 15 = @regexp_hex_escape +| 16 = @regexp_unicode_escape +| 17 = @regexp_dec_escape +| 18 = @regexp_oct_escape +| 19 = @regexp_ctrl_escape +| 20 = @regexp_char_class_escape +| 21 = @regexp_id_escape +| 22 = @regexp_backref +| 23 = @regexp_char_class +| 24 = @regexp_char_range +| 25 = @regexp_positive_lookbehind +| 26 = @regexp_negative_lookbehind +| 27 = @regexp_unicode_property_escape; + +regexpParseErrors (unique int id: @regexp_parse_error, + int regexp: @regexpterm ref, + varchar(900) message: string ref); + +@regexp_quantifier = @regexp_star | @regexp_plus | @regexp_opt | @regexp_range; +@regexp_escape = @regexp_char_escape | @regexp_char_class_escape | @regexp_unicode_property_escape; +@regexp_char_escape = @regexp_hex_escape | @regexp_unicode_escape | @regexp_dec_escape | @regexp_oct_escape | @regexp_ctrl_escape | @regexp_id_escape; +@regexp_constant = @regexp_normal_constant | @regexp_char_escape; +@regexp_lookahead = @regexp_positive_lookahead | @regexp_negative_lookahead; +@regexp_lookbehind = @regexp_positive_lookbehind | @regexp_negative_lookbehind; +@regexp_subpattern = @regexp_lookahead | @regexp_lookbehind; +@regexp_anchor = @regexp_dollar | @regexp_caret; + +isGreedy (int id: @regexp_quantifier ref); +rangeQuantifierLowerBound (unique int id: @regexp_range ref, int lo: int ref); +rangeQuantifierUpperBound (unique int id: @regexp_range ref, int hi: int ref); +isCapture (unique int id: @regexp_group ref, int number: int ref); +isNamedCapture (unique int id: @regexp_group ref, string name: string ref); +isInverted (int id: @regexp_char_class ref); +regexpConstValue (unique int id: @regexp_constant ref, varchar(1) value: string ref); +charClassEscape (unique int id: @regexp_char_class_escape ref, varchar(1) value: string ref); +backref (unique int id: @regexp_backref ref, int value: int ref); +namedBackref (unique int id: @regexp_backref ref, string name: string ref); +unicodePropertyEscapeName (unique int id: @regexp_unicode_property_escape ref, string name: string ref); +unicodePropertyEscapeValue (unique int id: @regexp_unicode_property_escape ref, string value: string ref); + +// tokens +#keyset[toplevel, idx] +tokeninfo (unique int id: @token, + int kind: int ref, + int toplevel: @toplevel ref, + int idx: int ref, + varchar(900) value: string ref); + +case @token.kind of + 0 = @token_eof +| 1 = @token_null_literal +| 2 = @token_boolean_literal +| 3 = @token_numeric_literal +| 4 = @token_string_literal +| 5 = @token_regular_expression +| 6 = @token_identifier +| 7 = @token_keyword +| 8 = @token_punctuator; + +// associate comments with the token immediately following them (which may be EOF) +next_token (int comment: @comment ref, int token: @token ref); + +// JSON +#keyset[parent, idx] +json (unique int id: @json_value, + int kind: int ref, + int parent: @json_parent ref, + int idx: int ref, + varchar(900) tostring: string ref); + +json_literals (varchar(900) value: string ref, + varchar(900) raw: string ref, + unique int expr: @json_value ref); + +json_properties (int obj: @json_object ref, + varchar(900) property: string ref, + int value: @json_value ref); + +json_errors (unique int id: @json_parse_error, + varchar(900) message: string ref); + +json_locations(unique int locatable: @json_locatable ref, + int location: @location_default ref); + +case @json_value.kind of + 0 = @json_null +| 1 = @json_boolean +| 2 = @json_number +| 3 = @json_string +| 4 = @json_array +| 5 = @json_object; + +@json_parent = @json_object | @json_array | @file; + +@json_locatable = @json_value | @json_parse_error; + +// locations +@ast_node = @toplevel | @stmt | @expr | @property | @typeexpr; + +@locatable = @file + | @ast_node + | @comment + | @line + | @js_parse_error | @regexp_parse_error + | @regexpterm + | @json_locatable + | @token + | @cfg_node + | @jsdoc | @jsdoc_type_expr | @jsdoc_tag + | @yaml_locatable + | @xmllocatable + | @configLocatable; + +hasLocation (unique int locatable: @locatable ref, + int location: @location ref); + +// CFG +entry_cfg_node (unique int id: @entry_node, int container: @stmt_container ref); +exit_cfg_node (unique int id: @exit_node, int container: @stmt_container ref); +guard_node (unique int id: @guard_node, int kind: int ref, int test: @expr ref); +case @guard_node.kind of + 0 = @falsy_guard +| 1 = @truthy_guard; +@condition_guard = @falsy_guard | @truthy_guard; + +@synthetic_cfg_node = @entry_node | @exit_node | @guard_node; +@cfg_node = @synthetic_cfg_node | @exprparent; + +successor (int pred: @cfg_node ref, int succ: @cfg_node ref); + +// JSDoc comments +jsdoc (unique int id: @jsdoc, varchar(900) description: string ref, int comment: @comment ref); +#keyset[parent, idx] +jsdoc_tags (unique int id: @jsdoc_tag, varchar(900) title: string ref, + int parent: @jsdoc ref, int idx: int ref, varchar(900) tostring: string ref); +jsdoc_tag_descriptions (unique int tag: @jsdoc_tag ref, varchar(900) text: string ref); +jsdoc_tag_names (unique int tag: @jsdoc_tag ref, varchar(900) text: string ref); + +#keyset[parent, idx] +jsdoc_type_exprs (unique int id: @jsdoc_type_expr, + int kind: int ref, + int parent: @jsdoc_type_expr_parent ref, + int idx: int ref, + varchar(900) tostring: string ref); +case @jsdoc_type_expr.kind of + 0 = @jsdoc_any_type_expr +| 1 = @jsdoc_null_type_expr +| 2 = @jsdoc_undefined_type_expr +| 3 = @jsdoc_unknown_type_expr +| 4 = @jsdoc_void_type_expr +| 5 = @jsdoc_named_type_expr +| 6 = @jsdoc_applied_type_expr +| 7 = @jsdoc_nullable_type_expr +| 8 = @jsdoc_non_nullable_type_expr +| 9 = @jsdoc_record_type_expr +| 10 = @jsdoc_array_type_expr +| 11 = @jsdoc_union_type_expr +| 12 = @jsdoc_function_type_expr +| 13 = @jsdoc_optional_type_expr +| 14 = @jsdoc_rest_type_expr +; + +#keyset[id, idx] +jsdoc_record_field_name (int id: @jsdoc_record_type_expr ref, int idx: int ref, varchar(900) name: string ref); +jsdoc_prefix_qualifier (int id: @jsdoc_type_expr ref); +jsdoc_has_new_parameter (int fn: @jsdoc_function_type_expr ref); + +@jsdoc_type_expr_parent = @jsdoc_type_expr | @jsdoc_tag; + +jsdoc_errors (unique int id: @jsdoc_error, int tag: @jsdoc_tag ref, varchar(900) message: string ref, varchar(900) tostring: string ref); + +// YAML +#keyset[parent, idx] +yaml (unique int id: @yaml_node, + int kind: int ref, + int parent: @yaml_node_parent ref, + int idx: int ref, + varchar(900) tag: string ref, + varchar(900) tostring: string ref); + +case @yaml_node.kind of + 0 = @yaml_scalar_node +| 1 = @yaml_mapping_node +| 2 = @yaml_sequence_node +| 3 = @yaml_alias_node +; + +@yaml_collection_node = @yaml_mapping_node | @yaml_sequence_node; + +@yaml_node_parent = @yaml_collection_node | @file; + +yaml_anchors (unique int node: @yaml_node ref, + varchar(900) anchor: string ref); + +yaml_aliases (unique int alias: @yaml_alias_node ref, + varchar(900) target: string ref); + +yaml_scalars (unique int scalar: @yaml_scalar_node ref, + int style: int ref, + varchar(900) value: string ref); + +yaml_errors (unique int id: @yaml_error, + varchar(900) message: string ref); + +yaml_locations(unique int locatable: @yaml_locatable ref, + int location: @location_default ref); + +@yaml_locatable = @yaml_node | @yaml_error; + +/* XML Files */ + +xmlEncoding( + unique int id: @file ref, + varchar(900) encoding: string ref +); + +xmlDTDs( + unique int id: @xmldtd, + varchar(900) root: string ref, + varchar(900) publicId: string ref, + varchar(900) systemId: string ref, + int fileid: @file ref +); + +xmlElements( + unique int id: @xmlelement, + varchar(900) name: string ref, + int parentid: @xmlparent ref, + int idx: int ref, + int fileid: @file ref +); + +xmlAttrs( + unique int id: @xmlattribute, + int elementid: @xmlelement ref, + varchar(900) name: string ref, + varchar(3600) value: string ref, + int idx: int ref, + int fileid: @file ref +); + +xmlNs( + int id: @xmlnamespace, + varchar(900) prefixName: string ref, + varchar(900) URI: string ref, + int fileid: @file ref +); + +xmlHasNs( + int elementId: @xmlnamespaceable ref, + int nsId: @xmlnamespace ref, + int fileid: @file ref +); + +xmlComments( + unique int id: @xmlcomment, + varchar(3600) text: string ref, + int parentid: @xmlparent ref, + int fileid: @file ref +); + +xmlChars( + unique int id: @xmlcharacters, + varchar(3600) text: string ref, + int parentid: @xmlparent ref, + int idx: int ref, + int isCDATA: int ref, + int fileid: @file ref +); + +@xmlparent = @file | @xmlelement; +@xmlnamespaceable = @xmlelement | @xmlattribute; + +xmllocations( + int xmlElement: @xmllocatable ref, + int location: @location_default ref +); + +@xmllocatable = @xmlcharacters | @xmlelement | @xmlcomment | @xmlattribute | @xmldtd | @file | @xmlnamespace; + +@dataflownode = @expr | @functiondeclstmt | @classdeclstmt | @namespacedeclaration | @enumdeclaration | @property; + +@optionalchainable = @callexpr | @propaccess; + +isOptionalChaining(int id: @optionalchainable ref); + +/* + * configuration files with key value pairs + */ + +configs( + unique int id: @config +); + +configNames( + unique int id: @configName, + int config: @config ref, + string name: string ref +); + +configValues( + unique int id: @configValue, + int config: @config ref, + string value: string ref +); + +configLocations( + int locatable: @configLocatable ref, + int location: @location_default ref +); + +@configLocatable = @config | @configName | @configValue; + +/** + * The time taken for the extraction of a file. + * This table contains non-deterministic content. + * + * The sum of the `time` column for each (`file`, `timerKind`) pair + * is the total time taken for extraction of `file`. The `extractionPhase` + * column provides a granular view of the extraction time of the file. + */ +extraction_time( + int file : @file ref, + // see `com.semmle.js.extractor.ExtractionMetrics.ExtractionPhase`. + int extractionPhase: int ref, + // 0 for the elapsed CPU time in nanoseconds, 1 for the elapsed wallclock time in nanoseconds + int timerKind: int ref, + float time: float ref +) + +/** + * Non-timing related data for the extraction of a single file. + * This table contains non-deterministic content. + */ +extraction_data( + int file : @file ref, + // the absolute path to the cache file + varchar(900) cacheFile: string ref, + boolean fromCache: boolean ref, + int length: int ref +) diff --git a/javascript/upgrades/96b0a386b6fb8da2b5f2514f26154a10c906f9c5/semmlecode.javascript.dbscheme b/javascript/upgrades/96b0a386b6fb8da2b5f2514f26154a10c906f9c5/semmlecode.javascript.dbscheme new file mode 100644 index 00000000000..dad09eeeff5 --- /dev/null +++ b/javascript/upgrades/96b0a386b6fb8da2b5f2514f26154a10c906f9c5/semmlecode.javascript.dbscheme @@ -0,0 +1,1186 @@ +/*** Standard fragments ***/ + +/** Files and folders **/ + +@location = @location_default; + +locations_default(unique int id: @location_default, + int file: @file ref, + int beginLine: int ref, + int beginColumn: int ref, + int endLine: int ref, + int endColumn: int ref + ); + +@sourceline = @locatable; + +numlines(int element_id: @sourceline ref, + int num_lines: int ref, + int num_code: int ref, + int num_comment: int ref + ); + + +/* + fromSource(0) = unknown, + fromSource(1) = from source, + fromSource(2) = from library +*/ +files(unique int id: @file, + varchar(900) name: string ref, + varchar(900) simple: string ref, + varchar(900) ext: string ref, + int fromSource: int ref); + +folders(unique int id: @folder, + varchar(900) name: string ref, + varchar(900) simple: string ref); + + +@container = @folder | @file ; + + +containerparent(int parent: @container ref, + unique int child: @container ref); + +/** Duplicate code **/ + +duplicateCode( + unique int id : @duplication, + varchar(900) relativePath : string ref, + int equivClass : int ref); + +similarCode( + unique int id : @similarity, + varchar(900) relativePath : string ref, + int equivClass : int ref); + +@duplication_or_similarity = @duplication | @similarity; + +tokens( + int id : @duplication_or_similarity ref, + int offset : int ref, + int beginLine : int ref, + int beginColumn : int ref, + int endLine : int ref, + int endColumn : int ref); + +/** External data **/ + +externalData( + int id : @externalDataElement, + varchar(900) path : string ref, + int column: int ref, + varchar(900) value : string ref +); + +snapshotDate(unique date snapshotDate : date ref); + +sourceLocationPrefix(varchar(900) prefix : string ref); + +/** Version control data **/ + +svnentries( + int id : @svnentry, + varchar(500) revision : string ref, + varchar(500) author : string ref, + date revisionDate : date ref, + int changeSize : int ref +); + +svnaffectedfiles( + int id : @svnentry ref, + int file : @file ref, + varchar(500) action : string ref +); + +svnentrymsg( + int id : @svnentry ref, + varchar(500) message : string ref +); + +svnchurn( + int commit : @svnentry ref, + int file : @file ref, + int addedLines : int ref, + int deletedLines : int ref +); + + +/*** JavaScript-specific part ***/ + +filetype( + int file: @file ref, + string filetype: string ref +) + +// top-level code fragments +toplevels (unique int id: @toplevel, + int kind: int ref); + +isExterns (int toplevel: @toplevel ref); + +case @toplevel.kind of + 0 = @script +| 1 = @inline_script +| 2 = @event_handler +| 3 = @javascript_url; + +isModule (int tl: @toplevel ref); +isNodejs (int tl: @toplevel ref); +isES2015Module (int tl: @toplevel ref); +isClosureModule (int tl: @toplevel ref); + +// statements +#keyset[parent, idx] +stmts (unique int id: @stmt, + int kind: int ref, + int parent: @stmtparent ref, + int idx: int ref, + varchar(900) tostring: string ref); + +stmtContainers (unique int stmt: @stmt ref, + int container: @stmt_container ref); + +jumpTargets (unique int jump: @stmt ref, + int target: @stmt ref); + +@stmtparent = @stmt | @toplevel | @functionexpr | @arrowfunctionexpr; +@stmt_container = @toplevel | @function | @namespacedeclaration | @externalmoduledeclaration | @globalaugmentationdeclaration; + +case @stmt.kind of + 0 = @emptystmt +| 1 = @blockstmt +| 2 = @exprstmt +| 3 = @ifstmt +| 4 = @labeledstmt +| 5 = @breakstmt +| 6 = @continuestmt +| 7 = @withstmt +| 8 = @switchstmt +| 9 = @returnstmt +| 10 = @throwstmt +| 11 = @trystmt +| 12 = @whilestmt +| 13 = @dowhilestmt +| 14 = @forstmt +| 15 = @forinstmt +| 16 = @debuggerstmt +| 17 = @functiondeclstmt +| 18 = @vardeclstmt +| 19 = @case +| 20 = @catchclause +| 21 = @forofstmt +| 22 = @constdeclstmt +| 23 = @letstmt +| 24 = @legacy_letstmt +| 25 = @foreachstmt +| 26 = @classdeclstmt +| 27 = @importdeclaration +| 28 = @exportalldeclaration +| 29 = @exportdefaultdeclaration +| 30 = @exportnameddeclaration +| 31 = @namespacedeclaration +| 32 = @importequalsdeclaration +| 33 = @exportassigndeclaration +| 34 = @interfacedeclaration +| 35 = @typealiasdeclaration +| 36 = @enumdeclaration +| 37 = @externalmoduledeclaration +| 38 = @exportasnamespacedeclaration +| 39 = @globalaugmentationdeclaration +; + +@declstmt = @vardeclstmt | @constdeclstmt | @letstmt | @legacy_letstmt; + +@exportdeclaration = @exportalldeclaration | @exportdefaultdeclaration | @exportnameddeclaration; + +@namespacedefinition = @namespacedeclaration | @enumdeclaration; +@typedefinition = @classdefinition | @interfacedeclaration | @enumdeclaration | @typealiasdeclaration | @enum_member; + +isInstantiated(unique int decl: @namespacedeclaration ref); + +@declarablenode = @declstmt | @namespacedeclaration | @classdeclstmt | @functiondeclstmt | @enumdeclaration | @externalmoduledeclaration | @globalaugmentationdeclaration | @field; +hasDeclareKeyword(unique int stmt: @declarablenode ref); + +isForAwaitOf(unique int forof: @forofstmt ref); + +// expressions +#keyset[parent, idx] +exprs (unique int id: @expr, + int kind: int ref, + int parent: @exprparent ref, + int idx: int ref, + varchar(900) tostring: string ref); + +literals (varchar(900) value: string ref, + varchar(900) raw: string ref, + unique int expr: @exprortype ref); + +enclosingStmt (unique int expr: @exprortype ref, + int stmt: @stmt ref); + +exprContainers (unique int expr: @exprortype ref, + int container: @stmt_container ref); + +arraySize (unique int ae: @arraylike ref, + int sz: int ref); + +isDelegating (int yield: @yieldexpr ref); + +@exprorstmt = @expr | @stmt; +@exprortype = @expr | @typeexpr; +@exprparent = @exprorstmt | @property | @functiontypeexpr; +@arraylike = @arrayexpr | @arraypattern; +@type_annotation = @typeexpr | @jsdoc_type_expr; + +case @expr.kind of + 0 = @label +| 1 = @nullliteral +| 2 = @booleanliteral +| 3 = @numberliteral +| 4 = @stringliteral +| 5 = @regexpliteral +| 6 = @thisexpr +| 7 = @arrayexpr +| 8 = @objexpr +| 9 = @functionexpr +| 10 = @seqexpr +| 11 = @conditionalexpr +| 12 = @newexpr +| 13 = @callexpr +| 14 = @dotexpr +| 15 = @indexexpr +| 16 = @negexpr +| 17 = @plusexpr +| 18 = @lognotexpr +| 19 = @bitnotexpr +| 20 = @typeofexpr +| 21 = @voidexpr +| 22 = @deleteexpr +| 23 = @eqexpr +| 24 = @neqexpr +| 25 = @eqqexpr +| 26 = @neqqexpr +| 27 = @ltexpr +| 28 = @leexpr +| 29 = @gtexpr +| 30 = @geexpr +| 31 = @lshiftexpr +| 32 = @rshiftexpr +| 33 = @urshiftexpr +| 34 = @addexpr +| 35 = @subexpr +| 36 = @mulexpr +| 37 = @divexpr +| 38 = @modexpr +| 39 = @bitorexpr +| 40 = @xorexpr +| 41 = @bitandexpr +| 42 = @inexpr +| 43 = @instanceofexpr +| 44 = @logandexpr +| 45 = @logorexpr +| 47 = @assignexpr +| 48 = @assignaddexpr +| 49 = @assignsubexpr +| 50 = @assignmulexpr +| 51 = @assigndivexpr +| 52 = @assignmodexpr +| 53 = @assignlshiftexpr +| 54 = @assignrshiftexpr +| 55 = @assignurshiftexpr +| 56 = @assignorexpr +| 57 = @assignxorexpr +| 58 = @assignandexpr +| 59 = @preincexpr +| 60 = @postincexpr +| 61 = @predecexpr +| 62 = @postdecexpr +| 63 = @parexpr +| 64 = @vardeclarator +| 65 = @arrowfunctionexpr +| 66 = @spreadelement +| 67 = @arraypattern +| 68 = @objectpattern +| 69 = @yieldexpr +| 70 = @taggedtemplateexpr +| 71 = @templateliteral +| 72 = @templateelement +| 73 = @arraycomprehensionexpr +| 74 = @generatorexpr +| 75 = @forincomprehensionblock +| 76 = @forofcomprehensionblock +| 77 = @legacy_letexpr +| 78 = @vardecl +| 79 = @proper_varaccess +| 80 = @classexpr +| 81 = @superexpr +| 82 = @newtargetexpr +| 83 = @namedimportspecifier +| 84 = @importdefaultspecifier +| 85 = @importnamespacespecifier +| 86 = @namedexportspecifier +| 87 = @expexpr +| 88 = @assignexpexpr +| 89 = @jsxelement +| 90 = @jsxqualifiedname +| 91 = @jsxemptyexpr +| 92 = @awaitexpr +| 93 = @functionsentexpr +| 94 = @decorator +| 95 = @exportdefaultspecifier +| 96 = @exportnamespacespecifier +| 97 = @bindexpr +| 98 = @externalmodulereference +| 99 = @dynamicimport +| 100 = @expressionwithtypearguments +| 101 = @prefixtypeassertion +| 102 = @astypeassertion +| 103 = @export_varaccess +| 104 = @decorator_list +| 105 = @non_null_assertion +| 106 = @bigintliteral +| 107 = @nullishcoalescingexpr +| 108 = @e4x_xml_anyname +| 109 = @e4x_xml_static_attribute_selector +| 110 = @e4x_xml_dynamic_attribute_selector +| 111 = @e4x_xml_filter_expression +| 112 = @e4x_xml_static_qualident +| 113 = @e4x_xml_dynamic_qualident +| 114 = @e4x_xml_dotdotexpr +| 115 = @importmetaexpr +; + +@varaccess = @proper_varaccess | @export_varaccess; +@varref = @vardecl | @varaccess; + +@identifier = @label | @varref | @typeidentifier; + +@literal = @nullliteral | @booleanliteral | @numberliteral | @stringliteral | @regexpliteral | @bigintliteral; + +@propaccess = @dotexpr | @indexexpr; + +@invokeexpr = @newexpr | @callexpr; + +@unaryexpr = @negexpr | @plusexpr | @lognotexpr | @bitnotexpr | @typeofexpr | @voidexpr | @deleteexpr | @spreadelement; + +@equalitytest = @eqexpr | @neqexpr | @eqqexpr | @neqqexpr; + +@comparison = @equalitytest | @ltexpr | @leexpr | @gtexpr | @geexpr; + +@binaryexpr = @comparison | @lshiftexpr | @rshiftexpr | @urshiftexpr | @addexpr | @subexpr | @mulexpr | @divexpr | @modexpr | @expexpr | @bitorexpr | @xorexpr | @bitandexpr | @inexpr | @instanceofexpr | @logandexpr | @logorexpr | @nullishcoalescingexpr; + +@assignment = @assignexpr | @assignaddexpr | @assignsubexpr | @assignmulexpr | @assigndivexpr | @assignmodexpr | @assignexpexpr | @assignlshiftexpr | @assignrshiftexpr | @assignurshiftexpr | @assignorexpr | @assignxorexpr | @assignandexpr; + +@updateexpr = @preincexpr | @postincexpr | @predecexpr | @postdecexpr; + +@pattern = @varref | @arraypattern | @objectpattern; + +@comprehensionexpr = @arraycomprehensionexpr | @generatorexpr; + +@comprehensionblock = @forincomprehensionblock | @forofcomprehensionblock; + +@importspecifier = @namedimportspecifier | @importdefaultspecifier | @importnamespacespecifier; + +@exportspecifier = @namedexportspecifier | @exportdefaultspecifier | @exportnamespacespecifier; + +@typeassertion = @astypeassertion | @prefixtypeassertion; + +@classdefinition = @classdeclstmt | @classexpr; +@interfacedefinition = @interfacedeclaration | @interfacetypeexpr; +@classorinterface = @classdefinition | @interfacedefinition; + +@lexical_decl = @vardecl | @typedecl; +@lexical_access = @varaccess | @localtypeaccess | @localvartypeaccess | @localnamespaceaccess; +@lexical_ref = @lexical_decl | @lexical_access; + +@e4x_xml_attribute_selector = @e4x_xml_static_attribute_selector | @e4x_xml_dynamic_attribute_selector; +@e4x_xml_qualident = @e4x_xml_static_qualident | @e4x_xml_dynamic_qualident; + +// scopes +scopes (unique int id: @scope, + int kind: int ref); + +case @scope.kind of + 0 = @globalscope +| 1 = @functionscope +| 2 = @catchscope +| 3 = @modulescope +| 4 = @blockscope +| 5 = @forscope +| 6 = @forinscope // for-of scopes work the same as for-in scopes +| 7 = @comprehensionblockscope +| 8 = @classexprscope +| 9 = @namespacescope +| 10 = @classdeclscope +| 11 = @interfacescope +| 12 = @typealiasscope +| 13 = @mappedtypescope +| 14 = @enumscope +| 15 = @externalmodulescope +| 16 = @conditionaltypescope; + +scopenodes (unique int node: @ast_node ref, + int scope: @scope ref); + +scopenesting (unique int inner: @scope ref, + int outer: @scope ref); + +// functions +@function = @functiondeclstmt | @functionexpr | @arrowfunctionexpr; + +@parameterized = @function | @catchclause; +@type_parameterized = @function | @classorinterface | @typealiasdeclaration | @mappedtypeexpr | @infertypeexpr; + +isGenerator (int fun: @function ref); +hasRestParameter (int fun: @function ref); +isAsync (int fun: @function ref); + +// variables and lexically scoped type names +#keyset[scope, name] +variables (unique int id: @variable, + varchar(900) name: string ref, + int scope: @scope ref); + +#keyset[scope, name] +local_type_names (unique int id: @local_type_name, + varchar(900) name: string ref, + int scope: @scope ref); + +#keyset[scope, name] +local_namespace_names (unique int id: @local_namespace_name, + varchar(900) name: string ref, + int scope: @scope ref); + +isArgumentsObject (int id: @variable ref); + +@lexical_name = @variable | @local_type_name | @local_namespace_name; + +@bind_id = @varaccess | @localvartypeaccess; +bind (unique int id: @bind_id ref, + int decl: @variable ref); + +decl (unique int id: @vardecl ref, + int decl: @variable ref); + +@typebind_id = @localtypeaccess | @export_varaccess; +typebind (unique int id: @typebind_id ref, + int decl: @local_type_name ref); + +@typedecl_id = @typedecl | @vardecl; +typedecl (unique int id: @typedecl_id ref, + int decl: @local_type_name ref); + +namespacedecl (unique int id: @vardecl ref, + int decl: @local_namespace_name ref); + +@namespacebind_id = @localnamespaceaccess | @export_varaccess; +namespacebind (unique int id: @namespacebind_id ref, + int decl: @local_namespace_name ref); + + +// properties in object literals, property patterns in object patterns, and method declarations in classes +#keyset[parent, index] +properties (unique int id: @property, + int parent: @property_parent ref, + int index: int ref, + int kind: int ref, + varchar(900) tostring: string ref); + +case @property.kind of + 0 = @value_property +| 1 = @property_getter +| 2 = @property_setter +| 3 = @jsx_attribute +| 4 = @function_call_signature +| 5 = @constructor_call_signature +| 6 = @index_signature +| 7 = @enum_member +| 8 = @proper_field +| 9 = @parameter_field +; + +@property_parent = @objexpr | @objectpattern | @classdefinition | @jsxelement | @interfacedefinition | @enumdeclaration; +@property_accessor = @property_getter | @property_setter; +@call_signature = @function_call_signature | @constructor_call_signature; +@field = @proper_field | @parameter_field; +@field_or_vardeclarator = @field | @vardeclarator; + +isComputed (int id: @property ref); +isMethod (int id: @property ref); +isStatic (int id: @property ref); +isAbstractMember (int id: @property ref); +isConstEnum (int id: @enumdeclaration ref); +isAbstractClass (int id: @classdeclstmt ref); + +hasPublicKeyword (int id: @property ref); +hasPrivateKeyword (int id: @property ref); +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( + unique int field: @parameter_field ref, + int constructor: @functionexpr ref, + int param_index: int ref +); + +// types +#keyset[parent, idx] +typeexprs ( + unique int id: @typeexpr, + int kind: int ref, + int parent: @typeexpr_parent ref, + int idx: int ref, + varchar(900) tostring: string ref +); + +case @typeexpr.kind of + 0 = @localtypeaccess +| 1 = @typedecl +| 2 = @keywordtypeexpr +| 3 = @stringliteraltypeexpr +| 4 = @numberliteraltypeexpr +| 5 = @booleanliteraltypeexpr +| 6 = @arraytypeexpr +| 7 = @uniontypeexpr +| 8 = @indexedaccesstypeexpr +| 9 = @intersectiontypeexpr +| 10 = @parenthesizedtypeexpr +| 11 = @tupletypeexpr +| 12 = @keyoftypeexpr +| 13 = @qualifiedtypeaccess +| 14 = @generictypeexpr +| 15 = @typelabel +| 16 = @typeoftypeexpr +| 17 = @localvartypeaccess +| 18 = @qualifiedvartypeaccess +| 19 = @thisvartypeaccess +| 20 = @predicatetypeexpr +| 21 = @interfacetypeexpr +| 22 = @typeparameter +| 23 = @plainfunctiontypeexpr +| 24 = @constructortypeexpr +| 25 = @localnamespaceaccess +| 26 = @qualifiednamespaceaccess +| 27 = @mappedtypeexpr +| 28 = @conditionaltypeexpr +| 29 = @infertypeexpr +| 30 = @importtypeaccess +| 31 = @importnamespaceaccess +| 32 = @importvartypeaccess +| 33 = @optionaltypeexpr +| 34 = @resttypeexpr +| 35 = @bigintliteraltypeexpr +| 36 = @readonlytypeexpr +; + +@typeref = @typeaccess | @typedecl; +@typeidentifier = @typedecl | @localtypeaccess | @typelabel | @localvartypeaccess | @localnamespaceaccess; +@typeexpr_parent = @expr | @stmt | @property | @typeexpr; +@literaltypeexpr = @stringliteraltypeexpr | @numberliteraltypeexpr | @booleanliteraltypeexpr | @bigintliteraltypeexpr; +@typeaccess = @localtypeaccess | @qualifiedtypeaccess | @importtypeaccess; +@vartypeaccess = @localvartypeaccess | @qualifiedvartypeaccess | @thisvartypeaccess | @importvartypeaccess; +@namespaceaccess = @localnamespaceaccess | @qualifiednamespaceaccess | @importnamespaceaccess; +@importtypeexpr = @importtypeaccess | @importnamespaceaccess | @importvartypeaccess; + +@functiontypeexpr = @plainfunctiontypeexpr | @constructortypeexpr; + +// types +types ( + unique int id: @type, + int kind: int ref, + varchar(900) tostring: string ref +); + +#keyset[parent, idx] +type_child ( + int child: @type ref, + int parent: @type ref, + int idx: int ref +); + +case @type.kind of + 0 = @anytype +| 1 = @stringtype +| 2 = @numbertype +| 3 = @uniontype +| 4 = @truetype +| 5 = @falsetype +| 6 = @typereference +| 7 = @objecttype +| 8 = @canonicaltypevariabletype +| 9 = @typeoftype +| 10 = @voidtype +| 11 = @undefinedtype +| 12 = @nulltype +| 13 = @nevertype +| 14 = @plainsymboltype +| 15 = @uniquesymboltype +| 16 = @objectkeywordtype +| 17 = @intersectiontype +| 18 = @tupletype +| 19 = @lexicaltypevariabletype +| 20 = @thistype +| 21 = @numberliteraltype +| 22 = @stringliteraltype +| 23 = @unknowntype +| 24 = @biginttype +| 25 = @bigintliteraltype +; + +@booleanliteraltype = @truetype | @falsetype; +@symboltype = @plainsymboltype | @uniquesymboltype; +@unionorintersectiontype = @uniontype | @intersectiontype; +@typevariabletype = @canonicaltypevariabletype | @lexicaltypevariabletype; + +hasAssertsKeyword(int node: @predicatetypeexpr ref); + +@typed_ast_node = @expr | @typeexpr | @function; +ast_node_type( + unique int node: @typed_ast_node ref, + int typ: @type ref); + +declared_function_signature( + unique int node: @function ref, + int sig: @signature_type ref +); + +invoke_expr_signature( + unique int node: @invokeexpr ref, + int sig: @signature_type ref +); + +invoke_expr_overload_index( + unique int node: @invokeexpr ref, + int index: int ref +); + +symbols ( + unique int id: @symbol, + int kind: int ref, + varchar(900) name: string ref +); + +symbol_parent ( + unique int symbol: @symbol ref, + int parent: @symbol ref +); + +symbol_module ( + int symbol: @symbol ref, + varchar(900) moduleName: string ref +); + +symbol_global ( + int symbol: @symbol ref, + varchar(900) globalName: string ref +); + +case @symbol.kind of + 0 = @root_symbol +| 1 = @member_symbol +| 2 = @other_symbol +; + +@type_with_symbol = @typereference | @typevariabletype | @typeoftype | @uniquesymboltype; +@ast_node_with_symbol = @typedefinition | @namespacedefinition | @toplevel | @typeaccess | @namespaceaccess | @vardecl | @function | @invokeexpr | @importdeclaration | @externalmodulereference; + +ast_node_symbol( + unique int node: @ast_node_with_symbol ref, + int symbol: @symbol ref); + +type_symbol( + unique int typ: @type_with_symbol ref, + int symbol: @symbol ref); + +#keyset[typ, name] +type_property( + int typ: @type ref, + 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( + unique int typ: @type_with_literal_value ref, + varchar(900) value: string ref); + +signature_types ( + unique int id: @signature_type, + int kind: int ref, + varchar(900) tostring: string ref, + int type_parameters: int ref, + 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 +; + +#keyset[typ, kind, index] +type_contains_signature ( + int typ: @type ref, + int kind: int ref, // constructor/call/index + int index: int ref, // ordering of overloaded signatures + int sig: @signature_type ref +); + +#keyset[parent, index] +signature_contains_type ( + int child: @type ref, + int parent: @signature_type ref, + int index: int ref +); + +#keyset[sig, index] +signature_parameter_name ( + int sig: @signature_type ref, + int index: int ref, + varchar(900) name: string ref +); + +number_index_type ( + unique int baseType: @type ref, + int propertyType: @type ref +); + +string_index_type ( + unique int baseType: @type ref, + int propertyType: @type ref +); + +base_type_names( + int typeName: @symbol ref, + int baseTypeName: @symbol ref +); + +self_types( + int typeName: @symbol ref, + int selfType: @typereference ref +); + +tuple_type_min_length( + unique int typ: @type ref, + int minLength: int ref +); + +tuple_type_rest( + unique int typ: @type ref +); + +// comments +comments (unique int id: @comment, + int kind: int ref, + int toplevel: @toplevel ref, + varchar(900) text: string ref, + varchar(900) tostring: string ref); + +case @comment.kind of + 0 = @slashslashcomment +| 1 = @slashstarcomment +| 2 = @doccomment +| 3 = @htmlcommentstart +| 4 = @htmlcommentend; + +@htmlcomment = @htmlcommentstart | @htmlcommentend; +@linecomment = @slashslashcomment | @htmlcomment; +@blockcomment = @slashstarcomment | @doccomment; + +// source lines +lines (unique int id: @line, + int toplevel: @toplevel ref, + varchar(900) text: string ref, + varchar(2) terminator: string ref); +indentation (int file: @file ref, + int lineno: int ref, + varchar(1) indentChar: string ref, + int indentDepth: int ref); + +// JavaScript parse errors +jsParseErrors (unique int id: @js_parse_error, + int toplevel: @toplevel ref, + varchar(900) message: string ref, + varchar(900) line: string ref); + +// regular expressions +#keyset[parent, idx] +regexpterm (unique int id: @regexpterm, + int kind: int ref, + int parent: @regexpparent ref, + int idx: int ref, + varchar(900) tostring: string ref); + +@regexpparent = @regexpterm | @regexpliteral | @stringliteral; + +case @regexpterm.kind of + 0 = @regexp_alt +| 1 = @regexp_seq +| 2 = @regexp_caret +| 3 = @regexp_dollar +| 4 = @regexp_wordboundary +| 5 = @regexp_nonwordboundary +| 6 = @regexp_positive_lookahead +| 7 = @regexp_negative_lookahead +| 8 = @regexp_star +| 9 = @regexp_plus +| 10 = @regexp_opt +| 11 = @regexp_range +| 12 = @regexp_dot +| 13 = @regexp_group +| 14 = @regexp_normal_constant +| 15 = @regexp_hex_escape +| 16 = @regexp_unicode_escape +| 17 = @regexp_dec_escape +| 18 = @regexp_oct_escape +| 19 = @regexp_ctrl_escape +| 20 = @regexp_char_class_escape +| 21 = @regexp_id_escape +| 22 = @regexp_backref +| 23 = @regexp_char_class +| 24 = @regexp_char_range +| 25 = @regexp_positive_lookbehind +| 26 = @regexp_negative_lookbehind +| 27 = @regexp_unicode_property_escape; + +regexpParseErrors (unique int id: @regexp_parse_error, + int regexp: @regexpterm ref, + varchar(900) message: string ref); + +@regexp_quantifier = @regexp_star | @regexp_plus | @regexp_opt | @regexp_range; +@regexp_escape = @regexp_char_escape | @regexp_char_class_escape | @regexp_unicode_property_escape; +@regexp_char_escape = @regexp_hex_escape | @regexp_unicode_escape | @regexp_dec_escape | @regexp_oct_escape | @regexp_ctrl_escape | @regexp_id_escape; +@regexp_constant = @regexp_normal_constant | @regexp_char_escape; +@regexp_lookahead = @regexp_positive_lookahead | @regexp_negative_lookahead; +@regexp_lookbehind = @regexp_positive_lookbehind | @regexp_negative_lookbehind; +@regexp_subpattern = @regexp_lookahead | @regexp_lookbehind; +@regexp_anchor = @regexp_dollar | @regexp_caret; + +isGreedy (int id: @regexp_quantifier ref); +rangeQuantifierLowerBound (unique int id: @regexp_range ref, int lo: int ref); +rangeQuantifierUpperBound (unique int id: @regexp_range ref, int hi: int ref); +isCapture (unique int id: @regexp_group ref, int number: int ref); +isNamedCapture (unique int id: @regexp_group ref, string name: string ref); +isInverted (int id: @regexp_char_class ref); +regexpConstValue (unique int id: @regexp_constant ref, varchar(1) value: string ref); +charClassEscape (unique int id: @regexp_char_class_escape ref, varchar(1) value: string ref); +backref (unique int id: @regexp_backref ref, int value: int ref); +namedBackref (unique int id: @regexp_backref ref, string name: string ref); +unicodePropertyEscapeName (unique int id: @regexp_unicode_property_escape ref, string name: string ref); +unicodePropertyEscapeValue (unique int id: @regexp_unicode_property_escape ref, string value: string ref); + +// tokens +#keyset[toplevel, idx] +tokeninfo (unique int id: @token, + int kind: int ref, + int toplevel: @toplevel ref, + int idx: int ref, + varchar(900) value: string ref); + +case @token.kind of + 0 = @token_eof +| 1 = @token_null_literal +| 2 = @token_boolean_literal +| 3 = @token_numeric_literal +| 4 = @token_string_literal +| 5 = @token_regular_expression +| 6 = @token_identifier +| 7 = @token_keyword +| 8 = @token_punctuator; + +// associate comments with the token immediately following them (which may be EOF) +next_token (int comment: @comment ref, int token: @token ref); + +// JSON +#keyset[parent, idx] +json (unique int id: @json_value, + int kind: int ref, + int parent: @json_parent ref, + int idx: int ref, + varchar(900) tostring: string ref); + +json_literals (varchar(900) value: string ref, + varchar(900) raw: string ref, + unique int expr: @json_value ref); + +json_properties (int obj: @json_object ref, + varchar(900) property: string ref, + int value: @json_value ref); + +json_errors (unique int id: @json_parse_error, + varchar(900) message: string ref); + +json_locations(unique int locatable: @json_locatable ref, + int location: @location_default ref); + +case @json_value.kind of + 0 = @json_null +| 1 = @json_boolean +| 2 = @json_number +| 3 = @json_string +| 4 = @json_array +| 5 = @json_object; + +@json_parent = @json_object | @json_array | @file; + +@json_locatable = @json_value | @json_parse_error; + +// locations +@ast_node = @toplevel | @stmt | @expr | @property | @typeexpr; + +@locatable = @file + | @ast_node + | @comment + | @line + | @js_parse_error | @regexp_parse_error + | @regexpterm + | @json_locatable + | @token + | @cfg_node + | @jsdoc | @jsdoc_type_expr | @jsdoc_tag + | @yaml_locatable + | @xmllocatable + | @configLocatable; + +hasLocation (unique int locatable: @locatable ref, + int location: @location ref); + +// CFG +entry_cfg_node (unique int id: @entry_node, int container: @stmt_container ref); +exit_cfg_node (unique int id: @exit_node, int container: @stmt_container ref); +guard_node (unique int id: @guard_node, int kind: int ref, int test: @expr ref); +case @guard_node.kind of + 0 = @falsy_guard +| 1 = @truthy_guard; +@condition_guard = @falsy_guard | @truthy_guard; + +@synthetic_cfg_node = @entry_node | @exit_node | @guard_node; +@cfg_node = @synthetic_cfg_node | @exprparent; + +successor (int pred: @cfg_node ref, int succ: @cfg_node ref); + +// JSDoc comments +jsdoc (unique int id: @jsdoc, varchar(900) description: string ref, int comment: @comment ref); +#keyset[parent, idx] +jsdoc_tags (unique int id: @jsdoc_tag, varchar(900) title: string ref, + int parent: @jsdoc ref, int idx: int ref, varchar(900) tostring: string ref); +jsdoc_tag_descriptions (unique int tag: @jsdoc_tag ref, varchar(900) text: string ref); +jsdoc_tag_names (unique int tag: @jsdoc_tag ref, varchar(900) text: string ref); + +#keyset[parent, idx] +jsdoc_type_exprs (unique int id: @jsdoc_type_expr, + int kind: int ref, + int parent: @jsdoc_type_expr_parent ref, + int idx: int ref, + varchar(900) tostring: string ref); +case @jsdoc_type_expr.kind of + 0 = @jsdoc_any_type_expr +| 1 = @jsdoc_null_type_expr +| 2 = @jsdoc_undefined_type_expr +| 3 = @jsdoc_unknown_type_expr +| 4 = @jsdoc_void_type_expr +| 5 = @jsdoc_named_type_expr +| 6 = @jsdoc_applied_type_expr +| 7 = @jsdoc_nullable_type_expr +| 8 = @jsdoc_non_nullable_type_expr +| 9 = @jsdoc_record_type_expr +| 10 = @jsdoc_array_type_expr +| 11 = @jsdoc_union_type_expr +| 12 = @jsdoc_function_type_expr +| 13 = @jsdoc_optional_type_expr +| 14 = @jsdoc_rest_type_expr +; + +#keyset[id, idx] +jsdoc_record_field_name (int id: @jsdoc_record_type_expr ref, int idx: int ref, varchar(900) name: string ref); +jsdoc_prefix_qualifier (int id: @jsdoc_type_expr ref); +jsdoc_has_new_parameter (int fn: @jsdoc_function_type_expr ref); + +@jsdoc_type_expr_parent = @jsdoc_type_expr | @jsdoc_tag; + +jsdoc_errors (unique int id: @jsdoc_error, int tag: @jsdoc_tag ref, varchar(900) message: string ref, varchar(900) tostring: string ref); + +// YAML +#keyset[parent, idx] +yaml (unique int id: @yaml_node, + int kind: int ref, + int parent: @yaml_node_parent ref, + int idx: int ref, + varchar(900) tag: string ref, + varchar(900) tostring: string ref); + +case @yaml_node.kind of + 0 = @yaml_scalar_node +| 1 = @yaml_mapping_node +| 2 = @yaml_sequence_node +| 3 = @yaml_alias_node +; + +@yaml_collection_node = @yaml_mapping_node | @yaml_sequence_node; + +@yaml_node_parent = @yaml_collection_node | @file; + +yaml_anchors (unique int node: @yaml_node ref, + varchar(900) anchor: string ref); + +yaml_aliases (unique int alias: @yaml_alias_node ref, + varchar(900) target: string ref); + +yaml_scalars (unique int scalar: @yaml_scalar_node ref, + int style: int ref, + varchar(900) value: string ref); + +yaml_errors (unique int id: @yaml_error, + varchar(900) message: string ref); + +yaml_locations(unique int locatable: @yaml_locatable ref, + int location: @location_default ref); + +@yaml_locatable = @yaml_node | @yaml_error; + +/* XML Files */ + +xmlEncoding( + unique int id: @file ref, + varchar(900) encoding: string ref +); + +xmlDTDs( + unique int id: @xmldtd, + varchar(900) root: string ref, + varchar(900) publicId: string ref, + varchar(900) systemId: string ref, + int fileid: @file ref +); + +xmlElements( + unique int id: @xmlelement, + varchar(900) name: string ref, + int parentid: @xmlparent ref, + int idx: int ref, + int fileid: @file ref +); + +xmlAttrs( + unique int id: @xmlattribute, + int elementid: @xmlelement ref, + varchar(900) name: string ref, + varchar(3600) value: string ref, + int idx: int ref, + int fileid: @file ref +); + +xmlNs( + int id: @xmlnamespace, + varchar(900) prefixName: string ref, + varchar(900) URI: string ref, + int fileid: @file ref +); + +xmlHasNs( + int elementId: @xmlnamespaceable ref, + int nsId: @xmlnamespace ref, + int fileid: @file ref +); + +xmlComments( + unique int id: @xmlcomment, + varchar(3600) text: string ref, + int parentid: @xmlparent ref, + int fileid: @file ref +); + +xmlChars( + unique int id: @xmlcharacters, + varchar(3600) text: string ref, + int parentid: @xmlparent ref, + int idx: int ref, + int isCDATA: int ref, + int fileid: @file ref +); + +@xmlparent = @file | @xmlelement; +@xmlnamespaceable = @xmlelement | @xmlattribute; + +xmllocations( + int xmlElement: @xmllocatable ref, + int location: @location_default ref +); + +@xmllocatable = @xmlcharacters | @xmlelement | @xmlcomment | @xmlattribute | @xmldtd | @file | @xmlnamespace; + +@dataflownode = @expr | @functiondeclstmt | @classdeclstmt | @namespacedeclaration | @enumdeclaration | @property; + +@optionalchainable = @callexpr | @propaccess; + +isOptionalChaining(int id: @optionalchainable ref); + +/* + * configuration files with key value pairs + */ + +configs( + unique int id: @config +); + +configNames( + unique int id: @configName, + int config: @config ref, + string name: string ref +); + +configValues( + unique int id: @configValue, + int config: @config ref, + string value: string ref +); + +configLocations( + int locatable: @configLocatable ref, + int location: @location_default ref +); + +@configLocatable = @config | @configName | @configValue; + +/** + * The time taken for the extraction of a file. + * This table contains non-deterministic content. + * + * The sum of the `time` column for each (`file`, `timerKind`) pair + * is the total time taken for extraction of `file`. The `extractionPhase` + * column provides a granular view of the extraction time of the file. + */ +extraction_time( + int file : @file ref, + // see `com.semmle.js.extractor.ExtractionMetrics.ExtractionPhase`. + int extractionPhase: int ref, + // 0 for the elapsed CPU time in nanoseconds, 1 for the elapsed wallclock time in nanoseconds + int timerKind: int ref, + float time: float ref +) + +/** + * Non-timing related data for the extraction of a single file. + * This table contains non-deterministic content. + */ +extraction_data( + int file : @file ref, + // the absolute path to the cache file + varchar(900) cacheFile: string ref, + boolean fromCache: boolean ref, + int length: int ref +) diff --git a/javascript/upgrades/96b0a386b6fb8da2b5f2514f26154a10c906f9c5/upgrade.properties b/javascript/upgrades/96b0a386b6fb8da2b5f2514f26154a10c906f9c5/upgrade.properties new file mode 100644 index 00000000000..623175336d6 --- /dev/null +++ b/javascript/upgrades/96b0a386b6fb8da2b5f2514f26154a10c906f9c5/upgrade.properties @@ -0,0 +1,2 @@ +description: add TypeScript symbols to import declarations +compatibility: backwards diff --git a/python/ql/src/Imports/FromImportOfMutableAttribute.ql b/python/ql/src/Imports/FromImportOfMutableAttribute.ql index e5e7c96985e..aa66fd6d9b2 100644 --- a/python/ql/src/Imports/FromImportOfMutableAttribute.ql +++ b/python/ql/src/Imports/FromImportOfMutableAttribute.ql @@ -10,22 +10,25 @@ * @precision medium * @id py/import-of-mutable-attribute */ + import python import semmle.python.filters.Tests -from ImportMember im, ModuleObject m, AttrNode store_attr, string name -where im.getModule().(ImportExpr).getImportedModuleName() = m.getName() and -im.getName() = name and -/* Modification must be in a function, so it can occur during lifetime of the import value */ -store_attr.getScope() instanceof Function and -/* variable resulting from import must have a long lifetime */ -not im.getScope() instanceof Function and -store_attr.isStore() and -store_attr.getObject(name).refersTo(m) and -/* Import not in same module as modification. */ -not im.getEnclosingModule() = store_attr.getScope().getEnclosingModule() and -/* Modification is not in a test */ -not store_attr.getScope().getScope*() instanceof TestScope - -select im, "Importing the value of '" + name + "' from $@ means that any change made to $@ will be not be observed locally.", -m, "module " + m.getName(), store_attr, m.getName() + "." + store_attr.getName() +from ImportMember im, ModuleValue m, AttrNode store_attr, string name +where + m.importedAs(im.getModule().(ImportExpr).getImportedModuleName()) and + im.getName() = name and + /* Modification must be in a function, so it can occur during lifetime of the import value */ + store_attr.getScope() instanceof Function and + /* variable resulting from import must have a long lifetime */ + not im.getScope() instanceof Function and + store_attr.isStore() and + store_attr.getObject(name).pointsTo(m) and + /* Import not in same module as modification. */ + not im.getEnclosingModule() = store_attr.getScope().getEnclosingModule() and + /* Modification is not in a test */ + not store_attr.getScope().getScope*() instanceof TestScope +select im, + "Importing the value of '" + name + + "' from $@ means that any change made to $@ will be not be observed locally.", m, + "module " + m.getName(), store_attr, m.getName() + "." + store_attr.getName() diff --git a/python/ql/src/Imports/ModuleImportsItself.ql b/python/ql/src/Imports/ModuleImportsItself.ql index d07d79ed9a3..d305d9a3596 100644 --- a/python/ql/src/Imports/ModuleImportsItself.ql +++ b/python/ql/src/Imports/ModuleImportsItself.ql @@ -12,11 +12,11 @@ import python -predicate modules_imports_itself(Import i, ModuleObject m) { - i.getEnclosingModule() = m.getModule() and +predicate modules_imports_itself(Import i, ModuleValue m) { + i.getEnclosingModule() = m.getScope() and m.importedAs(i.getAnImportedModuleName()) } -from Import i, ModuleObject m +from Import i, ModuleValue m where modules_imports_itself(i, m) select i, "The module '" + m.getName() + "' imports itself." diff --git a/python/ql/src/Imports/UnintentionalImport.ql b/python/ql/src/Imports/UnintentionalImport.ql index 3815b04f64a..8e396896e95 100644 --- a/python/ql/src/Imports/UnintentionalImport.ql +++ b/python/ql/src/Imports/UnintentionalImport.ql @@ -13,20 +13,20 @@ import python -predicate import_star(ImportStar imp, ModuleObject exporter) { +predicate import_star(ImportStar imp, ModuleValue exporter) { exporter.importedAs(imp.getImportedModuleName()) } -predicate all_defined(ModuleObject exporter) { - exporter.isC() +predicate all_defined(ModuleValue exporter) { + exporter.isBuiltin() or - exporter.getModule().(ImportTimeScope).definesName("__all__") + exporter.getScope().(ImportTimeScope).definesName("__all__") or - exporter.getModule().getInitModule().(ImportTimeScope).definesName("__all__") + exporter.getScope().getInitModule().(ImportTimeScope).definesName("__all__") } -from ImportStar imp, ModuleObject exporter +from ImportStar imp, ModuleValue exporter where import_star(imp, exporter) and not all_defined(exporter) select imp, "Import pollutes the enclosing namespace, as the imported module $@ does not define '__all__'.", exporter, exporter.getName() diff --git a/python/ql/src/Metrics/DirectImports.ql b/python/ql/src/Metrics/DirectImports.ql index 1eeb7694879..ec9114cddd9 100644 --- a/python/ql/src/Metrics/DirectImports.ql +++ b/python/ql/src/Metrics/DirectImports.ql @@ -11,6 +11,6 @@ */ import python -from ModuleObject m, int n -where n = count(ModuleObject imp | imp = m.getAnImportedModule()) -select m.getModule(), n \ No newline at end of file +from ModuleValue m, int n +where n = count(ModuleValue imp | imp = m.getAnImportedModule()) +select m.getScope(), n diff --git a/python/ql/src/Metrics/TransitiveImports.ql b/python/ql/src/Metrics/TransitiveImports.ql index 11fe7ee8f7e..cea731388f9 100644 --- a/python/ql/src/Metrics/TransitiveImports.ql +++ b/python/ql/src/Metrics/TransitiveImports.ql @@ -11,6 +11,6 @@ */ import python -from ModuleObject m, int n -where n = count(ModuleObject imp | imp = m.getAnImportedModule+() and imp != m) -select m.getModule(), n \ No newline at end of file +from ModuleValue m, int n +where n = count(ModuleValue imp | imp = m.getAnImportedModule+() and imp != m) +select m.getScope(), n diff --git a/python/ql/src/semmle/python/essa/Essa.qll b/python/ql/src/semmle/python/essa/Essa.qll index cbc8df53a63..b7903d05981 100644 --- a/python/ql/src/semmle/python/essa/Essa.qll +++ b/python/ql/src/semmle/python/essa/Essa.qll @@ -50,8 +50,16 @@ class EssaVariable extends TEssaDefinition { * Note that this differs from `EssaVariable.getAUse()`. */ ControlFlowNode getASourceUse() { + exists(SsaSourceVariable var | + result = use_for_var(var) and + result = var.getASourceUse() + ) + } + + pragma[nomagic] + private ControlFlowNode use_for_var(SsaSourceVariable var) { result = this.getAUse() and - result = this.getSourceVariable().getASourceUse() + var = this.getSourceVariable() } /** Gets the scope of this variable. */ @@ -268,11 +276,16 @@ class PhiFunction extends EssaDefinition, TPhiFunction { not exists(this.inputEdgeRefinement(result)) } + pragma[noinline] + private SsaSourceVariable pred_var(BasicBlock pred) { + result = this.getSourceVariable() and + pred = this.nonPiInput() + } + /** Gets another definition of the same source variable that reaches this definition. */ private EssaDefinition reachingDefinition(BasicBlock pred) { result.getScope() = this.getScope() and - result.getSourceVariable() = this.getSourceVariable() and - pred = this.nonPiInput() and + result.getSourceVariable() = pred_var(pred) and result.reachesEndOfBlock(pred) } diff --git a/python/ql/src/semmle/python/objects/ObjectAPI.qll b/python/ql/src/semmle/python/objects/ObjectAPI.qll index 7884daa7b06..5fb15229b96 100644 --- a/python/ql/src/semmle/python/objects/ObjectAPI.qll +++ b/python/ql/src/semmle/python/objects/ObjectAPI.qll @@ -117,6 +117,11 @@ class Value extends TObject { my_class.getABaseType+() = other_class ) } + + /** Gets the boolean value of this value. */ + boolean booleanValue() { + result = this.(ObjectInternal).booleanValue() + } } /** Class representing modules in the Python program @@ -167,6 +172,11 @@ class ModuleValue extends Value { this.(ModuleObjectInternal).hasCompleteExportInfo() } + /** Get a module that this module imports */ + ModuleValue getAnImportedModule() { + result.importedAs(this.getScope().getAnImportedModuleName()) + } + } module Module { @@ -594,6 +604,22 @@ class TupleValue extends SequenceValue { } +/** A class representing strings, either present in the source as a literal, or +in a builtin as a value. */ + +class StringValue extends Value { + StringValue() { + this instanceof BytesObjectInternal or + this instanceof UnicodeObjectInternal + } + + string getText() { + result = this.(BytesObjectInternal).strValue() + or + result = this.(UnicodeObjectInternal).strValue() + } +} + /** A method-resolution-order sequence of classes */ class MRO extends TClassList { @@ -689,6 +715,15 @@ module ClassValue { result = TBuiltinClassObject(Builtin::special("unicode")) } + /** Get the `ClassValue` for the `str` class. This is `bytes` in Python 2, + and `str` in Python 3. */ + ClassValue str() { + if major_version() = 2 then + result = bytes() + else + result = unicode() + } + /** Get the `ClassValue` for the `classmethod` class. */ ClassValue classmethod() { result = TBuiltinClassObject(Builtin::special("ClassMethod")) diff --git a/python/ql/src/semmle/python/pointsto/PointsTo.qll b/python/ql/src/semmle/python/pointsto/PointsTo.qll index 7918d1ad2a0..c119a7b77cf 100644 --- a/python/ql/src/semmle/python/pointsto/PointsTo.qll +++ b/python/ql/src/semmle/python/pointsto/PointsTo.qll @@ -284,7 +284,7 @@ cached module PointsToInternal { ssa_definition_points_to(var.getDefinition(), context, value, origin) or exists(EssaVariable prev | - ssaShortCut(prev, var) and + ssaShortCut+(prev, var) and variablePointsTo(prev, context, value, origin) ) } @@ -305,10 +305,6 @@ cached module PointsToInternal { start = def.getInput() and end.getDefinition() = def ) - or - exists(EssaVariable mid | - ssaShortCut(start, mid) and ssaShortCut(mid, end) - ) } pragma [noinline] @@ -578,6 +574,13 @@ cached module PointsToInternal { ) or /* Undefined variable */ + undefined_variable(def, context, value, origin) + or + /* Builtin not defined in outer scope */ + builtin_not_in_outer_scope(def, context, value, origin) + } + + private predicate undefined_variable(ScopeEntryDefinition def, PointsToContext context, ObjectInternal value, ControlFlowNode origin) { exists(Scope scope | not def.getVariable().getName() = "__name__" and not def.getVariable().isMetaVariable() and @@ -587,8 +590,9 @@ cached module PointsToInternal { def.getSourceVariable() instanceof LocalVariable and (context.isImport() or context.isRuntime() or context.isMain()) ) and value = ObjectInternal::undefined() and origin = def.getDefiningNode() - or - /* Builtin not defined in outer scope */ + } + + private predicate builtin_not_in_outer_scope(ScopeEntryDefinition def, PointsToContext context, ObjectInternal value, ControlFlowNode origin) { exists(Module mod, GlobalVariable var | var = def.getSourceVariable() and mod = def.getScope().getEnclosingModule() and @@ -1113,8 +1117,17 @@ module InterProceduralPointsTo { * Transfer of values from the callsite to the callee, for enclosing variables, but not arguments/parameters. */ pragma [noinline] private predicate callsite_entry_value_transfer(EssaVariable caller_var, PointsToContext caller, ScopeEntryDefinition entry_def, PointsToContext callee) { - entry_def.getSourceVariable() = caller_var.getSourceVariable() and - callsite_calls_function(caller_var.getAUse(), caller, entry_def.getScope(), callee, _) + exists(ControlFlowNode use, SsaSourceVariable var | + var_and_use(caller_var, use, var) and + entry_def.getSourceVariable() = var and + callsite_calls_function(use, caller, entry_def.getScope(), callee, _) + ) + } + + pragma[nomagic] + private predicate var_and_use(EssaVariable caller_var, ControlFlowNode use, SsaSourceVariable var) { + use = caller_var.getAUse() and + var = caller_var.getSourceVariable() } /** Helper for `scope_entry_value_transfer`. */