diff --git a/change-notes/1.24/analysis-cpp.md b/change-notes/1.24/analysis-cpp.md index 77d27f425bc..b4024b73307 100644 --- a/change-notes/1.24/analysis-cpp.md +++ b/change-notes/1.24/analysis-cpp.md @@ -46,3 +46,5 @@ The following changes in version 1.24 affect C/C++ analysis in all applications. the following improvements: * The library now models data flow through `strdup` and similar functions. * The library now models data flow through formatting functions such as `sprintf`. +* The security pack taint tracking library (`semmle.code.cpp.security.TaintTracking`) uses a new intermediate representation. This provides a more precise analysis of pointers to stack variables and flow through parameters, improving the results of many security queries. +* The global value numbering library (`semmle.code.cpp.valuenumbering.GlobalValueNumbering`) uses a new intermediate representation to provide a more precise analysis of heap allocated memory and pointers to stack variables. diff --git a/change-notes/1.24/analysis-javascript.md b/change-notes/1.24/analysis-javascript.md index 47fb2dc590b..7525e7c3f04 100644 --- a/change-notes/1.24/analysis-javascript.md +++ b/change-notes/1.24/analysis-javascript.md @@ -2,6 +2,8 @@ ## General improvements +* TypeScript 3.8 is now supported. + * Alert suppression can now be done with single-line block comments (`/* ... */`) as well as line comments (`// ...`). * Imports with the `.js` extension can now be resolved to a TypeScript file, @@ -13,6 +15,10 @@ * The analysis of sanitizer guards has improved, leading to fewer false-positive results from the security queries. +* The call graph construction has been improved, leading to more results from the security queries: + - Calls can now be resolved to indirectly-defined class members in more cases. + - Calls through partial invocations such as `.bind` can now be resolved in more cases. + * Support for the following frameworks and libraries has been improved: - [Electron](https://electronjs.org/) - [Handlebars](https://www.npmjs.com/package/handlebars) diff --git a/config/identical-files.json b/config/identical-files.json index 0f40aa45a95..47b37455b8c 100644 --- a/config/identical-files.json +++ b/config/identical-files.json @@ -222,10 +222,12 @@ "cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/PrintSSA.qll", "csharp/ql/src/semmle/code/csharp/ir/implementation/unaliased_ssa/internal/PrintSSA.qll" ], - "C++ IR ValueNumberInternal": [ + "IR ValueNumberInternal": [ "cpp/ql/src/semmle/code/cpp/ir/implementation/raw/gvn/internal/ValueNumberingInternal.qll", "cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/gvn/internal/ValueNumberingInternal.qll", - "cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/gvn/internal/ValueNumberingInternal.qll" + "cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/gvn/internal/ValueNumberingInternal.qll", + "csharp/ql/src/semmle/code/csharp/ir/implementation/raw/gvn/internal/ValueNumberingInternal.qll", + "csharp/ql/src/semmle/code/csharp/ir/implementation/unaliased_ssa/gvn/internal/ValueNumberingInternal.qll" ], "C++ IR ValueNumber": [ "cpp/ql/src/semmle/code/cpp/ir/implementation/raw/gvn/ValueNumbering.qll", diff --git a/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowDispatch.qll b/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowDispatch.qll index 75b77771e84..154430794d4 100644 --- a/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowDispatch.qll +++ b/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowDispatch.qll @@ -1,7 +1,5 @@ private import cpp -Function viableImpl(Call call) { result = viableCallable(call) } - /** * Gets a function that might be called by `call`. */ diff --git a/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl.qll b/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl.qll index 86d44e4fcce..5eb98aefe4e 100644 --- a/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl.qll +++ b/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl.qll @@ -259,44 +259,47 @@ private ReturnPosition viableReturnPos(DataFlowCall call, ReturnKindExt kind) { /** * Holds if `node` is reachable from a source in the given configuration - * ignoring call contexts. + * taking simple call contexts into consideration. */ -private predicate nodeCandFwd1(Node node, Configuration config) { +private predicate nodeCandFwd1(Node node, boolean fromArg, Configuration config) { not fullBarrier(node, config) and ( - config.isSource(node) + config.isSource(node) and + fromArg = false or exists(Node mid | - nodeCandFwd1(mid, config) and + nodeCandFwd1(mid, fromArg, config) and localFlowStep(mid, node, config) ) or exists(Node mid | - nodeCandFwd1(mid, config) and + nodeCandFwd1(mid, fromArg, config) and additionalLocalFlowStep(mid, node, config) ) or exists(Node mid | nodeCandFwd1(mid, config) and - jumpStep(mid, node, config) + jumpStep(mid, node, config) and + fromArg = false ) or exists(Node mid | nodeCandFwd1(mid, config) and - additionalJumpStep(mid, node, config) + additionalJumpStep(mid, node, config) and + fromArg = false ) or // store exists(Node mid | useFieldFlow(config) and - nodeCandFwd1(mid, config) and + nodeCandFwd1(mid, fromArg, config) and storeDirect(mid, _, node) and not outBarrier(mid, config) ) or // read exists(Content f | - nodeCandFwd1Read(f, node, config) and + nodeCandFwd1Read(f, node, fromArg, config) and storeCandFwd1(f, config) and not inBarrier(node, config) ) @@ -304,30 +307,37 @@ private predicate nodeCandFwd1(Node node, Configuration config) { // flow into a callable exists(Node arg | nodeCandFwd1(arg, config) and - viableParamArg(_, node, arg) + viableParamArg(_, node, arg) and + fromArg = true ) or // flow out of a callable - exists(DataFlowCall call, ReturnPosition pos, ReturnKindExt kind | - nodeCandFwd1ReturnPosition(pos, config) and - pos = viableReturnPos(call, kind) and - node = kind.getAnOutNode(call) + exists(DataFlowCall call | + nodeCandFwd1Out(call, node, false, config) and + fromArg = false + or + nodeCandFwd1OutFromArg(call, node, config) and + flowOutCandFwd1(call, fromArg, config) ) ) } -pragma[noinline] -private predicate nodeCandFwd1ReturnPosition(ReturnPosition pos, Configuration config) { +private predicate nodeCandFwd1(Node node, Configuration config) { nodeCandFwd1(node, _, config) } + +pragma[nomagic] +private predicate nodeCandFwd1ReturnPosition( + ReturnPosition pos, boolean fromArg, Configuration config +) { exists(ReturnNodeExt ret | - nodeCandFwd1(ret, config) and + nodeCandFwd1(ret, fromArg, config) and getReturnPosition(ret) = pos ) } pragma[nomagic] -private predicate nodeCandFwd1Read(Content f, Node node, Configuration config) { +private predicate nodeCandFwd1Read(Content f, Node node, boolean fromArg, Configuration config) { exists(Node mid | - nodeCandFwd1(mid, config) and + nodeCandFwd1(mid, fromArg, config) and readDirect(mid, f, node) ) } @@ -335,7 +345,7 @@ private predicate nodeCandFwd1Read(Content f, Node node, Configuration config) { /** * Holds if `f` is the target of a store in the flow covered by `nodeCandFwd1`. */ -pragma[noinline] +pragma[nomagic] private predicate storeCandFwd1(Content f, Configuration config) { exists(Node mid, Node node | not fullBarrier(node, config) and @@ -345,71 +355,120 @@ private predicate storeCandFwd1(Content f, Configuration config) { ) } +pragma[nomagic] +private predicate nodeCandFwd1ReturnKind( + DataFlowCall call, ReturnKindExt kind, boolean fromArg, Configuration config +) { + exists(ReturnPosition pos | + nodeCandFwd1ReturnPosition(pos, fromArg, config) and + pos = viableReturnPos(call, kind) + ) +} + +pragma[nomagic] +private predicate nodeCandFwd1Out( + DataFlowCall call, Node node, boolean fromArg, Configuration config +) { + exists(ReturnKindExt kind | + nodeCandFwd1ReturnKind(call, kind, fromArg, config) and + node = kind.getAnOutNode(call) + ) +} + +pragma[nomagic] +private predicate nodeCandFwd1OutFromArg(DataFlowCall call, Node node, Configuration config) { + nodeCandFwd1Out(call, node, true, config) +} + +/** + * Holds if an argument to `call` is reached in the flow covered by `nodeCandFwd1`. + */ +pragma[nomagic] +private predicate flowOutCandFwd1(DataFlowCall call, boolean fromArg, Configuration config) { + exists(ArgumentNode arg | + nodeCandFwd1(arg, fromArg, config) and + viableParamArg(call, _, arg) + ) +} + bindingset[result, b] private boolean unbindBool(boolean b) { result != b.booleanNot() } /** * Holds if `node` is part of a path from a source to a sink in the given - * configuration ignoring call contexts. + * configuration taking simple call contexts into consideration. */ pragma[nomagic] -private predicate nodeCand1(Node node, Configuration config) { +private predicate nodeCand1(Node node, boolean toReturn, Configuration config) { + nodeCand1_0(node, toReturn, config) and + nodeCandFwd1(node, config) +} + +pragma[nomagic] +private predicate nodeCand1_0(Node node, boolean toReturn, Configuration config) { nodeCandFwd1(node, config) and - config.isSink(node) + config.isSink(node) and + toReturn = false or - nodeCandFwd1(node, unbind(config)) and - ( - exists(Node mid | - localFlowStep(node, mid, config) and - nodeCand1(mid, config) - ) + exists(Node mid | + localFlowStep(node, mid, config) and + nodeCand1(mid, toReturn, config) + ) + or + exists(Node mid | + additionalLocalFlowStep(node, mid, config) and + nodeCand1(mid, toReturn, config) + ) + or + exists(Node mid | + jumpStep(node, mid, config) and + nodeCand1(mid, _, config) and + toReturn = false + ) + or + exists(Node mid | + additionalJumpStep(node, mid, config) and + nodeCand1(mid, _, config) and + toReturn = false + ) + or + // store + exists(Content f | + nodeCand1Store(f, node, toReturn, config) and + readCand1(f, config) + ) + or + // read + exists(Node mid, Content f | + readDirect(node, f, mid) and + storeCandFwd1(f, unbind(config)) and + nodeCand1(mid, toReturn, config) + ) + or + // flow into a callable + exists(DataFlowCall call | + nodeCand1Arg(call, node, false, config) and + toReturn = false or - exists(Node mid | - additionalLocalFlowStep(node, mid, config) and - nodeCand1(mid, config) - ) - or - exists(Node mid | - jumpStep(node, mid, config) and - nodeCand1(mid, config) - ) - or - exists(Node mid | - additionalJumpStep(node, mid, config) and - nodeCand1(mid, config) - ) - or - // store - exists(Content f | - nodeCand1Store(f, node, config) and - readCand1(f, config) - ) - or - // read - exists(Node mid, Content f | - readDirect(node, f, mid) and - storeCandFwd1(f, unbind(config)) and - nodeCand1(mid, config) - ) - or - // flow into a callable - exists(Node param | - viableParamArg(_, param, node) and - nodeCand1(param, config) - ) - or - // flow out of a callable - exists(ReturnPosition pos | - nodeCand1ReturnPosition(pos, config) and - getReturnPosition(node) = pos - ) + nodeCand1ArgToReturn(call, node, config) and + flowInCand1(call, toReturn, config) + ) + or + // flow out of a callable + exists(ReturnPosition pos | + nodeCand1ReturnPosition(pos, config) and + getReturnPosition(node) = pos and + toReturn = true ) } -pragma[noinline] +pragma[nomagic] +private predicate nodeCand1(Node node, Configuration config) { nodeCand1(node, _, config) } + +pragma[nomagic] private predicate nodeCand1ReturnPosition(ReturnPosition pos, Configuration config) { exists(DataFlowCall call, ReturnKindExt kind, Node out | - nodeCand1(out, config) and + nodeCand1(out, _, config) and pos = viableReturnPos(call, kind) and out = kind.getAnOutNode(call) ) @@ -418,21 +477,21 @@ private predicate nodeCand1ReturnPosition(ReturnPosition pos, Configuration conf /** * Holds if `f` is the target of a read in the flow covered by `nodeCand1`. */ -pragma[noinline] +pragma[nomagic] private predicate readCand1(Content f, Configuration config) { exists(Node mid, Node node | useFieldFlow(config) and nodeCandFwd1(node, unbind(config)) and readDirect(node, f, mid) and storeCandFwd1(f, unbind(config)) and - nodeCand1(mid, config) + nodeCand1(mid, _, config) ) } pragma[nomagic] -private predicate nodeCand1Store(Content f, Node node, Configuration config) { +private predicate nodeCand1Store(Content f, Node node, boolean toReturn, Configuration config) { exists(Node mid | - nodeCand1(mid, config) and + nodeCand1(mid, toReturn, config) and storeCandFwd1(f, unbind(config)) and storeDirect(node, f, mid) ) @@ -444,11 +503,45 @@ private predicate nodeCand1Store(Content f, Node node, Configuration config) { */ private predicate readStoreCand1(Content f, Configuration conf) { readCand1(f, conf) and - nodeCand1Store(f, _, conf) + nodeCand1Store(f, _, _, conf) +} + +pragma[nomagic] +private predicate viableParamArgCandFwd1( + DataFlowCall call, ParameterNode p, ArgumentNode arg, Configuration config +) { + viableParamArg(call, p, arg) and + nodeCandFwd1(arg, config) +} + +pragma[nomagic] +private predicate nodeCand1Arg( + DataFlowCall call, ArgumentNode arg, boolean toReturn, Configuration config +) { + exists(ParameterNode p | + nodeCand1(p, toReturn, config) and + viableParamArgCandFwd1(call, p, arg, config) + ) +} + +pragma[nomagic] +private predicate nodeCand1ArgToReturn(DataFlowCall call, ArgumentNode arg, Configuration config) { + nodeCand1Arg(call, arg, true, config) +} + +/** + * Holds if an output from `call` is reached in the flow covered by `nodeCand1`. + */ +pragma[nomagic] +private predicate flowInCand1(DataFlowCall call, boolean toReturn, Configuration config) { + exists(Node out | + nodeCand1(out, toReturn, config) and + nodeCandFwd1OutFromArg(call, out, config) + ) } private predicate throughFlowNodeCand(Node node, Configuration config) { - nodeCand1(node, config) and + nodeCand1(node, true, config) and not fullBarrier(node, config) and not inBarrier(node, config) and not outBarrier(node, config) @@ -2257,13 +2350,12 @@ private class PathNodeSink extends PathNodeImpl, TPathNodeSink { * a callable is recorded by `cc`. */ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCtx sc, AccessPath ap) { - exists(LocalCallContext localCC, AccessPath ap0, Node midnode, Configuration conf | - midnode = mid.getNode() and - conf = mid.getConfiguration() and - cc = mid.getCallContext() and - sc = mid.getSummaryCtx() and - localCC = getLocalCallContext(cc, midnode.getEnclosingCallable()) and - ap0 = mid.getAp() + exists( + AccessPath ap0, Node midnode, Configuration conf, DataFlowCallable enclosing, + LocalCallContext localCC + | + pathIntoLocalStep(mid, midnode, cc, enclosing, sc, ap0, conf) and + localCC = getLocalCallContext(cc, enclosing) | localFlowBigStep(midnode, node, true, conf, localCC) and ap = ap0 @@ -2297,6 +2389,20 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt pathThroughCallable(mid, node, cc, ap) and sc = mid.getSummaryCtx() } +pragma[nomagic] +private predicate pathIntoLocalStep( + PathNodeMid mid, Node midnode, CallContext cc, DataFlowCallable enclosing, SummaryCtx sc, + AccessPath ap0, Configuration conf +) { + midnode = mid.getNode() and + cc = mid.getCallContext() and + conf = mid.getConfiguration() and + localFlowBigStep(midnode, _, _, conf, _) and + enclosing = midnode.getEnclosingCallable() and + sc = mid.getSummaryCtx() and + ap0 = mid.getAp() +} + pragma[nomagic] private predicate readCand(Node node1, Content f, Node node2, Configuration config) { readDirect(node1, f, node2) and diff --git a/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl2.qll b/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl2.qll index 86d44e4fcce..5eb98aefe4e 100644 --- a/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl2.qll +++ b/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl2.qll @@ -259,44 +259,47 @@ private ReturnPosition viableReturnPos(DataFlowCall call, ReturnKindExt kind) { /** * Holds if `node` is reachable from a source in the given configuration - * ignoring call contexts. + * taking simple call contexts into consideration. */ -private predicate nodeCandFwd1(Node node, Configuration config) { +private predicate nodeCandFwd1(Node node, boolean fromArg, Configuration config) { not fullBarrier(node, config) and ( - config.isSource(node) + config.isSource(node) and + fromArg = false or exists(Node mid | - nodeCandFwd1(mid, config) and + nodeCandFwd1(mid, fromArg, config) and localFlowStep(mid, node, config) ) or exists(Node mid | - nodeCandFwd1(mid, config) and + nodeCandFwd1(mid, fromArg, config) and additionalLocalFlowStep(mid, node, config) ) or exists(Node mid | nodeCandFwd1(mid, config) and - jumpStep(mid, node, config) + jumpStep(mid, node, config) and + fromArg = false ) or exists(Node mid | nodeCandFwd1(mid, config) and - additionalJumpStep(mid, node, config) + additionalJumpStep(mid, node, config) and + fromArg = false ) or // store exists(Node mid | useFieldFlow(config) and - nodeCandFwd1(mid, config) and + nodeCandFwd1(mid, fromArg, config) and storeDirect(mid, _, node) and not outBarrier(mid, config) ) or // read exists(Content f | - nodeCandFwd1Read(f, node, config) and + nodeCandFwd1Read(f, node, fromArg, config) and storeCandFwd1(f, config) and not inBarrier(node, config) ) @@ -304,30 +307,37 @@ private predicate nodeCandFwd1(Node node, Configuration config) { // flow into a callable exists(Node arg | nodeCandFwd1(arg, config) and - viableParamArg(_, node, arg) + viableParamArg(_, node, arg) and + fromArg = true ) or // flow out of a callable - exists(DataFlowCall call, ReturnPosition pos, ReturnKindExt kind | - nodeCandFwd1ReturnPosition(pos, config) and - pos = viableReturnPos(call, kind) and - node = kind.getAnOutNode(call) + exists(DataFlowCall call | + nodeCandFwd1Out(call, node, false, config) and + fromArg = false + or + nodeCandFwd1OutFromArg(call, node, config) and + flowOutCandFwd1(call, fromArg, config) ) ) } -pragma[noinline] -private predicate nodeCandFwd1ReturnPosition(ReturnPosition pos, Configuration config) { +private predicate nodeCandFwd1(Node node, Configuration config) { nodeCandFwd1(node, _, config) } + +pragma[nomagic] +private predicate nodeCandFwd1ReturnPosition( + ReturnPosition pos, boolean fromArg, Configuration config +) { exists(ReturnNodeExt ret | - nodeCandFwd1(ret, config) and + nodeCandFwd1(ret, fromArg, config) and getReturnPosition(ret) = pos ) } pragma[nomagic] -private predicate nodeCandFwd1Read(Content f, Node node, Configuration config) { +private predicate nodeCandFwd1Read(Content f, Node node, boolean fromArg, Configuration config) { exists(Node mid | - nodeCandFwd1(mid, config) and + nodeCandFwd1(mid, fromArg, config) and readDirect(mid, f, node) ) } @@ -335,7 +345,7 @@ private predicate nodeCandFwd1Read(Content f, Node node, Configuration config) { /** * Holds if `f` is the target of a store in the flow covered by `nodeCandFwd1`. */ -pragma[noinline] +pragma[nomagic] private predicate storeCandFwd1(Content f, Configuration config) { exists(Node mid, Node node | not fullBarrier(node, config) and @@ -345,71 +355,120 @@ private predicate storeCandFwd1(Content f, Configuration config) { ) } +pragma[nomagic] +private predicate nodeCandFwd1ReturnKind( + DataFlowCall call, ReturnKindExt kind, boolean fromArg, Configuration config +) { + exists(ReturnPosition pos | + nodeCandFwd1ReturnPosition(pos, fromArg, config) and + pos = viableReturnPos(call, kind) + ) +} + +pragma[nomagic] +private predicate nodeCandFwd1Out( + DataFlowCall call, Node node, boolean fromArg, Configuration config +) { + exists(ReturnKindExt kind | + nodeCandFwd1ReturnKind(call, kind, fromArg, config) and + node = kind.getAnOutNode(call) + ) +} + +pragma[nomagic] +private predicate nodeCandFwd1OutFromArg(DataFlowCall call, Node node, Configuration config) { + nodeCandFwd1Out(call, node, true, config) +} + +/** + * Holds if an argument to `call` is reached in the flow covered by `nodeCandFwd1`. + */ +pragma[nomagic] +private predicate flowOutCandFwd1(DataFlowCall call, boolean fromArg, Configuration config) { + exists(ArgumentNode arg | + nodeCandFwd1(arg, fromArg, config) and + viableParamArg(call, _, arg) + ) +} + bindingset[result, b] private boolean unbindBool(boolean b) { result != b.booleanNot() } /** * Holds if `node` is part of a path from a source to a sink in the given - * configuration ignoring call contexts. + * configuration taking simple call contexts into consideration. */ pragma[nomagic] -private predicate nodeCand1(Node node, Configuration config) { +private predicate nodeCand1(Node node, boolean toReturn, Configuration config) { + nodeCand1_0(node, toReturn, config) and + nodeCandFwd1(node, config) +} + +pragma[nomagic] +private predicate nodeCand1_0(Node node, boolean toReturn, Configuration config) { nodeCandFwd1(node, config) and - config.isSink(node) + config.isSink(node) and + toReturn = false or - nodeCandFwd1(node, unbind(config)) and - ( - exists(Node mid | - localFlowStep(node, mid, config) and - nodeCand1(mid, config) - ) + exists(Node mid | + localFlowStep(node, mid, config) and + nodeCand1(mid, toReturn, config) + ) + or + exists(Node mid | + additionalLocalFlowStep(node, mid, config) and + nodeCand1(mid, toReturn, config) + ) + or + exists(Node mid | + jumpStep(node, mid, config) and + nodeCand1(mid, _, config) and + toReturn = false + ) + or + exists(Node mid | + additionalJumpStep(node, mid, config) and + nodeCand1(mid, _, config) and + toReturn = false + ) + or + // store + exists(Content f | + nodeCand1Store(f, node, toReturn, config) and + readCand1(f, config) + ) + or + // read + exists(Node mid, Content f | + readDirect(node, f, mid) and + storeCandFwd1(f, unbind(config)) and + nodeCand1(mid, toReturn, config) + ) + or + // flow into a callable + exists(DataFlowCall call | + nodeCand1Arg(call, node, false, config) and + toReturn = false or - exists(Node mid | - additionalLocalFlowStep(node, mid, config) and - nodeCand1(mid, config) - ) - or - exists(Node mid | - jumpStep(node, mid, config) and - nodeCand1(mid, config) - ) - or - exists(Node mid | - additionalJumpStep(node, mid, config) and - nodeCand1(mid, config) - ) - or - // store - exists(Content f | - nodeCand1Store(f, node, config) and - readCand1(f, config) - ) - or - // read - exists(Node mid, Content f | - readDirect(node, f, mid) and - storeCandFwd1(f, unbind(config)) and - nodeCand1(mid, config) - ) - or - // flow into a callable - exists(Node param | - viableParamArg(_, param, node) and - nodeCand1(param, config) - ) - or - // flow out of a callable - exists(ReturnPosition pos | - nodeCand1ReturnPosition(pos, config) and - getReturnPosition(node) = pos - ) + nodeCand1ArgToReturn(call, node, config) and + flowInCand1(call, toReturn, config) + ) + or + // flow out of a callable + exists(ReturnPosition pos | + nodeCand1ReturnPosition(pos, config) and + getReturnPosition(node) = pos and + toReturn = true ) } -pragma[noinline] +pragma[nomagic] +private predicate nodeCand1(Node node, Configuration config) { nodeCand1(node, _, config) } + +pragma[nomagic] private predicate nodeCand1ReturnPosition(ReturnPosition pos, Configuration config) { exists(DataFlowCall call, ReturnKindExt kind, Node out | - nodeCand1(out, config) and + nodeCand1(out, _, config) and pos = viableReturnPos(call, kind) and out = kind.getAnOutNode(call) ) @@ -418,21 +477,21 @@ private predicate nodeCand1ReturnPosition(ReturnPosition pos, Configuration conf /** * Holds if `f` is the target of a read in the flow covered by `nodeCand1`. */ -pragma[noinline] +pragma[nomagic] private predicate readCand1(Content f, Configuration config) { exists(Node mid, Node node | useFieldFlow(config) and nodeCandFwd1(node, unbind(config)) and readDirect(node, f, mid) and storeCandFwd1(f, unbind(config)) and - nodeCand1(mid, config) + nodeCand1(mid, _, config) ) } pragma[nomagic] -private predicate nodeCand1Store(Content f, Node node, Configuration config) { +private predicate nodeCand1Store(Content f, Node node, boolean toReturn, Configuration config) { exists(Node mid | - nodeCand1(mid, config) and + nodeCand1(mid, toReturn, config) and storeCandFwd1(f, unbind(config)) and storeDirect(node, f, mid) ) @@ -444,11 +503,45 @@ private predicate nodeCand1Store(Content f, Node node, Configuration config) { */ private predicate readStoreCand1(Content f, Configuration conf) { readCand1(f, conf) and - nodeCand1Store(f, _, conf) + nodeCand1Store(f, _, _, conf) +} + +pragma[nomagic] +private predicate viableParamArgCandFwd1( + DataFlowCall call, ParameterNode p, ArgumentNode arg, Configuration config +) { + viableParamArg(call, p, arg) and + nodeCandFwd1(arg, config) +} + +pragma[nomagic] +private predicate nodeCand1Arg( + DataFlowCall call, ArgumentNode arg, boolean toReturn, Configuration config +) { + exists(ParameterNode p | + nodeCand1(p, toReturn, config) and + viableParamArgCandFwd1(call, p, arg, config) + ) +} + +pragma[nomagic] +private predicate nodeCand1ArgToReturn(DataFlowCall call, ArgumentNode arg, Configuration config) { + nodeCand1Arg(call, arg, true, config) +} + +/** + * Holds if an output from `call` is reached in the flow covered by `nodeCand1`. + */ +pragma[nomagic] +private predicate flowInCand1(DataFlowCall call, boolean toReturn, Configuration config) { + exists(Node out | + nodeCand1(out, toReturn, config) and + nodeCandFwd1OutFromArg(call, out, config) + ) } private predicate throughFlowNodeCand(Node node, Configuration config) { - nodeCand1(node, config) and + nodeCand1(node, true, config) and not fullBarrier(node, config) and not inBarrier(node, config) and not outBarrier(node, config) @@ -2257,13 +2350,12 @@ private class PathNodeSink extends PathNodeImpl, TPathNodeSink { * a callable is recorded by `cc`. */ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCtx sc, AccessPath ap) { - exists(LocalCallContext localCC, AccessPath ap0, Node midnode, Configuration conf | - midnode = mid.getNode() and - conf = mid.getConfiguration() and - cc = mid.getCallContext() and - sc = mid.getSummaryCtx() and - localCC = getLocalCallContext(cc, midnode.getEnclosingCallable()) and - ap0 = mid.getAp() + exists( + AccessPath ap0, Node midnode, Configuration conf, DataFlowCallable enclosing, + LocalCallContext localCC + | + pathIntoLocalStep(mid, midnode, cc, enclosing, sc, ap0, conf) and + localCC = getLocalCallContext(cc, enclosing) | localFlowBigStep(midnode, node, true, conf, localCC) and ap = ap0 @@ -2297,6 +2389,20 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt pathThroughCallable(mid, node, cc, ap) and sc = mid.getSummaryCtx() } +pragma[nomagic] +private predicate pathIntoLocalStep( + PathNodeMid mid, Node midnode, CallContext cc, DataFlowCallable enclosing, SummaryCtx sc, + AccessPath ap0, Configuration conf +) { + midnode = mid.getNode() and + cc = mid.getCallContext() and + conf = mid.getConfiguration() and + localFlowBigStep(midnode, _, _, conf, _) and + enclosing = midnode.getEnclosingCallable() and + sc = mid.getSummaryCtx() and + ap0 = mid.getAp() +} + pragma[nomagic] private predicate readCand(Node node1, Content f, Node node2, Configuration config) { readDirect(node1, f, node2) and diff --git a/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl3.qll b/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl3.qll index 86d44e4fcce..5eb98aefe4e 100644 --- a/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl3.qll +++ b/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl3.qll @@ -259,44 +259,47 @@ private ReturnPosition viableReturnPos(DataFlowCall call, ReturnKindExt kind) { /** * Holds if `node` is reachable from a source in the given configuration - * ignoring call contexts. + * taking simple call contexts into consideration. */ -private predicate nodeCandFwd1(Node node, Configuration config) { +private predicate nodeCandFwd1(Node node, boolean fromArg, Configuration config) { not fullBarrier(node, config) and ( - config.isSource(node) + config.isSource(node) and + fromArg = false or exists(Node mid | - nodeCandFwd1(mid, config) and + nodeCandFwd1(mid, fromArg, config) and localFlowStep(mid, node, config) ) or exists(Node mid | - nodeCandFwd1(mid, config) and + nodeCandFwd1(mid, fromArg, config) and additionalLocalFlowStep(mid, node, config) ) or exists(Node mid | nodeCandFwd1(mid, config) and - jumpStep(mid, node, config) + jumpStep(mid, node, config) and + fromArg = false ) or exists(Node mid | nodeCandFwd1(mid, config) and - additionalJumpStep(mid, node, config) + additionalJumpStep(mid, node, config) and + fromArg = false ) or // store exists(Node mid | useFieldFlow(config) and - nodeCandFwd1(mid, config) and + nodeCandFwd1(mid, fromArg, config) and storeDirect(mid, _, node) and not outBarrier(mid, config) ) or // read exists(Content f | - nodeCandFwd1Read(f, node, config) and + nodeCandFwd1Read(f, node, fromArg, config) and storeCandFwd1(f, config) and not inBarrier(node, config) ) @@ -304,30 +307,37 @@ private predicate nodeCandFwd1(Node node, Configuration config) { // flow into a callable exists(Node arg | nodeCandFwd1(arg, config) and - viableParamArg(_, node, arg) + viableParamArg(_, node, arg) and + fromArg = true ) or // flow out of a callable - exists(DataFlowCall call, ReturnPosition pos, ReturnKindExt kind | - nodeCandFwd1ReturnPosition(pos, config) and - pos = viableReturnPos(call, kind) and - node = kind.getAnOutNode(call) + exists(DataFlowCall call | + nodeCandFwd1Out(call, node, false, config) and + fromArg = false + or + nodeCandFwd1OutFromArg(call, node, config) and + flowOutCandFwd1(call, fromArg, config) ) ) } -pragma[noinline] -private predicate nodeCandFwd1ReturnPosition(ReturnPosition pos, Configuration config) { +private predicate nodeCandFwd1(Node node, Configuration config) { nodeCandFwd1(node, _, config) } + +pragma[nomagic] +private predicate nodeCandFwd1ReturnPosition( + ReturnPosition pos, boolean fromArg, Configuration config +) { exists(ReturnNodeExt ret | - nodeCandFwd1(ret, config) and + nodeCandFwd1(ret, fromArg, config) and getReturnPosition(ret) = pos ) } pragma[nomagic] -private predicate nodeCandFwd1Read(Content f, Node node, Configuration config) { +private predicate nodeCandFwd1Read(Content f, Node node, boolean fromArg, Configuration config) { exists(Node mid | - nodeCandFwd1(mid, config) and + nodeCandFwd1(mid, fromArg, config) and readDirect(mid, f, node) ) } @@ -335,7 +345,7 @@ private predicate nodeCandFwd1Read(Content f, Node node, Configuration config) { /** * Holds if `f` is the target of a store in the flow covered by `nodeCandFwd1`. */ -pragma[noinline] +pragma[nomagic] private predicate storeCandFwd1(Content f, Configuration config) { exists(Node mid, Node node | not fullBarrier(node, config) and @@ -345,71 +355,120 @@ private predicate storeCandFwd1(Content f, Configuration config) { ) } +pragma[nomagic] +private predicate nodeCandFwd1ReturnKind( + DataFlowCall call, ReturnKindExt kind, boolean fromArg, Configuration config +) { + exists(ReturnPosition pos | + nodeCandFwd1ReturnPosition(pos, fromArg, config) and + pos = viableReturnPos(call, kind) + ) +} + +pragma[nomagic] +private predicate nodeCandFwd1Out( + DataFlowCall call, Node node, boolean fromArg, Configuration config +) { + exists(ReturnKindExt kind | + nodeCandFwd1ReturnKind(call, kind, fromArg, config) and + node = kind.getAnOutNode(call) + ) +} + +pragma[nomagic] +private predicate nodeCandFwd1OutFromArg(DataFlowCall call, Node node, Configuration config) { + nodeCandFwd1Out(call, node, true, config) +} + +/** + * Holds if an argument to `call` is reached in the flow covered by `nodeCandFwd1`. + */ +pragma[nomagic] +private predicate flowOutCandFwd1(DataFlowCall call, boolean fromArg, Configuration config) { + exists(ArgumentNode arg | + nodeCandFwd1(arg, fromArg, config) and + viableParamArg(call, _, arg) + ) +} + bindingset[result, b] private boolean unbindBool(boolean b) { result != b.booleanNot() } /** * Holds if `node` is part of a path from a source to a sink in the given - * configuration ignoring call contexts. + * configuration taking simple call contexts into consideration. */ pragma[nomagic] -private predicate nodeCand1(Node node, Configuration config) { +private predicate nodeCand1(Node node, boolean toReturn, Configuration config) { + nodeCand1_0(node, toReturn, config) and + nodeCandFwd1(node, config) +} + +pragma[nomagic] +private predicate nodeCand1_0(Node node, boolean toReturn, Configuration config) { nodeCandFwd1(node, config) and - config.isSink(node) + config.isSink(node) and + toReturn = false or - nodeCandFwd1(node, unbind(config)) and - ( - exists(Node mid | - localFlowStep(node, mid, config) and - nodeCand1(mid, config) - ) + exists(Node mid | + localFlowStep(node, mid, config) and + nodeCand1(mid, toReturn, config) + ) + or + exists(Node mid | + additionalLocalFlowStep(node, mid, config) and + nodeCand1(mid, toReturn, config) + ) + or + exists(Node mid | + jumpStep(node, mid, config) and + nodeCand1(mid, _, config) and + toReturn = false + ) + or + exists(Node mid | + additionalJumpStep(node, mid, config) and + nodeCand1(mid, _, config) and + toReturn = false + ) + or + // store + exists(Content f | + nodeCand1Store(f, node, toReturn, config) and + readCand1(f, config) + ) + or + // read + exists(Node mid, Content f | + readDirect(node, f, mid) and + storeCandFwd1(f, unbind(config)) and + nodeCand1(mid, toReturn, config) + ) + or + // flow into a callable + exists(DataFlowCall call | + nodeCand1Arg(call, node, false, config) and + toReturn = false or - exists(Node mid | - additionalLocalFlowStep(node, mid, config) and - nodeCand1(mid, config) - ) - or - exists(Node mid | - jumpStep(node, mid, config) and - nodeCand1(mid, config) - ) - or - exists(Node mid | - additionalJumpStep(node, mid, config) and - nodeCand1(mid, config) - ) - or - // store - exists(Content f | - nodeCand1Store(f, node, config) and - readCand1(f, config) - ) - or - // read - exists(Node mid, Content f | - readDirect(node, f, mid) and - storeCandFwd1(f, unbind(config)) and - nodeCand1(mid, config) - ) - or - // flow into a callable - exists(Node param | - viableParamArg(_, param, node) and - nodeCand1(param, config) - ) - or - // flow out of a callable - exists(ReturnPosition pos | - nodeCand1ReturnPosition(pos, config) and - getReturnPosition(node) = pos - ) + nodeCand1ArgToReturn(call, node, config) and + flowInCand1(call, toReturn, config) + ) + or + // flow out of a callable + exists(ReturnPosition pos | + nodeCand1ReturnPosition(pos, config) and + getReturnPosition(node) = pos and + toReturn = true ) } -pragma[noinline] +pragma[nomagic] +private predicate nodeCand1(Node node, Configuration config) { nodeCand1(node, _, config) } + +pragma[nomagic] private predicate nodeCand1ReturnPosition(ReturnPosition pos, Configuration config) { exists(DataFlowCall call, ReturnKindExt kind, Node out | - nodeCand1(out, config) and + nodeCand1(out, _, config) and pos = viableReturnPos(call, kind) and out = kind.getAnOutNode(call) ) @@ -418,21 +477,21 @@ private predicate nodeCand1ReturnPosition(ReturnPosition pos, Configuration conf /** * Holds if `f` is the target of a read in the flow covered by `nodeCand1`. */ -pragma[noinline] +pragma[nomagic] private predicate readCand1(Content f, Configuration config) { exists(Node mid, Node node | useFieldFlow(config) and nodeCandFwd1(node, unbind(config)) and readDirect(node, f, mid) and storeCandFwd1(f, unbind(config)) and - nodeCand1(mid, config) + nodeCand1(mid, _, config) ) } pragma[nomagic] -private predicate nodeCand1Store(Content f, Node node, Configuration config) { +private predicate nodeCand1Store(Content f, Node node, boolean toReturn, Configuration config) { exists(Node mid | - nodeCand1(mid, config) and + nodeCand1(mid, toReturn, config) and storeCandFwd1(f, unbind(config)) and storeDirect(node, f, mid) ) @@ -444,11 +503,45 @@ private predicate nodeCand1Store(Content f, Node node, Configuration config) { */ private predicate readStoreCand1(Content f, Configuration conf) { readCand1(f, conf) and - nodeCand1Store(f, _, conf) + nodeCand1Store(f, _, _, conf) +} + +pragma[nomagic] +private predicate viableParamArgCandFwd1( + DataFlowCall call, ParameterNode p, ArgumentNode arg, Configuration config +) { + viableParamArg(call, p, arg) and + nodeCandFwd1(arg, config) +} + +pragma[nomagic] +private predicate nodeCand1Arg( + DataFlowCall call, ArgumentNode arg, boolean toReturn, Configuration config +) { + exists(ParameterNode p | + nodeCand1(p, toReturn, config) and + viableParamArgCandFwd1(call, p, arg, config) + ) +} + +pragma[nomagic] +private predicate nodeCand1ArgToReturn(DataFlowCall call, ArgumentNode arg, Configuration config) { + nodeCand1Arg(call, arg, true, config) +} + +/** + * Holds if an output from `call` is reached in the flow covered by `nodeCand1`. + */ +pragma[nomagic] +private predicate flowInCand1(DataFlowCall call, boolean toReturn, Configuration config) { + exists(Node out | + nodeCand1(out, toReturn, config) and + nodeCandFwd1OutFromArg(call, out, config) + ) } private predicate throughFlowNodeCand(Node node, Configuration config) { - nodeCand1(node, config) and + nodeCand1(node, true, config) and not fullBarrier(node, config) and not inBarrier(node, config) and not outBarrier(node, config) @@ -2257,13 +2350,12 @@ private class PathNodeSink extends PathNodeImpl, TPathNodeSink { * a callable is recorded by `cc`. */ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCtx sc, AccessPath ap) { - exists(LocalCallContext localCC, AccessPath ap0, Node midnode, Configuration conf | - midnode = mid.getNode() and - conf = mid.getConfiguration() and - cc = mid.getCallContext() and - sc = mid.getSummaryCtx() and - localCC = getLocalCallContext(cc, midnode.getEnclosingCallable()) and - ap0 = mid.getAp() + exists( + AccessPath ap0, Node midnode, Configuration conf, DataFlowCallable enclosing, + LocalCallContext localCC + | + pathIntoLocalStep(mid, midnode, cc, enclosing, sc, ap0, conf) and + localCC = getLocalCallContext(cc, enclosing) | localFlowBigStep(midnode, node, true, conf, localCC) and ap = ap0 @@ -2297,6 +2389,20 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt pathThroughCallable(mid, node, cc, ap) and sc = mid.getSummaryCtx() } +pragma[nomagic] +private predicate pathIntoLocalStep( + PathNodeMid mid, Node midnode, CallContext cc, DataFlowCallable enclosing, SummaryCtx sc, + AccessPath ap0, Configuration conf +) { + midnode = mid.getNode() and + cc = mid.getCallContext() and + conf = mid.getConfiguration() and + localFlowBigStep(midnode, _, _, conf, _) and + enclosing = midnode.getEnclosingCallable() and + sc = mid.getSummaryCtx() and + ap0 = mid.getAp() +} + pragma[nomagic] private predicate readCand(Node node1, Content f, Node node2, Configuration config) { readDirect(node1, f, node2) and diff --git a/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl4.qll b/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl4.qll index 86d44e4fcce..5eb98aefe4e 100644 --- a/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl4.qll +++ b/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl4.qll @@ -259,44 +259,47 @@ private ReturnPosition viableReturnPos(DataFlowCall call, ReturnKindExt kind) { /** * Holds if `node` is reachable from a source in the given configuration - * ignoring call contexts. + * taking simple call contexts into consideration. */ -private predicate nodeCandFwd1(Node node, Configuration config) { +private predicate nodeCandFwd1(Node node, boolean fromArg, Configuration config) { not fullBarrier(node, config) and ( - config.isSource(node) + config.isSource(node) and + fromArg = false or exists(Node mid | - nodeCandFwd1(mid, config) and + nodeCandFwd1(mid, fromArg, config) and localFlowStep(mid, node, config) ) or exists(Node mid | - nodeCandFwd1(mid, config) and + nodeCandFwd1(mid, fromArg, config) and additionalLocalFlowStep(mid, node, config) ) or exists(Node mid | nodeCandFwd1(mid, config) and - jumpStep(mid, node, config) + jumpStep(mid, node, config) and + fromArg = false ) or exists(Node mid | nodeCandFwd1(mid, config) and - additionalJumpStep(mid, node, config) + additionalJumpStep(mid, node, config) and + fromArg = false ) or // store exists(Node mid | useFieldFlow(config) and - nodeCandFwd1(mid, config) and + nodeCandFwd1(mid, fromArg, config) and storeDirect(mid, _, node) and not outBarrier(mid, config) ) or // read exists(Content f | - nodeCandFwd1Read(f, node, config) and + nodeCandFwd1Read(f, node, fromArg, config) and storeCandFwd1(f, config) and not inBarrier(node, config) ) @@ -304,30 +307,37 @@ private predicate nodeCandFwd1(Node node, Configuration config) { // flow into a callable exists(Node arg | nodeCandFwd1(arg, config) and - viableParamArg(_, node, arg) + viableParamArg(_, node, arg) and + fromArg = true ) or // flow out of a callable - exists(DataFlowCall call, ReturnPosition pos, ReturnKindExt kind | - nodeCandFwd1ReturnPosition(pos, config) and - pos = viableReturnPos(call, kind) and - node = kind.getAnOutNode(call) + exists(DataFlowCall call | + nodeCandFwd1Out(call, node, false, config) and + fromArg = false + or + nodeCandFwd1OutFromArg(call, node, config) and + flowOutCandFwd1(call, fromArg, config) ) ) } -pragma[noinline] -private predicate nodeCandFwd1ReturnPosition(ReturnPosition pos, Configuration config) { +private predicate nodeCandFwd1(Node node, Configuration config) { nodeCandFwd1(node, _, config) } + +pragma[nomagic] +private predicate nodeCandFwd1ReturnPosition( + ReturnPosition pos, boolean fromArg, Configuration config +) { exists(ReturnNodeExt ret | - nodeCandFwd1(ret, config) and + nodeCandFwd1(ret, fromArg, config) and getReturnPosition(ret) = pos ) } pragma[nomagic] -private predicate nodeCandFwd1Read(Content f, Node node, Configuration config) { +private predicate nodeCandFwd1Read(Content f, Node node, boolean fromArg, Configuration config) { exists(Node mid | - nodeCandFwd1(mid, config) and + nodeCandFwd1(mid, fromArg, config) and readDirect(mid, f, node) ) } @@ -335,7 +345,7 @@ private predicate nodeCandFwd1Read(Content f, Node node, Configuration config) { /** * Holds if `f` is the target of a store in the flow covered by `nodeCandFwd1`. */ -pragma[noinline] +pragma[nomagic] private predicate storeCandFwd1(Content f, Configuration config) { exists(Node mid, Node node | not fullBarrier(node, config) and @@ -345,71 +355,120 @@ private predicate storeCandFwd1(Content f, Configuration config) { ) } +pragma[nomagic] +private predicate nodeCandFwd1ReturnKind( + DataFlowCall call, ReturnKindExt kind, boolean fromArg, Configuration config +) { + exists(ReturnPosition pos | + nodeCandFwd1ReturnPosition(pos, fromArg, config) and + pos = viableReturnPos(call, kind) + ) +} + +pragma[nomagic] +private predicate nodeCandFwd1Out( + DataFlowCall call, Node node, boolean fromArg, Configuration config +) { + exists(ReturnKindExt kind | + nodeCandFwd1ReturnKind(call, kind, fromArg, config) and + node = kind.getAnOutNode(call) + ) +} + +pragma[nomagic] +private predicate nodeCandFwd1OutFromArg(DataFlowCall call, Node node, Configuration config) { + nodeCandFwd1Out(call, node, true, config) +} + +/** + * Holds if an argument to `call` is reached in the flow covered by `nodeCandFwd1`. + */ +pragma[nomagic] +private predicate flowOutCandFwd1(DataFlowCall call, boolean fromArg, Configuration config) { + exists(ArgumentNode arg | + nodeCandFwd1(arg, fromArg, config) and + viableParamArg(call, _, arg) + ) +} + bindingset[result, b] private boolean unbindBool(boolean b) { result != b.booleanNot() } /** * Holds if `node` is part of a path from a source to a sink in the given - * configuration ignoring call contexts. + * configuration taking simple call contexts into consideration. */ pragma[nomagic] -private predicate nodeCand1(Node node, Configuration config) { +private predicate nodeCand1(Node node, boolean toReturn, Configuration config) { + nodeCand1_0(node, toReturn, config) and + nodeCandFwd1(node, config) +} + +pragma[nomagic] +private predicate nodeCand1_0(Node node, boolean toReturn, Configuration config) { nodeCandFwd1(node, config) and - config.isSink(node) + config.isSink(node) and + toReturn = false or - nodeCandFwd1(node, unbind(config)) and - ( - exists(Node mid | - localFlowStep(node, mid, config) and - nodeCand1(mid, config) - ) + exists(Node mid | + localFlowStep(node, mid, config) and + nodeCand1(mid, toReturn, config) + ) + or + exists(Node mid | + additionalLocalFlowStep(node, mid, config) and + nodeCand1(mid, toReturn, config) + ) + or + exists(Node mid | + jumpStep(node, mid, config) and + nodeCand1(mid, _, config) and + toReturn = false + ) + or + exists(Node mid | + additionalJumpStep(node, mid, config) and + nodeCand1(mid, _, config) and + toReturn = false + ) + or + // store + exists(Content f | + nodeCand1Store(f, node, toReturn, config) and + readCand1(f, config) + ) + or + // read + exists(Node mid, Content f | + readDirect(node, f, mid) and + storeCandFwd1(f, unbind(config)) and + nodeCand1(mid, toReturn, config) + ) + or + // flow into a callable + exists(DataFlowCall call | + nodeCand1Arg(call, node, false, config) and + toReturn = false or - exists(Node mid | - additionalLocalFlowStep(node, mid, config) and - nodeCand1(mid, config) - ) - or - exists(Node mid | - jumpStep(node, mid, config) and - nodeCand1(mid, config) - ) - or - exists(Node mid | - additionalJumpStep(node, mid, config) and - nodeCand1(mid, config) - ) - or - // store - exists(Content f | - nodeCand1Store(f, node, config) and - readCand1(f, config) - ) - or - // read - exists(Node mid, Content f | - readDirect(node, f, mid) and - storeCandFwd1(f, unbind(config)) and - nodeCand1(mid, config) - ) - or - // flow into a callable - exists(Node param | - viableParamArg(_, param, node) and - nodeCand1(param, config) - ) - or - // flow out of a callable - exists(ReturnPosition pos | - nodeCand1ReturnPosition(pos, config) and - getReturnPosition(node) = pos - ) + nodeCand1ArgToReturn(call, node, config) and + flowInCand1(call, toReturn, config) + ) + or + // flow out of a callable + exists(ReturnPosition pos | + nodeCand1ReturnPosition(pos, config) and + getReturnPosition(node) = pos and + toReturn = true ) } -pragma[noinline] +pragma[nomagic] +private predicate nodeCand1(Node node, Configuration config) { nodeCand1(node, _, config) } + +pragma[nomagic] private predicate nodeCand1ReturnPosition(ReturnPosition pos, Configuration config) { exists(DataFlowCall call, ReturnKindExt kind, Node out | - nodeCand1(out, config) and + nodeCand1(out, _, config) and pos = viableReturnPos(call, kind) and out = kind.getAnOutNode(call) ) @@ -418,21 +477,21 @@ private predicate nodeCand1ReturnPosition(ReturnPosition pos, Configuration conf /** * Holds if `f` is the target of a read in the flow covered by `nodeCand1`. */ -pragma[noinline] +pragma[nomagic] private predicate readCand1(Content f, Configuration config) { exists(Node mid, Node node | useFieldFlow(config) and nodeCandFwd1(node, unbind(config)) and readDirect(node, f, mid) and storeCandFwd1(f, unbind(config)) and - nodeCand1(mid, config) + nodeCand1(mid, _, config) ) } pragma[nomagic] -private predicate nodeCand1Store(Content f, Node node, Configuration config) { +private predicate nodeCand1Store(Content f, Node node, boolean toReturn, Configuration config) { exists(Node mid | - nodeCand1(mid, config) and + nodeCand1(mid, toReturn, config) and storeCandFwd1(f, unbind(config)) and storeDirect(node, f, mid) ) @@ -444,11 +503,45 @@ private predicate nodeCand1Store(Content f, Node node, Configuration config) { */ private predicate readStoreCand1(Content f, Configuration conf) { readCand1(f, conf) and - nodeCand1Store(f, _, conf) + nodeCand1Store(f, _, _, conf) +} + +pragma[nomagic] +private predicate viableParamArgCandFwd1( + DataFlowCall call, ParameterNode p, ArgumentNode arg, Configuration config +) { + viableParamArg(call, p, arg) and + nodeCandFwd1(arg, config) +} + +pragma[nomagic] +private predicate nodeCand1Arg( + DataFlowCall call, ArgumentNode arg, boolean toReturn, Configuration config +) { + exists(ParameterNode p | + nodeCand1(p, toReturn, config) and + viableParamArgCandFwd1(call, p, arg, config) + ) +} + +pragma[nomagic] +private predicate nodeCand1ArgToReturn(DataFlowCall call, ArgumentNode arg, Configuration config) { + nodeCand1Arg(call, arg, true, config) +} + +/** + * Holds if an output from `call` is reached in the flow covered by `nodeCand1`. + */ +pragma[nomagic] +private predicate flowInCand1(DataFlowCall call, boolean toReturn, Configuration config) { + exists(Node out | + nodeCand1(out, toReturn, config) and + nodeCandFwd1OutFromArg(call, out, config) + ) } private predicate throughFlowNodeCand(Node node, Configuration config) { - nodeCand1(node, config) and + nodeCand1(node, true, config) and not fullBarrier(node, config) and not inBarrier(node, config) and not outBarrier(node, config) @@ -2257,13 +2350,12 @@ private class PathNodeSink extends PathNodeImpl, TPathNodeSink { * a callable is recorded by `cc`. */ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCtx sc, AccessPath ap) { - exists(LocalCallContext localCC, AccessPath ap0, Node midnode, Configuration conf | - midnode = mid.getNode() and - conf = mid.getConfiguration() and - cc = mid.getCallContext() and - sc = mid.getSummaryCtx() and - localCC = getLocalCallContext(cc, midnode.getEnclosingCallable()) and - ap0 = mid.getAp() + exists( + AccessPath ap0, Node midnode, Configuration conf, DataFlowCallable enclosing, + LocalCallContext localCC + | + pathIntoLocalStep(mid, midnode, cc, enclosing, sc, ap0, conf) and + localCC = getLocalCallContext(cc, enclosing) | localFlowBigStep(midnode, node, true, conf, localCC) and ap = ap0 @@ -2297,6 +2389,20 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt pathThroughCallable(mid, node, cc, ap) and sc = mid.getSummaryCtx() } +pragma[nomagic] +private predicate pathIntoLocalStep( + PathNodeMid mid, Node midnode, CallContext cc, DataFlowCallable enclosing, SummaryCtx sc, + AccessPath ap0, Configuration conf +) { + midnode = mid.getNode() and + cc = mid.getCallContext() and + conf = mid.getConfiguration() and + localFlowBigStep(midnode, _, _, conf, _) and + enclosing = midnode.getEnclosingCallable() and + sc = mid.getSummaryCtx() and + ap0 = mid.getAp() +} + pragma[nomagic] private predicate readCand(Node node1, Content f, Node node2, Configuration config) { readDirect(node1, f, node2) and diff --git a/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImplLocal.qll b/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImplLocal.qll index 86d44e4fcce..5eb98aefe4e 100644 --- a/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImplLocal.qll +++ b/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImplLocal.qll @@ -259,44 +259,47 @@ private ReturnPosition viableReturnPos(DataFlowCall call, ReturnKindExt kind) { /** * Holds if `node` is reachable from a source in the given configuration - * ignoring call contexts. + * taking simple call contexts into consideration. */ -private predicate nodeCandFwd1(Node node, Configuration config) { +private predicate nodeCandFwd1(Node node, boolean fromArg, Configuration config) { not fullBarrier(node, config) and ( - config.isSource(node) + config.isSource(node) and + fromArg = false or exists(Node mid | - nodeCandFwd1(mid, config) and + nodeCandFwd1(mid, fromArg, config) and localFlowStep(mid, node, config) ) or exists(Node mid | - nodeCandFwd1(mid, config) and + nodeCandFwd1(mid, fromArg, config) and additionalLocalFlowStep(mid, node, config) ) or exists(Node mid | nodeCandFwd1(mid, config) and - jumpStep(mid, node, config) + jumpStep(mid, node, config) and + fromArg = false ) or exists(Node mid | nodeCandFwd1(mid, config) and - additionalJumpStep(mid, node, config) + additionalJumpStep(mid, node, config) and + fromArg = false ) or // store exists(Node mid | useFieldFlow(config) and - nodeCandFwd1(mid, config) and + nodeCandFwd1(mid, fromArg, config) and storeDirect(mid, _, node) and not outBarrier(mid, config) ) or // read exists(Content f | - nodeCandFwd1Read(f, node, config) and + nodeCandFwd1Read(f, node, fromArg, config) and storeCandFwd1(f, config) and not inBarrier(node, config) ) @@ -304,30 +307,37 @@ private predicate nodeCandFwd1(Node node, Configuration config) { // flow into a callable exists(Node arg | nodeCandFwd1(arg, config) and - viableParamArg(_, node, arg) + viableParamArg(_, node, arg) and + fromArg = true ) or // flow out of a callable - exists(DataFlowCall call, ReturnPosition pos, ReturnKindExt kind | - nodeCandFwd1ReturnPosition(pos, config) and - pos = viableReturnPos(call, kind) and - node = kind.getAnOutNode(call) + exists(DataFlowCall call | + nodeCandFwd1Out(call, node, false, config) and + fromArg = false + or + nodeCandFwd1OutFromArg(call, node, config) and + flowOutCandFwd1(call, fromArg, config) ) ) } -pragma[noinline] -private predicate nodeCandFwd1ReturnPosition(ReturnPosition pos, Configuration config) { +private predicate nodeCandFwd1(Node node, Configuration config) { nodeCandFwd1(node, _, config) } + +pragma[nomagic] +private predicate nodeCandFwd1ReturnPosition( + ReturnPosition pos, boolean fromArg, Configuration config +) { exists(ReturnNodeExt ret | - nodeCandFwd1(ret, config) and + nodeCandFwd1(ret, fromArg, config) and getReturnPosition(ret) = pos ) } pragma[nomagic] -private predicate nodeCandFwd1Read(Content f, Node node, Configuration config) { +private predicate nodeCandFwd1Read(Content f, Node node, boolean fromArg, Configuration config) { exists(Node mid | - nodeCandFwd1(mid, config) and + nodeCandFwd1(mid, fromArg, config) and readDirect(mid, f, node) ) } @@ -335,7 +345,7 @@ private predicate nodeCandFwd1Read(Content f, Node node, Configuration config) { /** * Holds if `f` is the target of a store in the flow covered by `nodeCandFwd1`. */ -pragma[noinline] +pragma[nomagic] private predicate storeCandFwd1(Content f, Configuration config) { exists(Node mid, Node node | not fullBarrier(node, config) and @@ -345,71 +355,120 @@ private predicate storeCandFwd1(Content f, Configuration config) { ) } +pragma[nomagic] +private predicate nodeCandFwd1ReturnKind( + DataFlowCall call, ReturnKindExt kind, boolean fromArg, Configuration config +) { + exists(ReturnPosition pos | + nodeCandFwd1ReturnPosition(pos, fromArg, config) and + pos = viableReturnPos(call, kind) + ) +} + +pragma[nomagic] +private predicate nodeCandFwd1Out( + DataFlowCall call, Node node, boolean fromArg, Configuration config +) { + exists(ReturnKindExt kind | + nodeCandFwd1ReturnKind(call, kind, fromArg, config) and + node = kind.getAnOutNode(call) + ) +} + +pragma[nomagic] +private predicate nodeCandFwd1OutFromArg(DataFlowCall call, Node node, Configuration config) { + nodeCandFwd1Out(call, node, true, config) +} + +/** + * Holds if an argument to `call` is reached in the flow covered by `nodeCandFwd1`. + */ +pragma[nomagic] +private predicate flowOutCandFwd1(DataFlowCall call, boolean fromArg, Configuration config) { + exists(ArgumentNode arg | + nodeCandFwd1(arg, fromArg, config) and + viableParamArg(call, _, arg) + ) +} + bindingset[result, b] private boolean unbindBool(boolean b) { result != b.booleanNot() } /** * Holds if `node` is part of a path from a source to a sink in the given - * configuration ignoring call contexts. + * configuration taking simple call contexts into consideration. */ pragma[nomagic] -private predicate nodeCand1(Node node, Configuration config) { +private predicate nodeCand1(Node node, boolean toReturn, Configuration config) { + nodeCand1_0(node, toReturn, config) and + nodeCandFwd1(node, config) +} + +pragma[nomagic] +private predicate nodeCand1_0(Node node, boolean toReturn, Configuration config) { nodeCandFwd1(node, config) and - config.isSink(node) + config.isSink(node) and + toReturn = false or - nodeCandFwd1(node, unbind(config)) and - ( - exists(Node mid | - localFlowStep(node, mid, config) and - nodeCand1(mid, config) - ) + exists(Node mid | + localFlowStep(node, mid, config) and + nodeCand1(mid, toReturn, config) + ) + or + exists(Node mid | + additionalLocalFlowStep(node, mid, config) and + nodeCand1(mid, toReturn, config) + ) + or + exists(Node mid | + jumpStep(node, mid, config) and + nodeCand1(mid, _, config) and + toReturn = false + ) + or + exists(Node mid | + additionalJumpStep(node, mid, config) and + nodeCand1(mid, _, config) and + toReturn = false + ) + or + // store + exists(Content f | + nodeCand1Store(f, node, toReturn, config) and + readCand1(f, config) + ) + or + // read + exists(Node mid, Content f | + readDirect(node, f, mid) and + storeCandFwd1(f, unbind(config)) and + nodeCand1(mid, toReturn, config) + ) + or + // flow into a callable + exists(DataFlowCall call | + nodeCand1Arg(call, node, false, config) and + toReturn = false or - exists(Node mid | - additionalLocalFlowStep(node, mid, config) and - nodeCand1(mid, config) - ) - or - exists(Node mid | - jumpStep(node, mid, config) and - nodeCand1(mid, config) - ) - or - exists(Node mid | - additionalJumpStep(node, mid, config) and - nodeCand1(mid, config) - ) - or - // store - exists(Content f | - nodeCand1Store(f, node, config) and - readCand1(f, config) - ) - or - // read - exists(Node mid, Content f | - readDirect(node, f, mid) and - storeCandFwd1(f, unbind(config)) and - nodeCand1(mid, config) - ) - or - // flow into a callable - exists(Node param | - viableParamArg(_, param, node) and - nodeCand1(param, config) - ) - or - // flow out of a callable - exists(ReturnPosition pos | - nodeCand1ReturnPosition(pos, config) and - getReturnPosition(node) = pos - ) + nodeCand1ArgToReturn(call, node, config) and + flowInCand1(call, toReturn, config) + ) + or + // flow out of a callable + exists(ReturnPosition pos | + nodeCand1ReturnPosition(pos, config) and + getReturnPosition(node) = pos and + toReturn = true ) } -pragma[noinline] +pragma[nomagic] +private predicate nodeCand1(Node node, Configuration config) { nodeCand1(node, _, config) } + +pragma[nomagic] private predicate nodeCand1ReturnPosition(ReturnPosition pos, Configuration config) { exists(DataFlowCall call, ReturnKindExt kind, Node out | - nodeCand1(out, config) and + nodeCand1(out, _, config) and pos = viableReturnPos(call, kind) and out = kind.getAnOutNode(call) ) @@ -418,21 +477,21 @@ private predicate nodeCand1ReturnPosition(ReturnPosition pos, Configuration conf /** * Holds if `f` is the target of a read in the flow covered by `nodeCand1`. */ -pragma[noinline] +pragma[nomagic] private predicate readCand1(Content f, Configuration config) { exists(Node mid, Node node | useFieldFlow(config) and nodeCandFwd1(node, unbind(config)) and readDirect(node, f, mid) and storeCandFwd1(f, unbind(config)) and - nodeCand1(mid, config) + nodeCand1(mid, _, config) ) } pragma[nomagic] -private predicate nodeCand1Store(Content f, Node node, Configuration config) { +private predicate nodeCand1Store(Content f, Node node, boolean toReturn, Configuration config) { exists(Node mid | - nodeCand1(mid, config) and + nodeCand1(mid, toReturn, config) and storeCandFwd1(f, unbind(config)) and storeDirect(node, f, mid) ) @@ -444,11 +503,45 @@ private predicate nodeCand1Store(Content f, Node node, Configuration config) { */ private predicate readStoreCand1(Content f, Configuration conf) { readCand1(f, conf) and - nodeCand1Store(f, _, conf) + nodeCand1Store(f, _, _, conf) +} + +pragma[nomagic] +private predicate viableParamArgCandFwd1( + DataFlowCall call, ParameterNode p, ArgumentNode arg, Configuration config +) { + viableParamArg(call, p, arg) and + nodeCandFwd1(arg, config) +} + +pragma[nomagic] +private predicate nodeCand1Arg( + DataFlowCall call, ArgumentNode arg, boolean toReturn, Configuration config +) { + exists(ParameterNode p | + nodeCand1(p, toReturn, config) and + viableParamArgCandFwd1(call, p, arg, config) + ) +} + +pragma[nomagic] +private predicate nodeCand1ArgToReturn(DataFlowCall call, ArgumentNode arg, Configuration config) { + nodeCand1Arg(call, arg, true, config) +} + +/** + * Holds if an output from `call` is reached in the flow covered by `nodeCand1`. + */ +pragma[nomagic] +private predicate flowInCand1(DataFlowCall call, boolean toReturn, Configuration config) { + exists(Node out | + nodeCand1(out, toReturn, config) and + nodeCandFwd1OutFromArg(call, out, config) + ) } private predicate throughFlowNodeCand(Node node, Configuration config) { - nodeCand1(node, config) and + nodeCand1(node, true, config) and not fullBarrier(node, config) and not inBarrier(node, config) and not outBarrier(node, config) @@ -2257,13 +2350,12 @@ private class PathNodeSink extends PathNodeImpl, TPathNodeSink { * a callable is recorded by `cc`. */ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCtx sc, AccessPath ap) { - exists(LocalCallContext localCC, AccessPath ap0, Node midnode, Configuration conf | - midnode = mid.getNode() and - conf = mid.getConfiguration() and - cc = mid.getCallContext() and - sc = mid.getSummaryCtx() and - localCC = getLocalCallContext(cc, midnode.getEnclosingCallable()) and - ap0 = mid.getAp() + exists( + AccessPath ap0, Node midnode, Configuration conf, DataFlowCallable enclosing, + LocalCallContext localCC + | + pathIntoLocalStep(mid, midnode, cc, enclosing, sc, ap0, conf) and + localCC = getLocalCallContext(cc, enclosing) | localFlowBigStep(midnode, node, true, conf, localCC) and ap = ap0 @@ -2297,6 +2389,20 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt pathThroughCallable(mid, node, cc, ap) and sc = mid.getSummaryCtx() } +pragma[nomagic] +private predicate pathIntoLocalStep( + PathNodeMid mid, Node midnode, CallContext cc, DataFlowCallable enclosing, SummaryCtx sc, + AccessPath ap0, Configuration conf +) { + midnode = mid.getNode() and + cc = mid.getCallContext() and + conf = mid.getConfiguration() and + localFlowBigStep(midnode, _, _, conf, _) and + enclosing = midnode.getEnclosingCallable() and + sc = mid.getSummaryCtx() and + ap0 = mid.getAp() +} + pragma[nomagic] private predicate readCand(Node node1, Content f, Node node2, Configuration config) { readDirect(node1, f, node2) and diff --git a/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowPrivate.qll b/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowPrivate.qll index dc035ec8681..a0fd5dc4c50 100644 --- a/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowPrivate.qll +++ b/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowPrivate.qll @@ -132,16 +132,6 @@ OutNode getAnOutNode(DataFlowCall call, ReturnKind kind) { */ predicate jumpStep(Node n1, Node n2) { none() } -/** - * Holds if `call` passes an implicit or explicit qualifier, i.e., a - * `this` parameter. - */ -predicate callHasQualifier(Call call) { - call.hasQualifier() - or - call.getTarget() instanceof Destructor -} - private newtype TContent = TFieldContent(Field f) or TCollectionContent() or 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 f39a52136f1..7e2107508a5 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/dataflow/DefaultTaintTracking.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/dataflow/DefaultTaintTracking.qll @@ -345,6 +345,7 @@ private Element adjustedSink(DataFlow::Node sink) { result.(AssignOperation).getAnOperand() = sink.asExpr() } +cached predicate tainted(Expr source, Element tainted) { exists(DefaultTaintTrackingCfg cfg, DataFlow::Node sink | cfg.hasFlow(getNodeForSource(source), sink) and @@ -362,6 +363,7 @@ predicate tainted_instruction( ) } +cached predicate taintedIncludingGlobalVars(Expr source, Element tainted, string globalVar) { tainted(source, tainted) and globalVar = "" diff --git a/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowDispatch.qll b/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowDispatch.qll index cebeda25a6e..f1d8c21595e 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowDispatch.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowDispatch.qll @@ -3,8 +3,6 @@ private import semmle.code.cpp.ir.IR private import semmle.code.cpp.ir.dataflow.DataFlow private import semmle.code.cpp.ir.dataflow.internal.DataFlowPrivate -Function viableImpl(CallInstruction call) { result = viableCallable(call) } - /** * Gets a function that might be called by `call`. */ diff --git a/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl.qll b/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl.qll index 86d44e4fcce..5eb98aefe4e 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl.qll @@ -259,44 +259,47 @@ private ReturnPosition viableReturnPos(DataFlowCall call, ReturnKindExt kind) { /** * Holds if `node` is reachable from a source in the given configuration - * ignoring call contexts. + * taking simple call contexts into consideration. */ -private predicate nodeCandFwd1(Node node, Configuration config) { +private predicate nodeCandFwd1(Node node, boolean fromArg, Configuration config) { not fullBarrier(node, config) and ( - config.isSource(node) + config.isSource(node) and + fromArg = false or exists(Node mid | - nodeCandFwd1(mid, config) and + nodeCandFwd1(mid, fromArg, config) and localFlowStep(mid, node, config) ) or exists(Node mid | - nodeCandFwd1(mid, config) and + nodeCandFwd1(mid, fromArg, config) and additionalLocalFlowStep(mid, node, config) ) or exists(Node mid | nodeCandFwd1(mid, config) and - jumpStep(mid, node, config) + jumpStep(mid, node, config) and + fromArg = false ) or exists(Node mid | nodeCandFwd1(mid, config) and - additionalJumpStep(mid, node, config) + additionalJumpStep(mid, node, config) and + fromArg = false ) or // store exists(Node mid | useFieldFlow(config) and - nodeCandFwd1(mid, config) and + nodeCandFwd1(mid, fromArg, config) and storeDirect(mid, _, node) and not outBarrier(mid, config) ) or // read exists(Content f | - nodeCandFwd1Read(f, node, config) and + nodeCandFwd1Read(f, node, fromArg, config) and storeCandFwd1(f, config) and not inBarrier(node, config) ) @@ -304,30 +307,37 @@ private predicate nodeCandFwd1(Node node, Configuration config) { // flow into a callable exists(Node arg | nodeCandFwd1(arg, config) and - viableParamArg(_, node, arg) + viableParamArg(_, node, arg) and + fromArg = true ) or // flow out of a callable - exists(DataFlowCall call, ReturnPosition pos, ReturnKindExt kind | - nodeCandFwd1ReturnPosition(pos, config) and - pos = viableReturnPos(call, kind) and - node = kind.getAnOutNode(call) + exists(DataFlowCall call | + nodeCandFwd1Out(call, node, false, config) and + fromArg = false + or + nodeCandFwd1OutFromArg(call, node, config) and + flowOutCandFwd1(call, fromArg, config) ) ) } -pragma[noinline] -private predicate nodeCandFwd1ReturnPosition(ReturnPosition pos, Configuration config) { +private predicate nodeCandFwd1(Node node, Configuration config) { nodeCandFwd1(node, _, config) } + +pragma[nomagic] +private predicate nodeCandFwd1ReturnPosition( + ReturnPosition pos, boolean fromArg, Configuration config +) { exists(ReturnNodeExt ret | - nodeCandFwd1(ret, config) and + nodeCandFwd1(ret, fromArg, config) and getReturnPosition(ret) = pos ) } pragma[nomagic] -private predicate nodeCandFwd1Read(Content f, Node node, Configuration config) { +private predicate nodeCandFwd1Read(Content f, Node node, boolean fromArg, Configuration config) { exists(Node mid | - nodeCandFwd1(mid, config) and + nodeCandFwd1(mid, fromArg, config) and readDirect(mid, f, node) ) } @@ -335,7 +345,7 @@ private predicate nodeCandFwd1Read(Content f, Node node, Configuration config) { /** * Holds if `f` is the target of a store in the flow covered by `nodeCandFwd1`. */ -pragma[noinline] +pragma[nomagic] private predicate storeCandFwd1(Content f, Configuration config) { exists(Node mid, Node node | not fullBarrier(node, config) and @@ -345,71 +355,120 @@ private predicate storeCandFwd1(Content f, Configuration config) { ) } +pragma[nomagic] +private predicate nodeCandFwd1ReturnKind( + DataFlowCall call, ReturnKindExt kind, boolean fromArg, Configuration config +) { + exists(ReturnPosition pos | + nodeCandFwd1ReturnPosition(pos, fromArg, config) and + pos = viableReturnPos(call, kind) + ) +} + +pragma[nomagic] +private predicate nodeCandFwd1Out( + DataFlowCall call, Node node, boolean fromArg, Configuration config +) { + exists(ReturnKindExt kind | + nodeCandFwd1ReturnKind(call, kind, fromArg, config) and + node = kind.getAnOutNode(call) + ) +} + +pragma[nomagic] +private predicate nodeCandFwd1OutFromArg(DataFlowCall call, Node node, Configuration config) { + nodeCandFwd1Out(call, node, true, config) +} + +/** + * Holds if an argument to `call` is reached in the flow covered by `nodeCandFwd1`. + */ +pragma[nomagic] +private predicate flowOutCandFwd1(DataFlowCall call, boolean fromArg, Configuration config) { + exists(ArgumentNode arg | + nodeCandFwd1(arg, fromArg, config) and + viableParamArg(call, _, arg) + ) +} + bindingset[result, b] private boolean unbindBool(boolean b) { result != b.booleanNot() } /** * Holds if `node` is part of a path from a source to a sink in the given - * configuration ignoring call contexts. + * configuration taking simple call contexts into consideration. */ pragma[nomagic] -private predicate nodeCand1(Node node, Configuration config) { +private predicate nodeCand1(Node node, boolean toReturn, Configuration config) { + nodeCand1_0(node, toReturn, config) and + nodeCandFwd1(node, config) +} + +pragma[nomagic] +private predicate nodeCand1_0(Node node, boolean toReturn, Configuration config) { nodeCandFwd1(node, config) and - config.isSink(node) + config.isSink(node) and + toReturn = false or - nodeCandFwd1(node, unbind(config)) and - ( - exists(Node mid | - localFlowStep(node, mid, config) and - nodeCand1(mid, config) - ) + exists(Node mid | + localFlowStep(node, mid, config) and + nodeCand1(mid, toReturn, config) + ) + or + exists(Node mid | + additionalLocalFlowStep(node, mid, config) and + nodeCand1(mid, toReturn, config) + ) + or + exists(Node mid | + jumpStep(node, mid, config) and + nodeCand1(mid, _, config) and + toReturn = false + ) + or + exists(Node mid | + additionalJumpStep(node, mid, config) and + nodeCand1(mid, _, config) and + toReturn = false + ) + or + // store + exists(Content f | + nodeCand1Store(f, node, toReturn, config) and + readCand1(f, config) + ) + or + // read + exists(Node mid, Content f | + readDirect(node, f, mid) and + storeCandFwd1(f, unbind(config)) and + nodeCand1(mid, toReturn, config) + ) + or + // flow into a callable + exists(DataFlowCall call | + nodeCand1Arg(call, node, false, config) and + toReturn = false or - exists(Node mid | - additionalLocalFlowStep(node, mid, config) and - nodeCand1(mid, config) - ) - or - exists(Node mid | - jumpStep(node, mid, config) and - nodeCand1(mid, config) - ) - or - exists(Node mid | - additionalJumpStep(node, mid, config) and - nodeCand1(mid, config) - ) - or - // store - exists(Content f | - nodeCand1Store(f, node, config) and - readCand1(f, config) - ) - or - // read - exists(Node mid, Content f | - readDirect(node, f, mid) and - storeCandFwd1(f, unbind(config)) and - nodeCand1(mid, config) - ) - or - // flow into a callable - exists(Node param | - viableParamArg(_, param, node) and - nodeCand1(param, config) - ) - or - // flow out of a callable - exists(ReturnPosition pos | - nodeCand1ReturnPosition(pos, config) and - getReturnPosition(node) = pos - ) + nodeCand1ArgToReturn(call, node, config) and + flowInCand1(call, toReturn, config) + ) + or + // flow out of a callable + exists(ReturnPosition pos | + nodeCand1ReturnPosition(pos, config) and + getReturnPosition(node) = pos and + toReturn = true ) } -pragma[noinline] +pragma[nomagic] +private predicate nodeCand1(Node node, Configuration config) { nodeCand1(node, _, config) } + +pragma[nomagic] private predicate nodeCand1ReturnPosition(ReturnPosition pos, Configuration config) { exists(DataFlowCall call, ReturnKindExt kind, Node out | - nodeCand1(out, config) and + nodeCand1(out, _, config) and pos = viableReturnPos(call, kind) and out = kind.getAnOutNode(call) ) @@ -418,21 +477,21 @@ private predicate nodeCand1ReturnPosition(ReturnPosition pos, Configuration conf /** * Holds if `f` is the target of a read in the flow covered by `nodeCand1`. */ -pragma[noinline] +pragma[nomagic] private predicate readCand1(Content f, Configuration config) { exists(Node mid, Node node | useFieldFlow(config) and nodeCandFwd1(node, unbind(config)) and readDirect(node, f, mid) and storeCandFwd1(f, unbind(config)) and - nodeCand1(mid, config) + nodeCand1(mid, _, config) ) } pragma[nomagic] -private predicate nodeCand1Store(Content f, Node node, Configuration config) { +private predicate nodeCand1Store(Content f, Node node, boolean toReturn, Configuration config) { exists(Node mid | - nodeCand1(mid, config) and + nodeCand1(mid, toReturn, config) and storeCandFwd1(f, unbind(config)) and storeDirect(node, f, mid) ) @@ -444,11 +503,45 @@ private predicate nodeCand1Store(Content f, Node node, Configuration config) { */ private predicate readStoreCand1(Content f, Configuration conf) { readCand1(f, conf) and - nodeCand1Store(f, _, conf) + nodeCand1Store(f, _, _, conf) +} + +pragma[nomagic] +private predicate viableParamArgCandFwd1( + DataFlowCall call, ParameterNode p, ArgumentNode arg, Configuration config +) { + viableParamArg(call, p, arg) and + nodeCandFwd1(arg, config) +} + +pragma[nomagic] +private predicate nodeCand1Arg( + DataFlowCall call, ArgumentNode arg, boolean toReturn, Configuration config +) { + exists(ParameterNode p | + nodeCand1(p, toReturn, config) and + viableParamArgCandFwd1(call, p, arg, config) + ) +} + +pragma[nomagic] +private predicate nodeCand1ArgToReturn(DataFlowCall call, ArgumentNode arg, Configuration config) { + nodeCand1Arg(call, arg, true, config) +} + +/** + * Holds if an output from `call` is reached in the flow covered by `nodeCand1`. + */ +pragma[nomagic] +private predicate flowInCand1(DataFlowCall call, boolean toReturn, Configuration config) { + exists(Node out | + nodeCand1(out, toReturn, config) and + nodeCandFwd1OutFromArg(call, out, config) + ) } private predicate throughFlowNodeCand(Node node, Configuration config) { - nodeCand1(node, config) and + nodeCand1(node, true, config) and not fullBarrier(node, config) and not inBarrier(node, config) and not outBarrier(node, config) @@ -2257,13 +2350,12 @@ private class PathNodeSink extends PathNodeImpl, TPathNodeSink { * a callable is recorded by `cc`. */ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCtx sc, AccessPath ap) { - exists(LocalCallContext localCC, AccessPath ap0, Node midnode, Configuration conf | - midnode = mid.getNode() and - conf = mid.getConfiguration() and - cc = mid.getCallContext() and - sc = mid.getSummaryCtx() and - localCC = getLocalCallContext(cc, midnode.getEnclosingCallable()) and - ap0 = mid.getAp() + exists( + AccessPath ap0, Node midnode, Configuration conf, DataFlowCallable enclosing, + LocalCallContext localCC + | + pathIntoLocalStep(mid, midnode, cc, enclosing, sc, ap0, conf) and + localCC = getLocalCallContext(cc, enclosing) | localFlowBigStep(midnode, node, true, conf, localCC) and ap = ap0 @@ -2297,6 +2389,20 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt pathThroughCallable(mid, node, cc, ap) and sc = mid.getSummaryCtx() } +pragma[nomagic] +private predicate pathIntoLocalStep( + PathNodeMid mid, Node midnode, CallContext cc, DataFlowCallable enclosing, SummaryCtx sc, + AccessPath ap0, Configuration conf +) { + midnode = mid.getNode() and + cc = mid.getCallContext() and + conf = mid.getConfiguration() and + localFlowBigStep(midnode, _, _, conf, _) and + enclosing = midnode.getEnclosingCallable() and + sc = mid.getSummaryCtx() and + ap0 = mid.getAp() +} + pragma[nomagic] private predicate readCand(Node node1, Content f, Node node2, Configuration config) { readDirect(node1, f, node2) and diff --git a/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl2.qll b/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl2.qll index 86d44e4fcce..5eb98aefe4e 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl2.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl2.qll @@ -259,44 +259,47 @@ private ReturnPosition viableReturnPos(DataFlowCall call, ReturnKindExt kind) { /** * Holds if `node` is reachable from a source in the given configuration - * ignoring call contexts. + * taking simple call contexts into consideration. */ -private predicate nodeCandFwd1(Node node, Configuration config) { +private predicate nodeCandFwd1(Node node, boolean fromArg, Configuration config) { not fullBarrier(node, config) and ( - config.isSource(node) + config.isSource(node) and + fromArg = false or exists(Node mid | - nodeCandFwd1(mid, config) and + nodeCandFwd1(mid, fromArg, config) and localFlowStep(mid, node, config) ) or exists(Node mid | - nodeCandFwd1(mid, config) and + nodeCandFwd1(mid, fromArg, config) and additionalLocalFlowStep(mid, node, config) ) or exists(Node mid | nodeCandFwd1(mid, config) and - jumpStep(mid, node, config) + jumpStep(mid, node, config) and + fromArg = false ) or exists(Node mid | nodeCandFwd1(mid, config) and - additionalJumpStep(mid, node, config) + additionalJumpStep(mid, node, config) and + fromArg = false ) or // store exists(Node mid | useFieldFlow(config) and - nodeCandFwd1(mid, config) and + nodeCandFwd1(mid, fromArg, config) and storeDirect(mid, _, node) and not outBarrier(mid, config) ) or // read exists(Content f | - nodeCandFwd1Read(f, node, config) and + nodeCandFwd1Read(f, node, fromArg, config) and storeCandFwd1(f, config) and not inBarrier(node, config) ) @@ -304,30 +307,37 @@ private predicate nodeCandFwd1(Node node, Configuration config) { // flow into a callable exists(Node arg | nodeCandFwd1(arg, config) and - viableParamArg(_, node, arg) + viableParamArg(_, node, arg) and + fromArg = true ) or // flow out of a callable - exists(DataFlowCall call, ReturnPosition pos, ReturnKindExt kind | - nodeCandFwd1ReturnPosition(pos, config) and - pos = viableReturnPos(call, kind) and - node = kind.getAnOutNode(call) + exists(DataFlowCall call | + nodeCandFwd1Out(call, node, false, config) and + fromArg = false + or + nodeCandFwd1OutFromArg(call, node, config) and + flowOutCandFwd1(call, fromArg, config) ) ) } -pragma[noinline] -private predicate nodeCandFwd1ReturnPosition(ReturnPosition pos, Configuration config) { +private predicate nodeCandFwd1(Node node, Configuration config) { nodeCandFwd1(node, _, config) } + +pragma[nomagic] +private predicate nodeCandFwd1ReturnPosition( + ReturnPosition pos, boolean fromArg, Configuration config +) { exists(ReturnNodeExt ret | - nodeCandFwd1(ret, config) and + nodeCandFwd1(ret, fromArg, config) and getReturnPosition(ret) = pos ) } pragma[nomagic] -private predicate nodeCandFwd1Read(Content f, Node node, Configuration config) { +private predicate nodeCandFwd1Read(Content f, Node node, boolean fromArg, Configuration config) { exists(Node mid | - nodeCandFwd1(mid, config) and + nodeCandFwd1(mid, fromArg, config) and readDirect(mid, f, node) ) } @@ -335,7 +345,7 @@ private predicate nodeCandFwd1Read(Content f, Node node, Configuration config) { /** * Holds if `f` is the target of a store in the flow covered by `nodeCandFwd1`. */ -pragma[noinline] +pragma[nomagic] private predicate storeCandFwd1(Content f, Configuration config) { exists(Node mid, Node node | not fullBarrier(node, config) and @@ -345,71 +355,120 @@ private predicate storeCandFwd1(Content f, Configuration config) { ) } +pragma[nomagic] +private predicate nodeCandFwd1ReturnKind( + DataFlowCall call, ReturnKindExt kind, boolean fromArg, Configuration config +) { + exists(ReturnPosition pos | + nodeCandFwd1ReturnPosition(pos, fromArg, config) and + pos = viableReturnPos(call, kind) + ) +} + +pragma[nomagic] +private predicate nodeCandFwd1Out( + DataFlowCall call, Node node, boolean fromArg, Configuration config +) { + exists(ReturnKindExt kind | + nodeCandFwd1ReturnKind(call, kind, fromArg, config) and + node = kind.getAnOutNode(call) + ) +} + +pragma[nomagic] +private predicate nodeCandFwd1OutFromArg(DataFlowCall call, Node node, Configuration config) { + nodeCandFwd1Out(call, node, true, config) +} + +/** + * Holds if an argument to `call` is reached in the flow covered by `nodeCandFwd1`. + */ +pragma[nomagic] +private predicate flowOutCandFwd1(DataFlowCall call, boolean fromArg, Configuration config) { + exists(ArgumentNode arg | + nodeCandFwd1(arg, fromArg, config) and + viableParamArg(call, _, arg) + ) +} + bindingset[result, b] private boolean unbindBool(boolean b) { result != b.booleanNot() } /** * Holds if `node` is part of a path from a source to a sink in the given - * configuration ignoring call contexts. + * configuration taking simple call contexts into consideration. */ pragma[nomagic] -private predicate nodeCand1(Node node, Configuration config) { +private predicate nodeCand1(Node node, boolean toReturn, Configuration config) { + nodeCand1_0(node, toReturn, config) and + nodeCandFwd1(node, config) +} + +pragma[nomagic] +private predicate nodeCand1_0(Node node, boolean toReturn, Configuration config) { nodeCandFwd1(node, config) and - config.isSink(node) + config.isSink(node) and + toReturn = false or - nodeCandFwd1(node, unbind(config)) and - ( - exists(Node mid | - localFlowStep(node, mid, config) and - nodeCand1(mid, config) - ) + exists(Node mid | + localFlowStep(node, mid, config) and + nodeCand1(mid, toReturn, config) + ) + or + exists(Node mid | + additionalLocalFlowStep(node, mid, config) and + nodeCand1(mid, toReturn, config) + ) + or + exists(Node mid | + jumpStep(node, mid, config) and + nodeCand1(mid, _, config) and + toReturn = false + ) + or + exists(Node mid | + additionalJumpStep(node, mid, config) and + nodeCand1(mid, _, config) and + toReturn = false + ) + or + // store + exists(Content f | + nodeCand1Store(f, node, toReturn, config) and + readCand1(f, config) + ) + or + // read + exists(Node mid, Content f | + readDirect(node, f, mid) and + storeCandFwd1(f, unbind(config)) and + nodeCand1(mid, toReturn, config) + ) + or + // flow into a callable + exists(DataFlowCall call | + nodeCand1Arg(call, node, false, config) and + toReturn = false or - exists(Node mid | - additionalLocalFlowStep(node, mid, config) and - nodeCand1(mid, config) - ) - or - exists(Node mid | - jumpStep(node, mid, config) and - nodeCand1(mid, config) - ) - or - exists(Node mid | - additionalJumpStep(node, mid, config) and - nodeCand1(mid, config) - ) - or - // store - exists(Content f | - nodeCand1Store(f, node, config) and - readCand1(f, config) - ) - or - // read - exists(Node mid, Content f | - readDirect(node, f, mid) and - storeCandFwd1(f, unbind(config)) and - nodeCand1(mid, config) - ) - or - // flow into a callable - exists(Node param | - viableParamArg(_, param, node) and - nodeCand1(param, config) - ) - or - // flow out of a callable - exists(ReturnPosition pos | - nodeCand1ReturnPosition(pos, config) and - getReturnPosition(node) = pos - ) + nodeCand1ArgToReturn(call, node, config) and + flowInCand1(call, toReturn, config) + ) + or + // flow out of a callable + exists(ReturnPosition pos | + nodeCand1ReturnPosition(pos, config) and + getReturnPosition(node) = pos and + toReturn = true ) } -pragma[noinline] +pragma[nomagic] +private predicate nodeCand1(Node node, Configuration config) { nodeCand1(node, _, config) } + +pragma[nomagic] private predicate nodeCand1ReturnPosition(ReturnPosition pos, Configuration config) { exists(DataFlowCall call, ReturnKindExt kind, Node out | - nodeCand1(out, config) and + nodeCand1(out, _, config) and pos = viableReturnPos(call, kind) and out = kind.getAnOutNode(call) ) @@ -418,21 +477,21 @@ private predicate nodeCand1ReturnPosition(ReturnPosition pos, Configuration conf /** * Holds if `f` is the target of a read in the flow covered by `nodeCand1`. */ -pragma[noinline] +pragma[nomagic] private predicate readCand1(Content f, Configuration config) { exists(Node mid, Node node | useFieldFlow(config) and nodeCandFwd1(node, unbind(config)) and readDirect(node, f, mid) and storeCandFwd1(f, unbind(config)) and - nodeCand1(mid, config) + nodeCand1(mid, _, config) ) } pragma[nomagic] -private predicate nodeCand1Store(Content f, Node node, Configuration config) { +private predicate nodeCand1Store(Content f, Node node, boolean toReturn, Configuration config) { exists(Node mid | - nodeCand1(mid, config) and + nodeCand1(mid, toReturn, config) and storeCandFwd1(f, unbind(config)) and storeDirect(node, f, mid) ) @@ -444,11 +503,45 @@ private predicate nodeCand1Store(Content f, Node node, Configuration config) { */ private predicate readStoreCand1(Content f, Configuration conf) { readCand1(f, conf) and - nodeCand1Store(f, _, conf) + nodeCand1Store(f, _, _, conf) +} + +pragma[nomagic] +private predicate viableParamArgCandFwd1( + DataFlowCall call, ParameterNode p, ArgumentNode arg, Configuration config +) { + viableParamArg(call, p, arg) and + nodeCandFwd1(arg, config) +} + +pragma[nomagic] +private predicate nodeCand1Arg( + DataFlowCall call, ArgumentNode arg, boolean toReturn, Configuration config +) { + exists(ParameterNode p | + nodeCand1(p, toReturn, config) and + viableParamArgCandFwd1(call, p, arg, config) + ) +} + +pragma[nomagic] +private predicate nodeCand1ArgToReturn(DataFlowCall call, ArgumentNode arg, Configuration config) { + nodeCand1Arg(call, arg, true, config) +} + +/** + * Holds if an output from `call` is reached in the flow covered by `nodeCand1`. + */ +pragma[nomagic] +private predicate flowInCand1(DataFlowCall call, boolean toReturn, Configuration config) { + exists(Node out | + nodeCand1(out, toReturn, config) and + nodeCandFwd1OutFromArg(call, out, config) + ) } private predicate throughFlowNodeCand(Node node, Configuration config) { - nodeCand1(node, config) and + nodeCand1(node, true, config) and not fullBarrier(node, config) and not inBarrier(node, config) and not outBarrier(node, config) @@ -2257,13 +2350,12 @@ private class PathNodeSink extends PathNodeImpl, TPathNodeSink { * a callable is recorded by `cc`. */ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCtx sc, AccessPath ap) { - exists(LocalCallContext localCC, AccessPath ap0, Node midnode, Configuration conf | - midnode = mid.getNode() and - conf = mid.getConfiguration() and - cc = mid.getCallContext() and - sc = mid.getSummaryCtx() and - localCC = getLocalCallContext(cc, midnode.getEnclosingCallable()) and - ap0 = mid.getAp() + exists( + AccessPath ap0, Node midnode, Configuration conf, DataFlowCallable enclosing, + LocalCallContext localCC + | + pathIntoLocalStep(mid, midnode, cc, enclosing, sc, ap0, conf) and + localCC = getLocalCallContext(cc, enclosing) | localFlowBigStep(midnode, node, true, conf, localCC) and ap = ap0 @@ -2297,6 +2389,20 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt pathThroughCallable(mid, node, cc, ap) and sc = mid.getSummaryCtx() } +pragma[nomagic] +private predicate pathIntoLocalStep( + PathNodeMid mid, Node midnode, CallContext cc, DataFlowCallable enclosing, SummaryCtx sc, + AccessPath ap0, Configuration conf +) { + midnode = mid.getNode() and + cc = mid.getCallContext() and + conf = mid.getConfiguration() and + localFlowBigStep(midnode, _, _, conf, _) and + enclosing = midnode.getEnclosingCallable() and + sc = mid.getSummaryCtx() and + ap0 = mid.getAp() +} + pragma[nomagic] private predicate readCand(Node node1, Content f, Node node2, Configuration config) { readDirect(node1, f, node2) and diff --git a/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl3.qll b/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl3.qll index 86d44e4fcce..5eb98aefe4e 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl3.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl3.qll @@ -259,44 +259,47 @@ private ReturnPosition viableReturnPos(DataFlowCall call, ReturnKindExt kind) { /** * Holds if `node` is reachable from a source in the given configuration - * ignoring call contexts. + * taking simple call contexts into consideration. */ -private predicate nodeCandFwd1(Node node, Configuration config) { +private predicate nodeCandFwd1(Node node, boolean fromArg, Configuration config) { not fullBarrier(node, config) and ( - config.isSource(node) + config.isSource(node) and + fromArg = false or exists(Node mid | - nodeCandFwd1(mid, config) and + nodeCandFwd1(mid, fromArg, config) and localFlowStep(mid, node, config) ) or exists(Node mid | - nodeCandFwd1(mid, config) and + nodeCandFwd1(mid, fromArg, config) and additionalLocalFlowStep(mid, node, config) ) or exists(Node mid | nodeCandFwd1(mid, config) and - jumpStep(mid, node, config) + jumpStep(mid, node, config) and + fromArg = false ) or exists(Node mid | nodeCandFwd1(mid, config) and - additionalJumpStep(mid, node, config) + additionalJumpStep(mid, node, config) and + fromArg = false ) or // store exists(Node mid | useFieldFlow(config) and - nodeCandFwd1(mid, config) and + nodeCandFwd1(mid, fromArg, config) and storeDirect(mid, _, node) and not outBarrier(mid, config) ) or // read exists(Content f | - nodeCandFwd1Read(f, node, config) and + nodeCandFwd1Read(f, node, fromArg, config) and storeCandFwd1(f, config) and not inBarrier(node, config) ) @@ -304,30 +307,37 @@ private predicate nodeCandFwd1(Node node, Configuration config) { // flow into a callable exists(Node arg | nodeCandFwd1(arg, config) and - viableParamArg(_, node, arg) + viableParamArg(_, node, arg) and + fromArg = true ) or // flow out of a callable - exists(DataFlowCall call, ReturnPosition pos, ReturnKindExt kind | - nodeCandFwd1ReturnPosition(pos, config) and - pos = viableReturnPos(call, kind) and - node = kind.getAnOutNode(call) + exists(DataFlowCall call | + nodeCandFwd1Out(call, node, false, config) and + fromArg = false + or + nodeCandFwd1OutFromArg(call, node, config) and + flowOutCandFwd1(call, fromArg, config) ) ) } -pragma[noinline] -private predicate nodeCandFwd1ReturnPosition(ReturnPosition pos, Configuration config) { +private predicate nodeCandFwd1(Node node, Configuration config) { nodeCandFwd1(node, _, config) } + +pragma[nomagic] +private predicate nodeCandFwd1ReturnPosition( + ReturnPosition pos, boolean fromArg, Configuration config +) { exists(ReturnNodeExt ret | - nodeCandFwd1(ret, config) and + nodeCandFwd1(ret, fromArg, config) and getReturnPosition(ret) = pos ) } pragma[nomagic] -private predicate nodeCandFwd1Read(Content f, Node node, Configuration config) { +private predicate nodeCandFwd1Read(Content f, Node node, boolean fromArg, Configuration config) { exists(Node mid | - nodeCandFwd1(mid, config) and + nodeCandFwd1(mid, fromArg, config) and readDirect(mid, f, node) ) } @@ -335,7 +345,7 @@ private predicate nodeCandFwd1Read(Content f, Node node, Configuration config) { /** * Holds if `f` is the target of a store in the flow covered by `nodeCandFwd1`. */ -pragma[noinline] +pragma[nomagic] private predicate storeCandFwd1(Content f, Configuration config) { exists(Node mid, Node node | not fullBarrier(node, config) and @@ -345,71 +355,120 @@ private predicate storeCandFwd1(Content f, Configuration config) { ) } +pragma[nomagic] +private predicate nodeCandFwd1ReturnKind( + DataFlowCall call, ReturnKindExt kind, boolean fromArg, Configuration config +) { + exists(ReturnPosition pos | + nodeCandFwd1ReturnPosition(pos, fromArg, config) and + pos = viableReturnPos(call, kind) + ) +} + +pragma[nomagic] +private predicate nodeCandFwd1Out( + DataFlowCall call, Node node, boolean fromArg, Configuration config +) { + exists(ReturnKindExt kind | + nodeCandFwd1ReturnKind(call, kind, fromArg, config) and + node = kind.getAnOutNode(call) + ) +} + +pragma[nomagic] +private predicate nodeCandFwd1OutFromArg(DataFlowCall call, Node node, Configuration config) { + nodeCandFwd1Out(call, node, true, config) +} + +/** + * Holds if an argument to `call` is reached in the flow covered by `nodeCandFwd1`. + */ +pragma[nomagic] +private predicate flowOutCandFwd1(DataFlowCall call, boolean fromArg, Configuration config) { + exists(ArgumentNode arg | + nodeCandFwd1(arg, fromArg, config) and + viableParamArg(call, _, arg) + ) +} + bindingset[result, b] private boolean unbindBool(boolean b) { result != b.booleanNot() } /** * Holds if `node` is part of a path from a source to a sink in the given - * configuration ignoring call contexts. + * configuration taking simple call contexts into consideration. */ pragma[nomagic] -private predicate nodeCand1(Node node, Configuration config) { +private predicate nodeCand1(Node node, boolean toReturn, Configuration config) { + nodeCand1_0(node, toReturn, config) and + nodeCandFwd1(node, config) +} + +pragma[nomagic] +private predicate nodeCand1_0(Node node, boolean toReturn, Configuration config) { nodeCandFwd1(node, config) and - config.isSink(node) + config.isSink(node) and + toReturn = false or - nodeCandFwd1(node, unbind(config)) and - ( - exists(Node mid | - localFlowStep(node, mid, config) and - nodeCand1(mid, config) - ) + exists(Node mid | + localFlowStep(node, mid, config) and + nodeCand1(mid, toReturn, config) + ) + or + exists(Node mid | + additionalLocalFlowStep(node, mid, config) and + nodeCand1(mid, toReturn, config) + ) + or + exists(Node mid | + jumpStep(node, mid, config) and + nodeCand1(mid, _, config) and + toReturn = false + ) + or + exists(Node mid | + additionalJumpStep(node, mid, config) and + nodeCand1(mid, _, config) and + toReturn = false + ) + or + // store + exists(Content f | + nodeCand1Store(f, node, toReturn, config) and + readCand1(f, config) + ) + or + // read + exists(Node mid, Content f | + readDirect(node, f, mid) and + storeCandFwd1(f, unbind(config)) and + nodeCand1(mid, toReturn, config) + ) + or + // flow into a callable + exists(DataFlowCall call | + nodeCand1Arg(call, node, false, config) and + toReturn = false or - exists(Node mid | - additionalLocalFlowStep(node, mid, config) and - nodeCand1(mid, config) - ) - or - exists(Node mid | - jumpStep(node, mid, config) and - nodeCand1(mid, config) - ) - or - exists(Node mid | - additionalJumpStep(node, mid, config) and - nodeCand1(mid, config) - ) - or - // store - exists(Content f | - nodeCand1Store(f, node, config) and - readCand1(f, config) - ) - or - // read - exists(Node mid, Content f | - readDirect(node, f, mid) and - storeCandFwd1(f, unbind(config)) and - nodeCand1(mid, config) - ) - or - // flow into a callable - exists(Node param | - viableParamArg(_, param, node) and - nodeCand1(param, config) - ) - or - // flow out of a callable - exists(ReturnPosition pos | - nodeCand1ReturnPosition(pos, config) and - getReturnPosition(node) = pos - ) + nodeCand1ArgToReturn(call, node, config) and + flowInCand1(call, toReturn, config) + ) + or + // flow out of a callable + exists(ReturnPosition pos | + nodeCand1ReturnPosition(pos, config) and + getReturnPosition(node) = pos and + toReturn = true ) } -pragma[noinline] +pragma[nomagic] +private predicate nodeCand1(Node node, Configuration config) { nodeCand1(node, _, config) } + +pragma[nomagic] private predicate nodeCand1ReturnPosition(ReturnPosition pos, Configuration config) { exists(DataFlowCall call, ReturnKindExt kind, Node out | - nodeCand1(out, config) and + nodeCand1(out, _, config) and pos = viableReturnPos(call, kind) and out = kind.getAnOutNode(call) ) @@ -418,21 +477,21 @@ private predicate nodeCand1ReturnPosition(ReturnPosition pos, Configuration conf /** * Holds if `f` is the target of a read in the flow covered by `nodeCand1`. */ -pragma[noinline] +pragma[nomagic] private predicate readCand1(Content f, Configuration config) { exists(Node mid, Node node | useFieldFlow(config) and nodeCandFwd1(node, unbind(config)) and readDirect(node, f, mid) and storeCandFwd1(f, unbind(config)) and - nodeCand1(mid, config) + nodeCand1(mid, _, config) ) } pragma[nomagic] -private predicate nodeCand1Store(Content f, Node node, Configuration config) { +private predicate nodeCand1Store(Content f, Node node, boolean toReturn, Configuration config) { exists(Node mid | - nodeCand1(mid, config) and + nodeCand1(mid, toReturn, config) and storeCandFwd1(f, unbind(config)) and storeDirect(node, f, mid) ) @@ -444,11 +503,45 @@ private predicate nodeCand1Store(Content f, Node node, Configuration config) { */ private predicate readStoreCand1(Content f, Configuration conf) { readCand1(f, conf) and - nodeCand1Store(f, _, conf) + nodeCand1Store(f, _, _, conf) +} + +pragma[nomagic] +private predicate viableParamArgCandFwd1( + DataFlowCall call, ParameterNode p, ArgumentNode arg, Configuration config +) { + viableParamArg(call, p, arg) and + nodeCandFwd1(arg, config) +} + +pragma[nomagic] +private predicate nodeCand1Arg( + DataFlowCall call, ArgumentNode arg, boolean toReturn, Configuration config +) { + exists(ParameterNode p | + nodeCand1(p, toReturn, config) and + viableParamArgCandFwd1(call, p, arg, config) + ) +} + +pragma[nomagic] +private predicate nodeCand1ArgToReturn(DataFlowCall call, ArgumentNode arg, Configuration config) { + nodeCand1Arg(call, arg, true, config) +} + +/** + * Holds if an output from `call` is reached in the flow covered by `nodeCand1`. + */ +pragma[nomagic] +private predicate flowInCand1(DataFlowCall call, boolean toReturn, Configuration config) { + exists(Node out | + nodeCand1(out, toReturn, config) and + nodeCandFwd1OutFromArg(call, out, config) + ) } private predicate throughFlowNodeCand(Node node, Configuration config) { - nodeCand1(node, config) and + nodeCand1(node, true, config) and not fullBarrier(node, config) and not inBarrier(node, config) and not outBarrier(node, config) @@ -2257,13 +2350,12 @@ private class PathNodeSink extends PathNodeImpl, TPathNodeSink { * a callable is recorded by `cc`. */ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCtx sc, AccessPath ap) { - exists(LocalCallContext localCC, AccessPath ap0, Node midnode, Configuration conf | - midnode = mid.getNode() and - conf = mid.getConfiguration() and - cc = mid.getCallContext() and - sc = mid.getSummaryCtx() and - localCC = getLocalCallContext(cc, midnode.getEnclosingCallable()) and - ap0 = mid.getAp() + exists( + AccessPath ap0, Node midnode, Configuration conf, DataFlowCallable enclosing, + LocalCallContext localCC + | + pathIntoLocalStep(mid, midnode, cc, enclosing, sc, ap0, conf) and + localCC = getLocalCallContext(cc, enclosing) | localFlowBigStep(midnode, node, true, conf, localCC) and ap = ap0 @@ -2297,6 +2389,20 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt pathThroughCallable(mid, node, cc, ap) and sc = mid.getSummaryCtx() } +pragma[nomagic] +private predicate pathIntoLocalStep( + PathNodeMid mid, Node midnode, CallContext cc, DataFlowCallable enclosing, SummaryCtx sc, + AccessPath ap0, Configuration conf +) { + midnode = mid.getNode() and + cc = mid.getCallContext() and + conf = mid.getConfiguration() and + localFlowBigStep(midnode, _, _, conf, _) and + enclosing = midnode.getEnclosingCallable() and + sc = mid.getSummaryCtx() and + ap0 = mid.getAp() +} + pragma[nomagic] private predicate readCand(Node node1, Content f, Node node2, Configuration config) { readDirect(node1, f, node2) and diff --git a/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl4.qll b/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl4.qll index 86d44e4fcce..5eb98aefe4e 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl4.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl4.qll @@ -259,44 +259,47 @@ private ReturnPosition viableReturnPos(DataFlowCall call, ReturnKindExt kind) { /** * Holds if `node` is reachable from a source in the given configuration - * ignoring call contexts. + * taking simple call contexts into consideration. */ -private predicate nodeCandFwd1(Node node, Configuration config) { +private predicate nodeCandFwd1(Node node, boolean fromArg, Configuration config) { not fullBarrier(node, config) and ( - config.isSource(node) + config.isSource(node) and + fromArg = false or exists(Node mid | - nodeCandFwd1(mid, config) and + nodeCandFwd1(mid, fromArg, config) and localFlowStep(mid, node, config) ) or exists(Node mid | - nodeCandFwd1(mid, config) and + nodeCandFwd1(mid, fromArg, config) and additionalLocalFlowStep(mid, node, config) ) or exists(Node mid | nodeCandFwd1(mid, config) and - jumpStep(mid, node, config) + jumpStep(mid, node, config) and + fromArg = false ) or exists(Node mid | nodeCandFwd1(mid, config) and - additionalJumpStep(mid, node, config) + additionalJumpStep(mid, node, config) and + fromArg = false ) or // store exists(Node mid | useFieldFlow(config) and - nodeCandFwd1(mid, config) and + nodeCandFwd1(mid, fromArg, config) and storeDirect(mid, _, node) and not outBarrier(mid, config) ) or // read exists(Content f | - nodeCandFwd1Read(f, node, config) and + nodeCandFwd1Read(f, node, fromArg, config) and storeCandFwd1(f, config) and not inBarrier(node, config) ) @@ -304,30 +307,37 @@ private predicate nodeCandFwd1(Node node, Configuration config) { // flow into a callable exists(Node arg | nodeCandFwd1(arg, config) and - viableParamArg(_, node, arg) + viableParamArg(_, node, arg) and + fromArg = true ) or // flow out of a callable - exists(DataFlowCall call, ReturnPosition pos, ReturnKindExt kind | - nodeCandFwd1ReturnPosition(pos, config) and - pos = viableReturnPos(call, kind) and - node = kind.getAnOutNode(call) + exists(DataFlowCall call | + nodeCandFwd1Out(call, node, false, config) and + fromArg = false + or + nodeCandFwd1OutFromArg(call, node, config) and + flowOutCandFwd1(call, fromArg, config) ) ) } -pragma[noinline] -private predicate nodeCandFwd1ReturnPosition(ReturnPosition pos, Configuration config) { +private predicate nodeCandFwd1(Node node, Configuration config) { nodeCandFwd1(node, _, config) } + +pragma[nomagic] +private predicate nodeCandFwd1ReturnPosition( + ReturnPosition pos, boolean fromArg, Configuration config +) { exists(ReturnNodeExt ret | - nodeCandFwd1(ret, config) and + nodeCandFwd1(ret, fromArg, config) and getReturnPosition(ret) = pos ) } pragma[nomagic] -private predicate nodeCandFwd1Read(Content f, Node node, Configuration config) { +private predicate nodeCandFwd1Read(Content f, Node node, boolean fromArg, Configuration config) { exists(Node mid | - nodeCandFwd1(mid, config) and + nodeCandFwd1(mid, fromArg, config) and readDirect(mid, f, node) ) } @@ -335,7 +345,7 @@ private predicate nodeCandFwd1Read(Content f, Node node, Configuration config) { /** * Holds if `f` is the target of a store in the flow covered by `nodeCandFwd1`. */ -pragma[noinline] +pragma[nomagic] private predicate storeCandFwd1(Content f, Configuration config) { exists(Node mid, Node node | not fullBarrier(node, config) and @@ -345,71 +355,120 @@ private predicate storeCandFwd1(Content f, Configuration config) { ) } +pragma[nomagic] +private predicate nodeCandFwd1ReturnKind( + DataFlowCall call, ReturnKindExt kind, boolean fromArg, Configuration config +) { + exists(ReturnPosition pos | + nodeCandFwd1ReturnPosition(pos, fromArg, config) and + pos = viableReturnPos(call, kind) + ) +} + +pragma[nomagic] +private predicate nodeCandFwd1Out( + DataFlowCall call, Node node, boolean fromArg, Configuration config +) { + exists(ReturnKindExt kind | + nodeCandFwd1ReturnKind(call, kind, fromArg, config) and + node = kind.getAnOutNode(call) + ) +} + +pragma[nomagic] +private predicate nodeCandFwd1OutFromArg(DataFlowCall call, Node node, Configuration config) { + nodeCandFwd1Out(call, node, true, config) +} + +/** + * Holds if an argument to `call` is reached in the flow covered by `nodeCandFwd1`. + */ +pragma[nomagic] +private predicate flowOutCandFwd1(DataFlowCall call, boolean fromArg, Configuration config) { + exists(ArgumentNode arg | + nodeCandFwd1(arg, fromArg, config) and + viableParamArg(call, _, arg) + ) +} + bindingset[result, b] private boolean unbindBool(boolean b) { result != b.booleanNot() } /** * Holds if `node` is part of a path from a source to a sink in the given - * configuration ignoring call contexts. + * configuration taking simple call contexts into consideration. */ pragma[nomagic] -private predicate nodeCand1(Node node, Configuration config) { +private predicate nodeCand1(Node node, boolean toReturn, Configuration config) { + nodeCand1_0(node, toReturn, config) and + nodeCandFwd1(node, config) +} + +pragma[nomagic] +private predicate nodeCand1_0(Node node, boolean toReturn, Configuration config) { nodeCandFwd1(node, config) and - config.isSink(node) + config.isSink(node) and + toReturn = false or - nodeCandFwd1(node, unbind(config)) and - ( - exists(Node mid | - localFlowStep(node, mid, config) and - nodeCand1(mid, config) - ) + exists(Node mid | + localFlowStep(node, mid, config) and + nodeCand1(mid, toReturn, config) + ) + or + exists(Node mid | + additionalLocalFlowStep(node, mid, config) and + nodeCand1(mid, toReturn, config) + ) + or + exists(Node mid | + jumpStep(node, mid, config) and + nodeCand1(mid, _, config) and + toReturn = false + ) + or + exists(Node mid | + additionalJumpStep(node, mid, config) and + nodeCand1(mid, _, config) and + toReturn = false + ) + or + // store + exists(Content f | + nodeCand1Store(f, node, toReturn, config) and + readCand1(f, config) + ) + or + // read + exists(Node mid, Content f | + readDirect(node, f, mid) and + storeCandFwd1(f, unbind(config)) and + nodeCand1(mid, toReturn, config) + ) + or + // flow into a callable + exists(DataFlowCall call | + nodeCand1Arg(call, node, false, config) and + toReturn = false or - exists(Node mid | - additionalLocalFlowStep(node, mid, config) and - nodeCand1(mid, config) - ) - or - exists(Node mid | - jumpStep(node, mid, config) and - nodeCand1(mid, config) - ) - or - exists(Node mid | - additionalJumpStep(node, mid, config) and - nodeCand1(mid, config) - ) - or - // store - exists(Content f | - nodeCand1Store(f, node, config) and - readCand1(f, config) - ) - or - // read - exists(Node mid, Content f | - readDirect(node, f, mid) and - storeCandFwd1(f, unbind(config)) and - nodeCand1(mid, config) - ) - or - // flow into a callable - exists(Node param | - viableParamArg(_, param, node) and - nodeCand1(param, config) - ) - or - // flow out of a callable - exists(ReturnPosition pos | - nodeCand1ReturnPosition(pos, config) and - getReturnPosition(node) = pos - ) + nodeCand1ArgToReturn(call, node, config) and + flowInCand1(call, toReturn, config) + ) + or + // flow out of a callable + exists(ReturnPosition pos | + nodeCand1ReturnPosition(pos, config) and + getReturnPosition(node) = pos and + toReturn = true ) } -pragma[noinline] +pragma[nomagic] +private predicate nodeCand1(Node node, Configuration config) { nodeCand1(node, _, config) } + +pragma[nomagic] private predicate nodeCand1ReturnPosition(ReturnPosition pos, Configuration config) { exists(DataFlowCall call, ReturnKindExt kind, Node out | - nodeCand1(out, config) and + nodeCand1(out, _, config) and pos = viableReturnPos(call, kind) and out = kind.getAnOutNode(call) ) @@ -418,21 +477,21 @@ private predicate nodeCand1ReturnPosition(ReturnPosition pos, Configuration conf /** * Holds if `f` is the target of a read in the flow covered by `nodeCand1`. */ -pragma[noinline] +pragma[nomagic] private predicate readCand1(Content f, Configuration config) { exists(Node mid, Node node | useFieldFlow(config) and nodeCandFwd1(node, unbind(config)) and readDirect(node, f, mid) and storeCandFwd1(f, unbind(config)) and - nodeCand1(mid, config) + nodeCand1(mid, _, config) ) } pragma[nomagic] -private predicate nodeCand1Store(Content f, Node node, Configuration config) { +private predicate nodeCand1Store(Content f, Node node, boolean toReturn, Configuration config) { exists(Node mid | - nodeCand1(mid, config) and + nodeCand1(mid, toReturn, config) and storeCandFwd1(f, unbind(config)) and storeDirect(node, f, mid) ) @@ -444,11 +503,45 @@ private predicate nodeCand1Store(Content f, Node node, Configuration config) { */ private predicate readStoreCand1(Content f, Configuration conf) { readCand1(f, conf) and - nodeCand1Store(f, _, conf) + nodeCand1Store(f, _, _, conf) +} + +pragma[nomagic] +private predicate viableParamArgCandFwd1( + DataFlowCall call, ParameterNode p, ArgumentNode arg, Configuration config +) { + viableParamArg(call, p, arg) and + nodeCandFwd1(arg, config) +} + +pragma[nomagic] +private predicate nodeCand1Arg( + DataFlowCall call, ArgumentNode arg, boolean toReturn, Configuration config +) { + exists(ParameterNode p | + nodeCand1(p, toReturn, config) and + viableParamArgCandFwd1(call, p, arg, config) + ) +} + +pragma[nomagic] +private predicate nodeCand1ArgToReturn(DataFlowCall call, ArgumentNode arg, Configuration config) { + nodeCand1Arg(call, arg, true, config) +} + +/** + * Holds if an output from `call` is reached in the flow covered by `nodeCand1`. + */ +pragma[nomagic] +private predicate flowInCand1(DataFlowCall call, boolean toReturn, Configuration config) { + exists(Node out | + nodeCand1(out, toReturn, config) and + nodeCandFwd1OutFromArg(call, out, config) + ) } private predicate throughFlowNodeCand(Node node, Configuration config) { - nodeCand1(node, config) and + nodeCand1(node, true, config) and not fullBarrier(node, config) and not inBarrier(node, config) and not outBarrier(node, config) @@ -2257,13 +2350,12 @@ private class PathNodeSink extends PathNodeImpl, TPathNodeSink { * a callable is recorded by `cc`. */ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCtx sc, AccessPath ap) { - exists(LocalCallContext localCC, AccessPath ap0, Node midnode, Configuration conf | - midnode = mid.getNode() and - conf = mid.getConfiguration() and - cc = mid.getCallContext() and - sc = mid.getSummaryCtx() and - localCC = getLocalCallContext(cc, midnode.getEnclosingCallable()) and - ap0 = mid.getAp() + exists( + AccessPath ap0, Node midnode, Configuration conf, DataFlowCallable enclosing, + LocalCallContext localCC + | + pathIntoLocalStep(mid, midnode, cc, enclosing, sc, ap0, conf) and + localCC = getLocalCallContext(cc, enclosing) | localFlowBigStep(midnode, node, true, conf, localCC) and ap = ap0 @@ -2297,6 +2389,20 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt pathThroughCallable(mid, node, cc, ap) and sc = mid.getSummaryCtx() } +pragma[nomagic] +private predicate pathIntoLocalStep( + PathNodeMid mid, Node midnode, CallContext cc, DataFlowCallable enclosing, SummaryCtx sc, + AccessPath ap0, Configuration conf +) { + midnode = mid.getNode() and + cc = mid.getCallContext() and + conf = mid.getConfiguration() and + localFlowBigStep(midnode, _, _, conf, _) and + enclosing = midnode.getEnclosingCallable() and + sc = mid.getSummaryCtx() and + ap0 = mid.getAp() +} + pragma[nomagic] private predicate readCand(Node node1, Content f, Node node2, Configuration config) { readDirect(node1, f, node2) and diff --git a/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowPrivate.qll b/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowPrivate.qll index d41a535998e..82694215bdc 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowPrivate.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowPrivate.qll @@ -122,16 +122,6 @@ OutNode getAnOutNode(DataFlowCall call, ReturnKind kind) { */ predicate jumpStep(Node n1, Node n2) { none() } -/** - * Holds if `call` passes an implicit or explicit qualifier, i.e., a - * `this` parameter. - */ -predicate callHasQualifier(Call call) { - call.hasQualifier() - or - call.getTarget() instanceof Destructor -} - private newtype TContent = TFieldContent(Field f) or TCollectionContent() or diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/Instruction.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/Instruction.qll index 0796c5468ca..52cf46a5f0e 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/Instruction.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/Instruction.qll @@ -266,6 +266,16 @@ module InstructionSanity { funcText = Language::getIdentityString(func.getFunction()) ) } + + query predicate switchInstructionWithoutDefaultEdge( + SwitchInstruction switchInstr, string message, IRFunction func, string funcText + ) { + not exists(switchInstr.getDefaultSuccessor()) and + message = + "SwitchInstruction " + switchInstr.toString() + " without a DefaultEdge in function '$@'." and + func = switchInstr.getEnclosingIRFunction() and + funcText = Language::getIdentityString(func.getFunction()) + } } /** diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/gvn/ValueNumbering.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/gvn/ValueNumbering.qll index 1575609fb2f..161f69936e9 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/gvn/ValueNumbering.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/gvn/ValueNumbering.qll @@ -27,19 +27,19 @@ class ValueNumber extends TValueNumber { final Language::Location getLocation() { if exists(Instruction i | - i = getAnInstruction() and not i.getLocation() instanceof UnknownLocation + i = getAnInstruction() and not i.getLocation() instanceof Language::UnknownLocation ) then result = min(Language::Location l | - l = getAnInstruction().getLocation() and not l instanceof UnknownLocation + l = getAnInstruction().getLocation() and not l instanceof Language::UnknownLocation | l order by l.getFile().getAbsolutePath(), l.getStartLine(), l.getStartColumn(), l.getEndLine(), l.getEndColumn() ) - else result instanceof UnknownDefaultLocation + else result instanceof Language::UnknownDefaultLocation } /** diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/gvn/internal/ValueNumberingImports.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/gvn/internal/ValueNumberingImports.qll index a5f4cfbf32d..8482a5e4b14 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/gvn/internal/ValueNumberingImports.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/gvn/internal/ValueNumberingImports.qll @@ -1,4 +1,3 @@ import semmle.code.cpp.ir.implementation.aliased_ssa.IR import semmle.code.cpp.ir.internal.Overlap import semmle.code.cpp.ir.internal.IRCppLanguage as Language -import semmle.code.cpp.Location diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/gvn/internal/ValueNumberingInternal.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/gvn/internal/ValueNumberingInternal.qll index 667f319a402..169b0ef7ccf 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/gvn/internal/ValueNumberingInternal.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/gvn/internal/ValueNumberingInternal.qll @@ -1,5 +1,4 @@ private import ValueNumberingImports -private import cpp newtype TValueNumber = TVariableAddressValueNumber(IRFunction irFunc, Language::AST ast) { @@ -15,7 +14,7 @@ newtype TValueNumber = TStringConstantValueNumber(IRFunction irFunc, IRType type, string value) { stringConstantValueNumber(_, irFunc, type, value) } or - TFieldAddressValueNumber(IRFunction irFunc, Field field, TValueNumber objectAddress) { + TFieldAddressValueNumber(IRFunction irFunc, Language::Field field, TValueNumber objectAddress) { fieldAddressValueNumber(_, irFunc, field, objectAddress) } or TBinaryValueNumber( @@ -33,7 +32,8 @@ newtype TValueNumber = unaryValueNumber(_, irFunc, opcode, operand) } or TInheritanceConversionValueNumber( - IRFunction irFunc, Opcode opcode, Class baseClass, Class derivedClass, TValueNumber operand + IRFunction irFunc, Opcode opcode, Language::Class baseClass, Language::Class derivedClass, + TValueNumber operand ) { inheritanceConversionValueNumber(_, irFunc, opcode, baseClass, derivedClass, operand) } or @@ -136,7 +136,7 @@ private predicate initializeThisValueNumber(InitializeThisInstruction instr, IRF instr.getEnclosingIRFunction() = irFunc } -predicate constantValueNumber( +private predicate constantValueNumber( ConstantInstruction instr, IRFunction irFunc, IRType type, string value ) { instr.getEnclosingIRFunction() = irFunc and diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/Instruction.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/Instruction.qll index 0796c5468ca..52cf46a5f0e 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/Instruction.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/Instruction.qll @@ -266,6 +266,16 @@ module InstructionSanity { funcText = Language::getIdentityString(func.getFunction()) ) } + + query predicate switchInstructionWithoutDefaultEdge( + SwitchInstruction switchInstr, string message, IRFunction func, string funcText + ) { + not exists(switchInstr.getDefaultSuccessor()) and + message = + "SwitchInstruction " + switchInstr.toString() + " without a DefaultEdge in function '$@'." and + func = switchInstr.getEnclosingIRFunction() and + funcText = Language::getIdentityString(func.getFunction()) + } } /** diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/gvn/ValueNumbering.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/gvn/ValueNumbering.qll index 1575609fb2f..161f69936e9 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/gvn/ValueNumbering.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/gvn/ValueNumbering.qll @@ -27,19 +27,19 @@ class ValueNumber extends TValueNumber { final Language::Location getLocation() { if exists(Instruction i | - i = getAnInstruction() and not i.getLocation() instanceof UnknownLocation + i = getAnInstruction() and not i.getLocation() instanceof Language::UnknownLocation ) then result = min(Language::Location l | - l = getAnInstruction().getLocation() and not l instanceof UnknownLocation + l = getAnInstruction().getLocation() and not l instanceof Language::UnknownLocation | l order by l.getFile().getAbsolutePath(), l.getStartLine(), l.getStartColumn(), l.getEndLine(), l.getEndColumn() ) - else result instanceof UnknownDefaultLocation + else result instanceof Language::UnknownDefaultLocation } /** diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/gvn/internal/ValueNumberingImports.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/gvn/internal/ValueNumberingImports.qll index a5f4cfbf32d..8482a5e4b14 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/gvn/internal/ValueNumberingImports.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/gvn/internal/ValueNumberingImports.qll @@ -1,4 +1,3 @@ import semmle.code.cpp.ir.implementation.aliased_ssa.IR import semmle.code.cpp.ir.internal.Overlap import semmle.code.cpp.ir.internal.IRCppLanguage as Language -import semmle.code.cpp.Location diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/gvn/internal/ValueNumberingInternal.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/gvn/internal/ValueNumberingInternal.qll index 667f319a402..169b0ef7ccf 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/gvn/internal/ValueNumberingInternal.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/gvn/internal/ValueNumberingInternal.qll @@ -1,5 +1,4 @@ private import ValueNumberingImports -private import cpp newtype TValueNumber = TVariableAddressValueNumber(IRFunction irFunc, Language::AST ast) { @@ -15,7 +14,7 @@ newtype TValueNumber = TStringConstantValueNumber(IRFunction irFunc, IRType type, string value) { stringConstantValueNumber(_, irFunc, type, value) } or - TFieldAddressValueNumber(IRFunction irFunc, Field field, TValueNumber objectAddress) { + TFieldAddressValueNumber(IRFunction irFunc, Language::Field field, TValueNumber objectAddress) { fieldAddressValueNumber(_, irFunc, field, objectAddress) } or TBinaryValueNumber( @@ -33,7 +32,8 @@ newtype TValueNumber = unaryValueNumber(_, irFunc, opcode, operand) } or TInheritanceConversionValueNumber( - IRFunction irFunc, Opcode opcode, Class baseClass, Class derivedClass, TValueNumber operand + IRFunction irFunc, Opcode opcode, Language::Class baseClass, Language::Class derivedClass, + TValueNumber operand ) { inheritanceConversionValueNumber(_, irFunc, opcode, baseClass, derivedClass, operand) } or @@ -136,7 +136,7 @@ private predicate initializeThisValueNumber(InitializeThisInstruction instr, IRF instr.getEnclosingIRFunction() = irFunc } -predicate constantValueNumber( +private predicate constantValueNumber( ConstantInstruction instr, IRFunction irFunc, IRType type, string value ) { instr.getEnclosingIRFunction() = irFunc and diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/internal/IRConstruction.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/internal/IRConstruction.qll index 849dbce0343..96ae480405a 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/internal/IRConstruction.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/internal/IRConstruction.qll @@ -102,6 +102,19 @@ private module Cached { result = getMemoryOperandDefinition(instr, _, _) } + /** + * Gets a non-phi instruction that defines an operand of `instr` but only if + * both `instr` and the result have neighbor on the other side of the edge + * between them. This is a necessary condition for being in a cycle, and it + * removes about two thirds of the tuples that would otherwise be in this + * predicate. + */ + private Instruction getNonPhiOperandDefOfIntermediate(Instruction instr) { + result = getNonPhiOperandDef(instr) and + exists(getNonPhiOperandDef(result)) and + instr = getNonPhiOperandDef(_) + } + /** * Holds if `instr` is part of a cycle in the operand graph that doesn't go * through a phi instruction and therefore should be impossible. @@ -115,7 +128,7 @@ private module Cached { cached predicate isInCycle(Instruction instr) { instr instanceof Instruction and - getNonPhiOperandDef+(instr) = instr + getNonPhiOperandDefOfIntermediate+(instr) = instr } cached diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/internal/TranslatedStmt.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/internal/TranslatedStmt.qll index 43727b3ff0a..88a7d4c99ea 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/internal/TranslatedStmt.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/internal/TranslatedStmt.qll @@ -655,6 +655,11 @@ class TranslatedSwitchStmt extends TranslatedStmt { kind = getCaseEdge(switchCase) and result = getTranslatedStmt(switchCase).getFirstInstruction() ) + or + not stmt.hasDefaultCase() and + tag = SwitchBranchTag() and + kind instanceof DefaultEdge and + result = getParent().getChildSuccessor(this) } override Instruction getChildSuccessor(TranslatedElement child) { diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/Instruction.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/Instruction.qll index 0796c5468ca..52cf46a5f0e 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/Instruction.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/Instruction.qll @@ -266,6 +266,16 @@ module InstructionSanity { funcText = Language::getIdentityString(func.getFunction()) ) } + + query predicate switchInstructionWithoutDefaultEdge( + SwitchInstruction switchInstr, string message, IRFunction func, string funcText + ) { + not exists(switchInstr.getDefaultSuccessor()) and + message = + "SwitchInstruction " + switchInstr.toString() + " without a DefaultEdge in function '$@'." and + func = switchInstr.getEnclosingIRFunction() and + funcText = Language::getIdentityString(func.getFunction()) + } } /** diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/gvn/ValueNumbering.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/gvn/ValueNumbering.qll index 1575609fb2f..161f69936e9 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/gvn/ValueNumbering.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/gvn/ValueNumbering.qll @@ -27,19 +27,19 @@ class ValueNumber extends TValueNumber { final Language::Location getLocation() { if exists(Instruction i | - i = getAnInstruction() and not i.getLocation() instanceof UnknownLocation + i = getAnInstruction() and not i.getLocation() instanceof Language::UnknownLocation ) then result = min(Language::Location l | - l = getAnInstruction().getLocation() and not l instanceof UnknownLocation + l = getAnInstruction().getLocation() and not l instanceof Language::UnknownLocation | l order by l.getFile().getAbsolutePath(), l.getStartLine(), l.getStartColumn(), l.getEndLine(), l.getEndColumn() ) - else result instanceof UnknownDefaultLocation + else result instanceof Language::UnknownDefaultLocation } /** diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/gvn/internal/ValueNumberingImports.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/gvn/internal/ValueNumberingImports.qll index a5f4cfbf32d..8482a5e4b14 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/gvn/internal/ValueNumberingImports.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/gvn/internal/ValueNumberingImports.qll @@ -1,4 +1,3 @@ import semmle.code.cpp.ir.implementation.aliased_ssa.IR import semmle.code.cpp.ir.internal.Overlap import semmle.code.cpp.ir.internal.IRCppLanguage as Language -import semmle.code.cpp.Location diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/gvn/internal/ValueNumberingInternal.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/gvn/internal/ValueNumberingInternal.qll index 667f319a402..169b0ef7ccf 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/gvn/internal/ValueNumberingInternal.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/gvn/internal/ValueNumberingInternal.qll @@ -1,5 +1,4 @@ private import ValueNumberingImports -private import cpp newtype TValueNumber = TVariableAddressValueNumber(IRFunction irFunc, Language::AST ast) { @@ -15,7 +14,7 @@ newtype TValueNumber = TStringConstantValueNumber(IRFunction irFunc, IRType type, string value) { stringConstantValueNumber(_, irFunc, type, value) } or - TFieldAddressValueNumber(IRFunction irFunc, Field field, TValueNumber objectAddress) { + TFieldAddressValueNumber(IRFunction irFunc, Language::Field field, TValueNumber objectAddress) { fieldAddressValueNumber(_, irFunc, field, objectAddress) } or TBinaryValueNumber( @@ -33,7 +32,8 @@ newtype TValueNumber = unaryValueNumber(_, irFunc, opcode, operand) } or TInheritanceConversionValueNumber( - IRFunction irFunc, Opcode opcode, Class baseClass, Class derivedClass, TValueNumber operand + IRFunction irFunc, Opcode opcode, Language::Class baseClass, Language::Class derivedClass, + TValueNumber operand ) { inheritanceConversionValueNumber(_, irFunc, opcode, baseClass, derivedClass, operand) } or @@ -136,7 +136,7 @@ private predicate initializeThisValueNumber(InitializeThisInstruction instr, IRF instr.getEnclosingIRFunction() = irFunc } -predicate constantValueNumber( +private predicate constantValueNumber( ConstantInstruction instr, IRFunction irFunc, IRType type, string value ) { instr.getEnclosingIRFunction() = irFunc and 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 3bde6c7d501..6e88e711711 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/internal/IRCppLanguage.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/internal/IRCppLanguage.qll @@ -13,6 +13,10 @@ class Function = Cpp::Function; class Location = Cpp::Location; +class UnknownLocation = Cpp::UnknownLocation; + +class UnknownDefaultLocation = Cpp::UnknownDefaultLocation; + class File = Cpp::File; class AST = Cpp::Locatable; diff --git a/cpp/ql/src/semmle/code/cpp/models/implementations/Pure.qll b/cpp/ql/src/semmle/code/cpp/models/implementations/Pure.qll index f25aed49d23..c831a8bf357 100644 --- a/cpp/ql/src/semmle/code/cpp/models/implementations/Pure.qll +++ b/cpp/ql/src/semmle/code/cpp/models/implementations/Pure.qll @@ -80,7 +80,7 @@ class PureStrFunction extends AliasFunction, ArrayFunction, TaintFunction, SideE override predicate parameterIsAlwaysReturned(int i) { none() } - override predicate hasOnlySpecificReadSideEffects() { none() } + override predicate hasOnlySpecificReadSideEffects() { any() } override predicate hasOnlySpecificWriteSideEffects() { any() } diff --git a/cpp/ql/src/semmle/code/cpp/models/interfaces/SideEffect.qll b/cpp/ql/src/semmle/code/cpp/models/interfaces/SideEffect.qll index 905e79eb32b..3377db771a3 100644 --- a/cpp/ql/src/semmle/code/cpp/models/interfaces/SideEffect.qll +++ b/cpp/ql/src/semmle/code/cpp/models/interfaces/SideEffect.qll @@ -18,14 +18,18 @@ abstract class SideEffectFunction extends Function { /** * Holds if the function never reads from memory that was defined before entry to the function. * This memory could be from global variables, or from other memory that was reachable from a - * pointer that was passed into the function. + * pointer that was passed into the function. Input side-effects, and reads from memory that + * cannot be visible to the caller (for example a buffer inside an I/O library) are not modeled + * here. */ abstract predicate hasOnlySpecificReadSideEffects(); /** * Holds if the function never writes to memory that remains allocated after the function * returns. This memory could be from global variables, or from other memory that was reachable - * from a pointer that was passed into the function. + * from a pointer that was passed into the function. Output side-effects, and writes to memory + * that cannot be visible to the caller (for example a buffer inside an I/O library) are not + * modeled here. */ abstract predicate hasOnlySpecificWriteSideEffects(); @@ -43,7 +47,6 @@ abstract class SideEffectFunction extends Function { */ predicate hasSpecificReadSideEffect(ParameterIndex i, boolean buffer) { none() } - // TODO: name? /** * Gets the index of the parameter that indicates the size of the buffer pointed to by the * parameter at index `i`. diff --git a/cpp/ql/test/library-tests/dataflow/security-taint/tainted.expected b/cpp/ql/test/library-tests/dataflow/security-taint/tainted.expected index 114c213ff54..7359b068d8e 100644 --- a/cpp/ql/test/library-tests/dataflow/security-taint/tainted.expected +++ b/cpp/ql/test/library-tests/dataflow/security-taint/tainted.expected @@ -15,27 +15,39 @@ | 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: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:8:24:8:25 | s1 | envStrGlobal | | 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:45:13:45:24 | envStrGlobal | 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:50:15:50:24 | envStr_ptr | | +| test.cpp:49:23:49:28 | call to getenv | test.cpp:50:15:50:24 | envStr_ptr | envStrGlobal | +| test.cpp:49:23:49:28 | call to getenv | test.cpp:50:28:50:40 | & ... | envStrGlobal | +| test.cpp:49:23:49:28 | call to getenv | test.cpp:50:29:50:40 | envStrGlobal | envStrGlobal | +| test.cpp:49:23:49:28 | call to getenv | test.cpp:52:2:52:12 | * ... | | +| test.cpp:49:23:49:28 | call to getenv | test.cpp:52:3:52:12 | envStr_ptr | | | 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:49:23:49:28 | call to getenv | test.cpp:54:6:54:35 | ! ... | envStrGlobal | +| test.cpp:49:23:49:28 | call to getenv | test.cpp:54:7:54:12 | call to strcmp | envStrGlobal | +| test.cpp:49:23:49:28 | call to getenv | test.cpp:54:7:54:35 | (bool)... | envStrGlobal | +| test.cpp:49:23:49:28 | call to getenv | test.cpp:54:14:54:25 | envStrGlobal | 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:25:64:32 | userName | | +| test.cpp:68:28:68:33 | call to getenv | test.cpp:11:20:11:21 | s1 | | | 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:67:7:67:13 | copying | | | 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:69:10:69:13 | copy | | | 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:12:70:15 | copy | | | test.cpp:68:28:68:33 | call to getenv | test.cpp:70:18:70:25 | userName | | +| test.cpp:68:28:68:33 | call to getenv | test.cpp:71:12:71:15 | copy | | | 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 | | diff --git a/cpp/ql/test/library-tests/dataflow/security-taint/tainted.ql b/cpp/ql/test/library-tests/dataflow/security-taint/tainted.ql index 64a724c4cab..4caea41850e 100644 --- a/cpp/ql/test/library-tests/dataflow/security-taint/tainted.ql +++ b/cpp/ql/test/library-tests/dataflow/security-taint/tainted.ql @@ -1,4 +1,4 @@ -import semmle.code.cpp.security.TaintTracking +import semmle.code.cpp.security.TaintTrackingImpl from Expr source, Element tainted, string globalVar where diff --git a/cpp/ql/test/library-tests/ir/ir/PrintAST.expected b/cpp/ql/test/library-tests/ir/ir/PrintAST.expected index 343ffc27db5..c2014db4cf8 100644 --- a/cpp/ql/test/library-tests/ir/ir/PrintAST.expected +++ b/cpp/ql/test/library-tests/ir/ir/PrintAST.expected @@ -8070,6 +8070,239 @@ ir.cpp: # 1170| Type = [ArrayType] const char[4] # 1170| Value = [StringLiteral] "foo" # 1170| ValueCategory = lvalue +# 1173| [TopLevelFunction] void switch1Case(int) +# 1173| params: +# 1173| 0: [Parameter] x +# 1173| Type = [IntType] int +# 1173| body: [Block] { ... } +# 1174| 0: [DeclStmt] declaration +# 1174| 0: [VariableDeclarationEntry] definition of y +# 1174| Type = [IntType] int +# 1174| init: [Initializer] initializer for y +# 1174| expr: [Literal] 0 +# 1174| Type = [IntType] int +# 1174| Value = [Literal] 0 +# 1174| ValueCategory = prvalue +# 1175| 1: [SwitchStmt] switch (...) ... +# 1175| 0: [VariableAccess] x +# 1175| Type = [IntType] int +# 1175| ValueCategory = prvalue(load) +# 1175| 1: [Block] { ... } +# 1176| 0: [SwitchCase] case ...: +# 1176| 0: [Literal] 1 +# 1176| Type = [IntType] int +# 1176| Value = [Literal] 1 +# 1176| ValueCategory = prvalue +# 1177| 1: [ExprStmt] ExprStmt +# 1177| 0: [AssignExpr] ... = ... +# 1177| Type = [IntType] int +# 1177| ValueCategory = lvalue +# 1177| 0: [VariableAccess] y +# 1177| Type = [IntType] int +# 1177| ValueCategory = lvalue +# 1177| 1: [Literal] 2 +# 1177| Type = [IntType] int +# 1177| Value = [Literal] 2 +# 1177| ValueCategory = prvalue +# 1179| 2: [DeclStmt] declaration +# 1179| 0: [VariableDeclarationEntry] definition of z +# 1179| Type = [IntType] int +# 1179| init: [Initializer] initializer for z +# 1179| expr: [VariableAccess] y +# 1179| Type = [IntType] int +# 1179| ValueCategory = prvalue(load) +# 1180| 3: [ReturnStmt] return ... +# 1182| [TopLevelFunction] void switch2Case_fallthrough(int) +# 1182| params: +# 1182| 0: [Parameter] x +# 1182| Type = [IntType] int +# 1182| body: [Block] { ... } +# 1183| 0: [DeclStmt] declaration +# 1183| 0: [VariableDeclarationEntry] definition of y +# 1183| Type = [IntType] int +# 1183| init: [Initializer] initializer for y +# 1183| expr: [Literal] 0 +# 1183| Type = [IntType] int +# 1183| Value = [Literal] 0 +# 1183| ValueCategory = prvalue +# 1184| 1: [SwitchStmt] switch (...) ... +# 1184| 0: [VariableAccess] x +# 1184| Type = [IntType] int +# 1184| ValueCategory = prvalue(load) +# 1184| 1: [Block] { ... } +# 1185| 0: [SwitchCase] case ...: +# 1185| 0: [Literal] 1 +# 1185| Type = [IntType] int +# 1185| Value = [Literal] 1 +# 1185| ValueCategory = prvalue +# 1186| 1: [ExprStmt] ExprStmt +# 1186| 0: [AssignExpr] ... = ... +# 1186| Type = [IntType] int +# 1186| ValueCategory = lvalue +# 1186| 0: [VariableAccess] y +# 1186| Type = [IntType] int +# 1186| ValueCategory = lvalue +# 1186| 1: [Literal] 2 +# 1186| Type = [IntType] int +# 1186| Value = [Literal] 2 +# 1186| ValueCategory = prvalue +# 1187| 2: [SwitchCase] case ...: +# 1187| 0: [Literal] 2 +# 1187| Type = [IntType] int +# 1187| Value = [Literal] 2 +# 1187| ValueCategory = prvalue +# 1188| 3: [ExprStmt] ExprStmt +# 1188| 0: [AssignExpr] ... = ... +# 1188| Type = [IntType] int +# 1188| ValueCategory = lvalue +# 1188| 0: [VariableAccess] y +# 1188| Type = [IntType] int +# 1188| ValueCategory = lvalue +# 1188| 1: [Literal] 3 +# 1188| Type = [IntType] int +# 1188| Value = [Literal] 3 +# 1188| ValueCategory = prvalue +# 1190| 2: [DeclStmt] declaration +# 1190| 0: [VariableDeclarationEntry] definition of z +# 1190| Type = [IntType] int +# 1190| init: [Initializer] initializer for z +# 1190| expr: [VariableAccess] y +# 1190| Type = [IntType] int +# 1190| ValueCategory = prvalue(load) +# 1191| 3: [ReturnStmt] return ... +# 1193| [TopLevelFunction] void switch2Case(int) +# 1193| params: +# 1193| 0: [Parameter] x +# 1193| Type = [IntType] int +# 1193| body: [Block] { ... } +# 1194| 0: [DeclStmt] declaration +# 1194| 0: [VariableDeclarationEntry] definition of y +# 1194| Type = [IntType] int +# 1194| init: [Initializer] initializer for y +# 1194| expr: [Literal] 0 +# 1194| Type = [IntType] int +# 1194| Value = [Literal] 0 +# 1194| ValueCategory = prvalue +# 1195| 1: [SwitchStmt] switch (...) ... +# 1195| 0: [VariableAccess] x +# 1195| Type = [IntType] int +# 1195| ValueCategory = prvalue(load) +# 1195| 1: [Block] { ... } +# 1196| 0: [SwitchCase] case ...: +# 1196| 0: [Literal] 1 +# 1196| Type = [IntType] int +# 1196| Value = [Literal] 1 +# 1196| ValueCategory = prvalue +# 1197| 1: [ExprStmt] ExprStmt +# 1197| 0: [AssignExpr] ... = ... +# 1197| Type = [IntType] int +# 1197| ValueCategory = lvalue +# 1197| 0: [VariableAccess] y +# 1197| Type = [IntType] int +# 1197| ValueCategory = lvalue +# 1197| 1: [Literal] 2 +# 1197| Type = [IntType] int +# 1197| Value = [Literal] 2 +# 1197| ValueCategory = prvalue +# 1198| 2: [BreakStmt] break; +# 1199| 3: [SwitchCase] case ...: +# 1199| 0: [Literal] 2 +# 1199| Type = [IntType] int +# 1199| Value = [Literal] 2 +# 1199| ValueCategory = prvalue +# 1200| 4: [ExprStmt] ExprStmt +# 1200| 0: [AssignExpr] ... = ... +# 1200| Type = [IntType] int +# 1200| ValueCategory = lvalue +# 1200| 0: [VariableAccess] y +# 1200| Type = [IntType] int +# 1200| ValueCategory = lvalue +# 1200| 1: [Literal] 3 +# 1200| Type = [IntType] int +# 1200| Value = [Literal] 3 +# 1200| ValueCategory = prvalue +# 1201| 2: [LabelStmt] label ...: +# 1202| 3: [DeclStmt] declaration +# 1202| 0: [VariableDeclarationEntry] definition of z +# 1202| Type = [IntType] int +# 1202| init: [Initializer] initializer for z +# 1202| expr: [VariableAccess] y +# 1202| Type = [IntType] int +# 1202| ValueCategory = prvalue(load) +# 1203| 4: [ReturnStmt] return ... +# 1205| [TopLevelFunction] void switch2Case_default(int) +# 1205| params: +# 1205| 0: [Parameter] x +# 1205| Type = [IntType] int +# 1205| body: [Block] { ... } +# 1206| 0: [DeclStmt] declaration +# 1206| 0: [VariableDeclarationEntry] definition of y +# 1206| Type = [IntType] int +# 1206| init: [Initializer] initializer for y +# 1206| expr: [Literal] 0 +# 1206| Type = [IntType] int +# 1206| Value = [Literal] 0 +# 1206| ValueCategory = prvalue +# 1207| 1: [SwitchStmt] switch (...) ... +# 1207| 0: [VariableAccess] x +# 1207| Type = [IntType] int +# 1207| ValueCategory = prvalue(load) +# 1207| 1: [Block] { ... } +# 1208| 0: [SwitchCase] case ...: +# 1208| 0: [Literal] 1 +# 1208| Type = [IntType] int +# 1208| Value = [Literal] 1 +# 1208| ValueCategory = prvalue +# 1209| 1: [ExprStmt] ExprStmt +# 1209| 0: [AssignExpr] ... = ... +# 1209| Type = [IntType] int +# 1209| ValueCategory = lvalue +# 1209| 0: [VariableAccess] y +# 1209| Type = [IntType] int +# 1209| ValueCategory = lvalue +# 1209| 1: [Literal] 2 +# 1209| Type = [IntType] int +# 1209| Value = [Literal] 2 +# 1209| ValueCategory = prvalue +# 1210| 2: [BreakStmt] break; +# 1212| 3: [SwitchCase] case ...: +# 1212| 0: [Literal] 2 +# 1212| Type = [IntType] int +# 1212| Value = [Literal] 2 +# 1212| ValueCategory = prvalue +# 1213| 4: [ExprStmt] ExprStmt +# 1213| 0: [AssignExpr] ... = ... +# 1213| Type = [IntType] int +# 1213| ValueCategory = lvalue +# 1213| 0: [VariableAccess] y +# 1213| Type = [IntType] int +# 1213| ValueCategory = lvalue +# 1213| 1: [Literal] 3 +# 1213| Type = [IntType] int +# 1213| Value = [Literal] 3 +# 1213| ValueCategory = prvalue +# 1214| 5: [BreakStmt] break; +# 1216| 6: [SwitchCase] default: +# 1217| 7: [ExprStmt] ExprStmt +# 1217| 0: [AssignExpr] ... = ... +# 1217| Type = [IntType] int +# 1217| ValueCategory = lvalue +# 1217| 0: [VariableAccess] y +# 1217| Type = [IntType] int +# 1217| ValueCategory = lvalue +# 1217| 1: [Literal] 4 +# 1217| Type = [IntType] int +# 1217| Value = [Literal] 4 +# 1217| ValueCategory = prvalue +# 1218| 2: [LabelStmt] label ...: +# 1219| 3: [DeclStmt] declaration +# 1219| 0: [VariableDeclarationEntry] definition of z +# 1219| Type = [IntType] int +# 1219| init: [Initializer] initializer for z +# 1219| expr: [VariableAccess] y +# 1219| Type = [IntType] int +# 1219| ValueCategory = prvalue(load) +# 1220| 4: [ReturnStmt] return ... perf-regression.cpp: # 4| [CopyAssignmentOperator] Big& Big::operator=(Big const&) # 4| params: diff --git a/cpp/ql/test/library-tests/ir/ir/aliased_ssa_sanity.expected b/cpp/ql/test/library-tests/ir/ir/aliased_ssa_sanity.expected index e5e666c020b..289b7bcae86 100644 --- a/cpp/ql/test/library-tests/ir/ir/aliased_ssa_sanity.expected +++ b/cpp/ql/test/library-tests/ir/ir/aliased_ssa_sanity.expected @@ -19,6 +19,7 @@ containsLoopOfForwardEdges lostReachability backEdgeCountMismatch useNotDominatedByDefinition +switchInstructionWithoutDefaultEdge missingCanonicalLanguageType multipleCanonicalLanguageTypes missingIRType diff --git a/cpp/ql/test/library-tests/ir/ir/aliased_ssa_sanity_unsound.expected b/cpp/ql/test/library-tests/ir/ir/aliased_ssa_sanity_unsound.expected index e5e666c020b..289b7bcae86 100644 --- a/cpp/ql/test/library-tests/ir/ir/aliased_ssa_sanity_unsound.expected +++ b/cpp/ql/test/library-tests/ir/ir/aliased_ssa_sanity_unsound.expected @@ -19,6 +19,7 @@ containsLoopOfForwardEdges lostReachability backEdgeCountMismatch useNotDominatedByDefinition +switchInstructionWithoutDefaultEdge missingCanonicalLanguageType multipleCanonicalLanguageTypes missingIRType diff --git a/cpp/ql/test/library-tests/ir/ir/ir.cpp b/cpp/ql/test/library-tests/ir/ir/ir.cpp index 6989287d222..c3a394f0985 100644 --- a/cpp/ql/test/library-tests/ir/ir/ir.cpp +++ b/cpp/ql/test/library-tests/ir/ir/ir.cpp @@ -1170,4 +1170,53 @@ String ReturnObjectImpl() { return String("foo"); } +void switch1Case(int x) { + int y = 0; + switch(x) { + case 1: + y = 2; + } + int z = y; +} + +void switch2Case_fallthrough(int x) { + int y = 0; + switch(x) { + case 1: + y = 2; + case 2: + y = 3; + } + int z = y; +} + +void switch2Case(int x) { + int y = 0; + switch(x) { + case 1: + y = 2; + break; + case 2: + y = 3; + } + int z = y; +} + +void switch2Case_default(int x) { + int y = 0; + switch(x) { + case 1: + y = 2; + break; + + case 2: + y = 3; + break; + + default: + y = 4; + } + int z = y; +} + // semmle-extractor-options: -std=c++17 --clang 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 1e14f92d0a6..b8505255529 100644 --- a/cpp/ql/test/library-tests/ir/ir/raw_ir.expected +++ b/cpp/ql/test/library-tests/ir/ir/raw_ir.expected @@ -6052,6 +6052,182 @@ ir.cpp: # 1169| v1169_8(void) = AliasedUse : ~mu1169_4 # 1169| v1169_9(void) = ExitFunction : +# 1173| void switch1Case(int) +# 1173| Block 0 +# 1173| v1173_1(void) = EnterFunction : +# 1173| mu1173_2(unknown) = AliasedDefinition : +# 1173| mu1173_3(unknown) = InitializeNonLocal : +# 1173| mu1173_4(unknown) = UnmodeledDefinition : +# 1173| r1173_5(glval) = VariableAddress[x] : +# 1173| mu1173_6(int) = InitializeParameter[x] : &:r1173_5 +# 1174| r1174_1(glval) = VariableAddress[y] : +# 1174| r1174_2(int) = Constant[0] : +# 1174| mu1174_3(int) = Store : &:r1174_1, r1174_2 +# 1175| r1175_1(glval) = VariableAddress[x] : +# 1175| r1175_2(int) = Load : &:r1175_1, ~mu1173_4 +# 1175| v1175_3(void) = Switch : r1175_2 +#-----| Case[1] -> Block 1 +#-----| Default -> Block 2 + +# 1176| Block 1 +# 1176| v1176_1(void) = NoOp : +# 1177| r1177_1(int) = Constant[2] : +# 1177| r1177_2(glval) = VariableAddress[y] : +# 1177| mu1177_3(int) = Store : &:r1177_2, r1177_1 +#-----| Goto -> Block 2 + +# 1179| Block 2 +# 1179| r1179_1(glval) = VariableAddress[z] : +# 1179| r1179_2(glval) = VariableAddress[y] : +# 1179| r1179_3(int) = Load : &:r1179_2, ~mu1173_4 +# 1179| mu1179_4(int) = Store : &:r1179_1, r1179_3 +# 1180| v1180_1(void) = NoOp : +# 1173| v1173_7(void) = ReturnVoid : +# 1173| v1173_8(void) = UnmodeledUse : mu* +# 1173| v1173_9(void) = AliasedUse : ~mu1173_4 +# 1173| v1173_10(void) = ExitFunction : + +# 1182| void switch2Case_fallthrough(int) +# 1182| Block 0 +# 1182| v1182_1(void) = EnterFunction : +# 1182| mu1182_2(unknown) = AliasedDefinition : +# 1182| mu1182_3(unknown) = InitializeNonLocal : +# 1182| mu1182_4(unknown) = UnmodeledDefinition : +# 1182| r1182_5(glval) = VariableAddress[x] : +# 1182| mu1182_6(int) = InitializeParameter[x] : &:r1182_5 +# 1183| r1183_1(glval) = VariableAddress[y] : +# 1183| r1183_2(int) = Constant[0] : +# 1183| mu1183_3(int) = Store : &:r1183_1, r1183_2 +# 1184| r1184_1(glval) = VariableAddress[x] : +# 1184| r1184_2(int) = Load : &:r1184_1, ~mu1182_4 +# 1184| v1184_3(void) = Switch : r1184_2 +#-----| Case[1] -> Block 1 +#-----| Case[2] -> Block 2 +#-----| Default -> Block 3 + +# 1185| Block 1 +# 1185| v1185_1(void) = NoOp : +# 1186| r1186_1(int) = Constant[2] : +# 1186| r1186_2(glval) = VariableAddress[y] : +# 1186| mu1186_3(int) = Store : &:r1186_2, r1186_1 +#-----| Goto -> Block 2 + +# 1187| Block 2 +# 1187| v1187_1(void) = NoOp : +# 1188| r1188_1(int) = Constant[3] : +# 1188| r1188_2(glval) = VariableAddress[y] : +# 1188| mu1188_3(int) = Store : &:r1188_2, r1188_1 +#-----| Goto -> Block 3 + +# 1190| Block 3 +# 1190| r1190_1(glval) = VariableAddress[z] : +# 1190| r1190_2(glval) = VariableAddress[y] : +# 1190| r1190_3(int) = Load : &:r1190_2, ~mu1182_4 +# 1190| mu1190_4(int) = Store : &:r1190_1, r1190_3 +# 1191| v1191_1(void) = NoOp : +# 1182| v1182_7(void) = ReturnVoid : +# 1182| v1182_8(void) = UnmodeledUse : mu* +# 1182| v1182_9(void) = AliasedUse : ~mu1182_4 +# 1182| v1182_10(void) = ExitFunction : + +# 1193| void switch2Case(int) +# 1193| Block 0 +# 1193| v1193_1(void) = EnterFunction : +# 1193| mu1193_2(unknown) = AliasedDefinition : +# 1193| mu1193_3(unknown) = InitializeNonLocal : +# 1193| mu1193_4(unknown) = UnmodeledDefinition : +# 1193| r1193_5(glval) = VariableAddress[x] : +# 1193| mu1193_6(int) = InitializeParameter[x] : &:r1193_5 +# 1194| r1194_1(glval) = VariableAddress[y] : +# 1194| r1194_2(int) = Constant[0] : +# 1194| mu1194_3(int) = Store : &:r1194_1, r1194_2 +# 1195| r1195_1(glval) = VariableAddress[x] : +# 1195| r1195_2(int) = Load : &:r1195_1, ~mu1193_4 +# 1195| v1195_3(void) = Switch : r1195_2 +#-----| Case[1] -> Block 1 +#-----| Case[2] -> Block 2 +#-----| Default -> Block 3 + +# 1196| Block 1 +# 1196| v1196_1(void) = NoOp : +# 1197| r1197_1(int) = Constant[2] : +# 1197| r1197_2(glval) = VariableAddress[y] : +# 1197| mu1197_3(int) = Store : &:r1197_2, r1197_1 +# 1198| v1198_1(void) = NoOp : +#-----| Goto -> Block 3 + +# 1199| Block 2 +# 1199| v1199_1(void) = NoOp : +# 1200| r1200_1(int) = Constant[3] : +# 1200| r1200_2(glval) = VariableAddress[y] : +# 1200| mu1200_3(int) = Store : &:r1200_2, r1200_1 +#-----| Goto -> Block 3 + +# 1201| Block 3 +# 1201| v1201_1(void) = NoOp : +# 1202| r1202_1(glval) = VariableAddress[z] : +# 1202| r1202_2(glval) = VariableAddress[y] : +# 1202| r1202_3(int) = Load : &:r1202_2, ~mu1193_4 +# 1202| mu1202_4(int) = Store : &:r1202_1, r1202_3 +# 1203| v1203_1(void) = NoOp : +# 1193| v1193_7(void) = ReturnVoid : +# 1193| v1193_8(void) = UnmodeledUse : mu* +# 1193| v1193_9(void) = AliasedUse : ~mu1193_4 +# 1193| v1193_10(void) = ExitFunction : + +# 1205| void switch2Case_default(int) +# 1205| Block 0 +# 1205| v1205_1(void) = EnterFunction : +# 1205| mu1205_2(unknown) = AliasedDefinition : +# 1205| mu1205_3(unknown) = InitializeNonLocal : +# 1205| mu1205_4(unknown) = UnmodeledDefinition : +# 1205| r1205_5(glval) = VariableAddress[x] : +# 1205| mu1205_6(int) = InitializeParameter[x] : &:r1205_5 +# 1206| r1206_1(glval) = VariableAddress[y] : +# 1206| r1206_2(int) = Constant[0] : +# 1206| mu1206_3(int) = Store : &:r1206_1, r1206_2 +# 1207| r1207_1(glval) = VariableAddress[x] : +# 1207| r1207_2(int) = Load : &:r1207_1, ~mu1205_4 +# 1207| v1207_3(void) = Switch : r1207_2 +#-----| Case[1] -> Block 1 +#-----| Case[2] -> Block 2 +#-----| Default -> Block 3 + +# 1208| Block 1 +# 1208| v1208_1(void) = NoOp : +# 1209| r1209_1(int) = Constant[2] : +# 1209| r1209_2(glval) = VariableAddress[y] : +# 1209| mu1209_3(int) = Store : &:r1209_2, r1209_1 +# 1210| v1210_1(void) = NoOp : +#-----| Goto -> Block 4 + +# 1212| Block 2 +# 1212| v1212_1(void) = NoOp : +# 1213| r1213_1(int) = Constant[3] : +# 1213| r1213_2(glval) = VariableAddress[y] : +# 1213| mu1213_3(int) = Store : &:r1213_2, r1213_1 +# 1214| v1214_1(void) = NoOp : +#-----| Goto -> Block 4 + +# 1216| Block 3 +# 1216| v1216_1(void) = NoOp : +# 1217| r1217_1(int) = Constant[4] : +# 1217| r1217_2(glval) = VariableAddress[y] : +# 1217| mu1217_3(int) = Store : &:r1217_2, r1217_1 +#-----| Goto -> Block 4 + +# 1218| Block 4 +# 1218| v1218_1(void) = NoOp : +# 1219| r1219_1(glval) = VariableAddress[z] : +# 1219| r1219_2(glval) = VariableAddress[y] : +# 1219| r1219_3(int) = Load : &:r1219_2, ~mu1205_4 +# 1219| mu1219_4(int) = Store : &:r1219_1, r1219_3 +# 1220| v1220_1(void) = NoOp : +# 1205| v1205_7(void) = ReturnVoid : +# 1205| v1205_8(void) = UnmodeledUse : mu* +# 1205| v1205_9(void) = AliasedUse : ~mu1205_4 +# 1205| v1205_10(void) = ExitFunction : + perf-regression.cpp: # 6| void Big::Big() # 6| Block 0 diff --git a/cpp/ql/test/library-tests/ir/ir/raw_sanity.expected b/cpp/ql/test/library-tests/ir/ir/raw_sanity.expected index e5e666c020b..289b7bcae86 100644 --- a/cpp/ql/test/library-tests/ir/ir/raw_sanity.expected +++ b/cpp/ql/test/library-tests/ir/ir/raw_sanity.expected @@ -19,6 +19,7 @@ containsLoopOfForwardEdges lostReachability backEdgeCountMismatch useNotDominatedByDefinition +switchInstructionWithoutDefaultEdge missingCanonicalLanguageType multipleCanonicalLanguageTypes missingIRType diff --git a/cpp/ql/test/library-tests/ir/ir/unaliased_ssa_sanity.expected b/cpp/ql/test/library-tests/ir/ir/unaliased_ssa_sanity.expected index e5e666c020b..289b7bcae86 100644 --- a/cpp/ql/test/library-tests/ir/ir/unaliased_ssa_sanity.expected +++ b/cpp/ql/test/library-tests/ir/ir/unaliased_ssa_sanity.expected @@ -19,6 +19,7 @@ containsLoopOfForwardEdges lostReachability backEdgeCountMismatch useNotDominatedByDefinition +switchInstructionWithoutDefaultEdge missingCanonicalLanguageType multipleCanonicalLanguageTypes missingIRType diff --git a/cpp/ql/test/library-tests/ir/ir/unaliased_ssa_sanity_unsound.expected b/cpp/ql/test/library-tests/ir/ir/unaliased_ssa_sanity_unsound.expected index e5e666c020b..289b7bcae86 100644 --- a/cpp/ql/test/library-tests/ir/ir/unaliased_ssa_sanity_unsound.expected +++ b/cpp/ql/test/library-tests/ir/ir/unaliased_ssa_sanity_unsound.expected @@ -19,6 +19,7 @@ containsLoopOfForwardEdges lostReachability backEdgeCountMismatch useNotDominatedByDefinition +switchInstructionWithoutDefaultEdge missingCanonicalLanguageType multipleCanonicalLanguageTypes missingIRType 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 32caabbe079..4c1bcc9bc13 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 @@ -886,27 +886,25 @@ ssa.cpp: # 199| r199_7(char *) = Load : &:r199_6, m198_11 # 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_4 -# 199| v199_11(void) = ^BufferReadSideEffect[0] : &:r199_5, ~m198_9 -# 199| v199_12(void) = ^BufferReadSideEffect[1] : &:r199_8, ~m198_13 -# 199| m199_13(int) = Store : &:r199_1, r199_9 +# 199| v199_10(void) = ^BufferReadSideEffect[0] : &:r199_5, ~m198_9 +# 199| v199_11(void) = ^BufferReadSideEffect[1] : &:r199_8, ~m198_13 +# 199| m199_12(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_7 # 200| r200_4(char *) = Convert : r200_3 # 200| r200_5(int) = Call : func:r200_1, 0:r200_4 -# 200| v200_6(void) = ^CallReadSideEffect : ~m198_4 -# 200| v200_7(void) = ^BufferReadSideEffect[0] : &:r200_4, ~m198_9 -# 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 +# 200| v200_6(void) = ^BufferReadSideEffect[0] : &:r200_4, ~m198_9 +# 200| r200_7(glval) = VariableAddress[ret] : +# 200| r200_8(int) = Load : &:r200_7, m199_12 +# 200| r200_9(int) = Add : r200_8, r200_5 +# 200| m200_10(int) = Store : &:r200_7, r200_9 # 201| r201_1(glval) = FunctionAddress[abs] : # 201| r201_2(glval) = VariableAddress[x] : # 201| r201_3(int) = Load : &:r201_2, m198_15 # 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 +# 201| r201_6(int) = Load : &:r201_5, m200_10 # 201| r201_7(int) = Add : r201_6, r201_4 # 201| m201_8(int) = Store : &:r201_5, r201_7 # 202| r202_1(glval) = VariableAddress[#return] : 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 1d3ad55c2ef..119e28b3ffd 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 @@ -883,27 +883,25 @@ ssa.cpp: # 199| r199_7(char *) = Load : &:r199_6, m198_11 # 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_4 -# 199| v199_11(void) = ^BufferReadSideEffect[0] : &:r199_5, ~m198_9 -# 199| v199_12(void) = ^BufferReadSideEffect[1] : &:r199_8, ~m198_13 -# 199| m199_13(int) = Store : &:r199_1, r199_9 +# 199| v199_10(void) = ^BufferReadSideEffect[0] : &:r199_5, ~m198_9 +# 199| v199_11(void) = ^BufferReadSideEffect[1] : &:r199_8, ~m198_13 +# 199| m199_12(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_7 # 200| r200_4(char *) = Convert : r200_3 # 200| r200_5(int) = Call : func:r200_1, 0:r200_4 -# 200| v200_6(void) = ^CallReadSideEffect : ~m198_4 -# 200| v200_7(void) = ^BufferReadSideEffect[0] : &:r200_4, ~m198_9 -# 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 +# 200| v200_6(void) = ^BufferReadSideEffect[0] : &:r200_4, ~m198_9 +# 200| r200_7(glval) = VariableAddress[ret] : +# 200| r200_8(int) = Load : &:r200_7, m199_12 +# 200| r200_9(int) = Add : r200_8, r200_5 +# 200| m200_10(int) = Store : &:r200_7, r200_9 # 201| r201_1(glval) = FunctionAddress[abs] : # 201| r201_2(glval) = VariableAddress[x] : # 201| r201_3(int) = Load : &:r201_2, m198_15 # 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 +# 201| r201_6(int) = Load : &:r201_5, m200_10 # 201| r201_7(int) = Add : r201_6, r201_4 # 201| m201_8(int) = Store : &:r201_5, r201_7 # 202| r202_1(glval) = VariableAddress[#return] : diff --git a/cpp/ql/test/library-tests/ir/ssa/aliased_ssa_sanity.expected b/cpp/ql/test/library-tests/ir/ssa/aliased_ssa_sanity.expected index 9769ee11f99..962a5a4484b 100644 --- a/cpp/ql/test/library-tests/ir/ssa/aliased_ssa_sanity.expected +++ b/cpp/ql/test/library-tests/ir/ssa/aliased_ssa_sanity.expected @@ -15,6 +15,7 @@ containsLoopOfForwardEdges lostReachability backEdgeCountMismatch useNotDominatedByDefinition +switchInstructionWithoutDefaultEdge missingCanonicalLanguageType multipleCanonicalLanguageTypes missingIRType diff --git a/cpp/ql/test/library-tests/ir/ssa/aliased_ssa_sanity_unsound.expected b/cpp/ql/test/library-tests/ir/ssa/aliased_ssa_sanity_unsound.expected index 9769ee11f99..962a5a4484b 100644 --- a/cpp/ql/test/library-tests/ir/ssa/aliased_ssa_sanity_unsound.expected +++ b/cpp/ql/test/library-tests/ir/ssa/aliased_ssa_sanity_unsound.expected @@ -15,6 +15,7 @@ containsLoopOfForwardEdges lostReachability backEdgeCountMismatch useNotDominatedByDefinition +switchInstructionWithoutDefaultEdge missingCanonicalLanguageType multipleCanonicalLanguageTypes missingIRType 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 90d5c8690a6..a0fd908c93b 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 @@ -829,27 +829,25 @@ ssa.cpp: # 199| r199_7(char *) = Load : &:r199_6, m198_10 # 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 : ~mu198_4 -# 199| v199_11(void) = ^BufferReadSideEffect[0] : &:r199_5, ~mu198_4 -# 199| v199_12(void) = ^BufferReadSideEffect[1] : &:r199_8, ~mu198_4 -# 199| m199_13(int) = Store : &:r199_1, r199_9 +# 199| v199_10(void) = ^BufferReadSideEffect[0] : &:r199_5, ~mu198_4 +# 199| v199_11(void) = ^BufferReadSideEffect[1] : &:r199_8, ~mu198_4 +# 199| m199_12(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_6 # 200| r200_4(char *) = Convert : r200_3 # 200| r200_5(int) = Call : func:r200_1, 0:r200_4 -# 200| v200_6(void) = ^CallReadSideEffect : ~mu198_4 -# 200| v200_7(void) = ^BufferReadSideEffect[0] : &:r200_4, ~mu198_4 -# 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 +# 200| v200_6(void) = ^BufferReadSideEffect[0] : &:r200_4, ~mu198_4 +# 200| r200_7(glval) = VariableAddress[ret] : +# 200| r200_8(int) = Load : &:r200_7, m199_12 +# 200| r200_9(int) = Add : r200_8, r200_5 +# 200| m200_10(int) = Store : &:r200_7, r200_9 # 201| r201_1(glval) = FunctionAddress[abs] : # 201| r201_2(glval) = VariableAddress[x] : # 201| r201_3(int) = Load : &:r201_2, m198_14 # 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 +# 201| r201_6(int) = Load : &:r201_5, m200_10 # 201| r201_7(int) = Add : r201_6, r201_4 # 201| m201_8(int) = Store : &:r201_5, r201_7 # 202| r202_1(glval) = VariableAddress[#return] : 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 90d5c8690a6..a0fd908c93b 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 @@ -829,27 +829,25 @@ ssa.cpp: # 199| r199_7(char *) = Load : &:r199_6, m198_10 # 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 : ~mu198_4 -# 199| v199_11(void) = ^BufferReadSideEffect[0] : &:r199_5, ~mu198_4 -# 199| v199_12(void) = ^BufferReadSideEffect[1] : &:r199_8, ~mu198_4 -# 199| m199_13(int) = Store : &:r199_1, r199_9 +# 199| v199_10(void) = ^BufferReadSideEffect[0] : &:r199_5, ~mu198_4 +# 199| v199_11(void) = ^BufferReadSideEffect[1] : &:r199_8, ~mu198_4 +# 199| m199_12(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_6 # 200| r200_4(char *) = Convert : r200_3 # 200| r200_5(int) = Call : func:r200_1, 0:r200_4 -# 200| v200_6(void) = ^CallReadSideEffect : ~mu198_4 -# 200| v200_7(void) = ^BufferReadSideEffect[0] : &:r200_4, ~mu198_4 -# 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 +# 200| v200_6(void) = ^BufferReadSideEffect[0] : &:r200_4, ~mu198_4 +# 200| r200_7(glval) = VariableAddress[ret] : +# 200| r200_8(int) = Load : &:r200_7, m199_12 +# 200| r200_9(int) = Add : r200_8, r200_5 +# 200| m200_10(int) = Store : &:r200_7, r200_9 # 201| r201_1(glval) = FunctionAddress[abs] : # 201| r201_2(glval) = VariableAddress[x] : # 201| r201_3(int) = Load : &:r201_2, m198_14 # 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 +# 201| r201_6(int) = Load : &:r201_5, m200_10 # 201| r201_7(int) = Add : r201_6, r201_4 # 201| m201_8(int) = Store : &:r201_5, r201_7 # 202| r202_1(glval) = VariableAddress[#return] : diff --git a/cpp/ql/test/library-tests/ir/ssa/unaliased_ssa_sanity.expected b/cpp/ql/test/library-tests/ir/ssa/unaliased_ssa_sanity.expected index 9769ee11f99..962a5a4484b 100644 --- a/cpp/ql/test/library-tests/ir/ssa/unaliased_ssa_sanity.expected +++ b/cpp/ql/test/library-tests/ir/ssa/unaliased_ssa_sanity.expected @@ -15,6 +15,7 @@ containsLoopOfForwardEdges lostReachability backEdgeCountMismatch useNotDominatedByDefinition +switchInstructionWithoutDefaultEdge missingCanonicalLanguageType multipleCanonicalLanguageTypes missingIRType diff --git a/cpp/ql/test/library-tests/ir/ssa/unaliased_ssa_sanity_unsound.expected b/cpp/ql/test/library-tests/ir/ssa/unaliased_ssa_sanity_unsound.expected index 9769ee11f99..962a5a4484b 100644 --- a/cpp/ql/test/library-tests/ir/ssa/unaliased_ssa_sanity_unsound.expected +++ b/cpp/ql/test/library-tests/ir/ssa/unaliased_ssa_sanity_unsound.expected @@ -15,6 +15,7 @@ containsLoopOfForwardEdges lostReachability backEdgeCountMismatch useNotDominatedByDefinition +switchInstructionWithoutDefaultEdge missingCanonicalLanguageType multipleCanonicalLanguageTypes missingIRType diff --git a/cpp/ql/test/library-tests/range_based_for/range_based_for.expected b/cpp/ql/test/library-tests/range_based_for/range_based_for.expected new file mode 100644 index 00000000000..075acb19427 --- /dev/null +++ b/cpp/ql/test/library-tests/range_based_for/range_based_for.expected @@ -0,0 +1,32 @@ +bodies +| test.cpp:8:3:10:3 | for(...:...) ... | test.cpp:8:28:10:3 | { ... } | +| test.cpp:28:3:30:3 | for(...:...) ... | test.cpp:28:28:30:3 | { ... } | +| test.cpp:44:3:46:3 | for(...:...) ... | test.cpp:44:27:46:3 | { ... } | +variables +| test.cpp:8:3:10:3 | for(...:...) ... | test.cpp:8:13:8:17 | value | file://:0:0:0:0 | short | +| test.cpp:28:3:30:3 | for(...:...) ... | test.cpp:28:12:28:16 | value | file://:0:0:0:0 | int | +| test.cpp:44:3:46:3 | for(...:...) ... | test.cpp:44:13:44:17 | value | file://:0:0:0:0 | long | +ranges +| test.cpp:8:3:10:3 | for(...:...) ... | test.cpp:8:21:8:25 | array | file://:0:0:0:0 | short[100] | +| test.cpp:28:3:30:3 | for(...:...) ... | test.cpp:28:20:28:25 | vector | test.cpp:16:8:16:13 | Vector | +| test.cpp:44:3:46:3 | for(...:...) ... | test.cpp:44:21:44:24 | list | file://:0:0:0:0 | const List & | +rangeVariables +| test.cpp:8:3:10:3 | for(...:...) ... | test.cpp:8:3:8:3 | (__range) | file://:0:0:0:0 | short(&)[100] | +| test.cpp:28:3:30:3 | for(...:...) ... | test.cpp:28:3:28:3 | (__range) | file://:0:0:0:0 | Vector & | +| test.cpp:44:3:46:3 | for(...:...) ... | test.cpp:44:3:44:3 | (__range) | file://:0:0:0:0 | const List & | +conditions +| test.cpp:8:3:10:3 | for(...:...) ... | file://:0:0:0:0 | ... != ... | file://:0:0:0:0 | (__begin) | file://:0:0:0:0 | (__end) | +| test.cpp:28:3:30:3 | for(...:...) ... | test.cpp:28:20:28:20 | call to operator!= | file://:0:0:0:0 | (__begin) | file://:0:0:0:0 | (__end) | +| test.cpp:44:3:46:3 | for(...:...) ... | file://:0:0:0:0 | ... != ... | file://:0:0:0:0 | (__begin) | file://:0:0:0:0 | (__end) | +beginVariables +| test.cpp:8:3:10:3 | for(...:...) ... | test.cpp:8:3:8:3 | (__begin) | file://:0:0:0:0 | short * | +| test.cpp:28:3:30:3 | for(...:...) ... | test.cpp:28:3:28:3 | (__begin) | test.cpp:17:10:17:17 | Iterator | +| test.cpp:44:3:46:3 | for(...:...) ... | test.cpp:44:3:44:3 | (__begin) | file://:0:0:0:0 | long * | +endVariables +| test.cpp:8:3:10:3 | for(...:...) ... | test.cpp:8:3:8:3 | (__end) | file://:0:0:0:0 | short * | +| test.cpp:28:3:30:3 | for(...:...) ... | test.cpp:28:3:28:3 | (__end) | test.cpp:17:10:17:17 | Iterator | +| test.cpp:44:3:46:3 | for(...:...) ... | test.cpp:44:3:44:3 | (__end) | file://:0:0:0:0 | long * | +updates +| test.cpp:8:3:10:3 | for(...:...) ... | file://:0:0:0:0 | ++ ... | file://:0:0:0:0 | (__begin) | +| test.cpp:28:3:30:3 | for(...:...) ... | test.cpp:28:20:28:20 | call to operator++ | file://:0:0:0:0 | (__begin) | +| test.cpp:44:3:46:3 | for(...:...) ... | file://:0:0:0:0 | ++ ... | file://:0:0:0:0 | (__begin) | diff --git a/cpp/ql/test/library-tests/range_based_for/range_based_for.ql b/cpp/ql/test/library-tests/range_based_for/range_based_for.ql new file mode 100644 index 00000000000..265899da51a --- /dev/null +++ b/cpp/ql/test/library-tests/range_based_for/range_based_for.ql @@ -0,0 +1,52 @@ +import cpp + +query predicate bodies(RangeBasedForStmt rbf, Stmt body) { body = rbf.getStmt() } + +query predicate variables(RangeBasedForStmt rbf, LocalVariable v, Type t) { + v = rbf.getVariable() and + t = v.getType() +} + +query predicate ranges(RangeBasedForStmt rbf, Expr range, Type t) { + range = rbf.getRange() and + t = range.getType() +} + +query predicate rangeVariables(RangeBasedForStmt rbf, LocalVariable rangeVar, Type t) { + rangeVar = rbf.getRangeVariable() and + t = rangeVar.getType() +} + +query predicate conditions(RangeBasedForStmt rbf, Expr condition, Expr left, Expr right) { + condition = rbf.getCondition() and + ( + condition instanceof BinaryOperation and + left = condition.(BinaryOperation).getLeftOperand() and + right = condition.(BinaryOperation).getRightOperand() + or + condition instanceof FunctionCall and + left = condition.(FunctionCall).getQualifier() and + right = condition.(FunctionCall).getArgument(0) + ) +} + +query predicate beginVariables(RangeBasedForStmt rbf, LocalVariable beginVar, Type t) { + beginVar = rbf.getBeginVariable() and + t = beginVar.getType() +} + +query predicate endVariables(RangeBasedForStmt rbf, LocalVariable endVar, Type t) { + endVar = rbf.getEndVariable() and + t = endVar.getType() +} + +query predicate updates(RangeBasedForStmt rbf, Expr update, Expr operand) { + update = rbf.getUpdate() and + ( + update instanceof UnaryOperation and + operand = update.(UnaryOperation).getOperand() + or + update instanceof FunctionCall and + operand = update.(FunctionCall).getQualifier() + ) +} diff --git a/cpp/ql/test/library-tests/range_based_for/test.cpp b/cpp/ql/test/library-tests/range_based_for/test.cpp new file mode 100644 index 00000000000..83c2b40a4ac --- /dev/null +++ b/cpp/ql/test/library-tests/range_based_for/test.cpp @@ -0,0 +1,47 @@ + +void print_short(short); +void print_int(int); +void print_long(long); + +short array[100]; +void loop_over_c_array() { + for (auto value : array) { + print_short(value); + } +} + +// A class that can be used with range-based-for, because it has the member +// functions `begin` and `end`. +template +struct Vector { + struct Iterator { + const T& operator*() const; + bool operator!=(const Iterator &rhs) const; + Iterator operator++(); + }; + Iterator begin(); + Iterator end(); +}; + +Vector vector; +void loop_over_vector_object() { + for (int value : vector) { + print_int(value); + } +} + +// A class that can be used with range-based-for, because there are `begin` and +// `end` functions that take a `List` as their argument. +template +struct List {}; + +template +T* begin(const List &list); +template +T* end(const List &list); + +void loop_over_list_object(const List &list) { + for (auto value : list) { + print_long(value); + } +} 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 c0671e967cc..64a52186564 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 @@ -568,6 +568,7 @@ lostReachability | range_analysis.c:371:37:371:39 | Constant: 500 | backEdgeCountMismatch useNotDominatedByDefinition +switchInstructionWithoutDefaultEdge missingCanonicalLanguageType multipleCanonicalLanguageTypes missingIRType diff --git a/cpp/ql/test/library-tests/syntax-zoo/raw_sanity.expected b/cpp/ql/test/library-tests/syntax-zoo/raw_sanity.expected index 80da8a79ced..63e211635ea 100644 --- a/cpp/ql/test/library-tests/syntax-zoo/raw_sanity.expected +++ b/cpp/ql/test/library-tests/syntax-zoo/raw_sanity.expected @@ -645,6 +645,7 @@ useNotDominatedByDefinition | pointer_to_member.cpp:36:22:36:28 | Address | Operand 'Address' is not dominated by its definition in function '$@'. | pointer_to_member.cpp:32:6:32:14 | IR: pmIsConst | void pmIsConst() | | try_catch.cpp:21:13:21:24 | Address | Operand 'Address' is not dominated by its definition in function '$@'. | try_catch.cpp:19:6:19:23 | IR: throw_from_nonstmt | void throw_from_nonstmt(int) | | vla.c:3:27:3:30 | Address | Operand 'Address' is not dominated by its definition in function '$@'. | vla.c:3:5:3:8 | IR: main | int main(int, char**) | +switchInstructionWithoutDefaultEdge missingCanonicalLanguageType multipleCanonicalLanguageTypes missingIRType diff --git a/cpp/ql/test/library-tests/syntax-zoo/unaliased_ssa_sanity.expected b/cpp/ql/test/library-tests/syntax-zoo/unaliased_ssa_sanity.expected index c7897f98817..a7f2d82ac7e 100644 --- a/cpp/ql/test/library-tests/syntax-zoo/unaliased_ssa_sanity.expected +++ b/cpp/ql/test/library-tests/syntax-zoo/unaliased_ssa_sanity.expected @@ -577,6 +577,7 @@ lostReachability | range_analysis.c:371:37:371:39 | Constant: 500 | backEdgeCountMismatch useNotDominatedByDefinition +switchInstructionWithoutDefaultEdge missingCanonicalLanguageType multipleCanonicalLanguageTypes missingIRType diff --git a/cpp/ql/test/library-tests/valuenumbering/GlobalValueNumbering/GlobalValueNumbering.expected b/cpp/ql/test/library-tests/valuenumbering/GlobalValueNumbering/GlobalValueNumbering.expected deleted file mode 100644 index 052ecf50921..00000000000 --- a/cpp/ql/test/library-tests/valuenumbering/GlobalValueNumbering/GlobalValueNumbering.expected +++ /dev/null @@ -1,42 +0,0 @@ -| test.cpp:5:3:5:3 | GVN | 5:c3-c3 6:c3-c3 | -| test.cpp:5:7:5:8 | GVN | 5:c7-c8 6:c7-c8 | -| test.cpp:5:7:5:13 | GVN | 5:c7-c13 6:c7-c13 7:c7-c7 | -| test.cpp:5:12:5:13 | GVN | 5:c12-c13 6:c12-c13 | -| test.cpp:16:3:16:3 | GVN | 16:c3-c3 17:c3-c3 | -| test.cpp:16:7:16:8 | GVN | 16:c7-c8 17:c7-c8 | -| test.cpp:16:7:16:13 | GVN | 16:c7-c13 17:c7-c13 | -| test.cpp:16:7:16:24 | GVN | 16:c7-c24 17:c7-c24 18:c7-c7 | -| test.cpp:16:12:16:13 | GVN | 16:c12-c13 17:c12-c13 | -| test.cpp:16:17:16:24 | GVN | 16:c17-c24 17:c17-c24 | -| test.cpp:29:3:29:3 | GVN | 29:c3-c3 31:c3-c3 | -| test.cpp:29:7:29:8 | GVN | 29:c7-c8 31:c7-c8 | -| test.cpp:29:7:29:13 | GVN | 29:c7-c13 31:c7-c13 | -| test.cpp:29:12:29:13 | GVN | 29:c12-c13 31:c12-c13 | -| test.cpp:31:7:31:24 | GVN | 31:c7-c24 32:c7-c7 | -| test.cpp:43:3:43:3 | GVN | 43:c3-c3 45:c3-c3 | -| test.cpp:43:7:43:8 | GVN | 43:c7-c8 45:c7-c8 | -| test.cpp:43:7:43:13 | GVN | 43:c7-c13 45:c7-c13 | -| test.cpp:43:7:43:24 | GVN | 43:c7-c24 45:c7-c24 46:c7-c7 | -| test.cpp:43:12:43:13 | GVN | 43:c12-c13 45:c12-c13 | -| test.cpp:43:17:43:24 | GVN | 43:c17-c24 45:c17-c24 | -| test.cpp:44:3:44:5 | GVN | 44:c3-c5 44:c4-c5 | -| test.cpp:53:10:53:13 | GVN | 53:c10-c13 56:c21-c24 | -| test.cpp:53:10:53:13 | GVN | 53:c10-c13 56:c21-c24 | -| test.cpp:53:11:53:13 | GVN | 53:c11-c13 56:c22-c24 | -| test.cpp:53:18:53:21 | GVN | 53:c18-c21 56:c39-c42 59:c17-c20 | -| test.cpp:56:14:56:16 | GVN | 56:c14-c16 56:c32-c34 56:c47-c49 59:c10-c12 | -| test.cpp:62:5:62:10 | GVN | 62:c5-c10 65:c10-c15 | -| test.cpp:77:20:77:28 | GVN | 77:c20-c28 79:c7-c7 | -| test.cpp:79:11:79:14 | GVN | 79:c11-c14 79:c24-c27 | -| test.cpp:92:11:92:16 | GVN | 92:c11-c16 92:c15-c16 93:c10-c10 | -| test.cpp:105:11:105:12 | GVN | 105:c11-c12 106:c33-c34 | -| test.cpp:105:11:105:12 | GVN | 105:c11-c12 106:c33-c34 107:c11-c12 | -| test.cpp:105:15:105:15 | GVN | 105:c15-c15 107:c15-c15 109:c10-c10 | -| test.cpp:113:3:113:5 | GVN | 113:c3-c5 115:c3-c5 | -| test.cpp:125:11:125:12 | GVN | 125:c11-c12 126:c11-c12 128:c3-c4 129:c11-c12 | -| test.cpp:125:15:125:15 | GVN | 125:c15-c15 126:c15-c15 | -| test.cpp:128:11:128:11 | GVN | 128:c11-c11 129:c15-c15 | -| test.cpp:136:11:136:18 | GVN | 136:c11-c18 137:c11-c18 139:c3-c10 | -| test.cpp:144:11:144:12 | GVN | 144:c11-c12 145:c11-c12 147:c3-c4 149:c11-c12 | -| test.cpp:144:15:144:15 | GVN | 144:c15-c15 149:c15-c15 | -| test.cpp:153:11:153:18 | GVN | 153:c11-c18 154:c11-c18 156:c3-c10 | diff --git a/cpp/ql/test/library-tests/valuenumbering/GlobalValueNumbering/Uniqueness.expected b/cpp/ql/test/library-tests/valuenumbering/GlobalValueNumbering/Uniqueness.expected deleted file mode 100644 index 0aebaa26f60..00000000000 --- a/cpp/ql/test/library-tests/valuenumbering/GlobalValueNumbering/Uniqueness.expected +++ /dev/null @@ -1,58 +0,0 @@ -| test.cpp:5:3:5:13 | ... = ... | | -| test.cpp:6:3:6:13 | ... = ... | | -| test.cpp:7:3:7:7 | ... = ... | | -| test.cpp:10:16:10:16 | 1 | | -| test.cpp:16:3:16:24 | ... = ... | | -| test.cpp:17:3:17:24 | ... = ... | | -| test.cpp:18:3:18:7 | ... = ... | | -| test.cpp:21:16:21:16 | 2 | | -| test.cpp:29:3:29:24 | ... = ... | | -| test.cpp:30:3:30:17 | call to change_global02 | | -| test.cpp:31:3:31:24 | ... = ... | | -| test.cpp:32:3:32:7 | ... = ... | | -| test.cpp:35:16:35:16 | 3 | | -| test.cpp:43:3:43:24 | ... = ... | | -| test.cpp:44:3:44:9 | ... = ... | | -| test.cpp:45:3:45:24 | ... = ... | | -| test.cpp:46:3:46:7 | ... = ... | | -| test.cpp:51:25:51:25 | (unsigned int)... | | -| test.cpp:53:10:53:13 | (int)... | | -| test.cpp:53:10:53:13 | * ... | LoadTotalOverlap, Unary | -| test.cpp:53:18:53:21 | (int)... | | -| test.cpp:55:5:55:15 | ... = ... | | -| test.cpp:56:12:56:25 | (...) | | -| test.cpp:56:12:56:43 | ... && ... | | -| test.cpp:56:13:56:16 | (int)... | | -| test.cpp:56:13:56:16 | * ... | Unary, Unique | -| test.cpp:56:21:56:24 | (int)... | | -| test.cpp:56:21:56:24 | * ... | LoadTotalOverlap, Unary | -| test.cpp:56:30:56:43 | (...) | | -| test.cpp:56:31:56:34 | (int)... | | -| test.cpp:56:31:56:34 | * ... | Unary, Unique | -| test.cpp:56:39:56:42 | (int)... | | -| test.cpp:56:47:56:51 | ... ++ | | -| test.cpp:59:9:59:12 | (int)... | | -| test.cpp:59:9:59:12 | * ... | Unary, Unique | -| test.cpp:59:17:59:20 | (int)... | | -| test.cpp:62:5:62:12 | ... ++ | | -| test.cpp:77:20:77:28 | call to getAValue | Unary, Unique | -| test.cpp:77:20:77:30 | (signed short)... | | -| test.cpp:79:7:79:7 | (int)... | | -| test.cpp:79:7:79:7 | v | Unary, Unary | -| test.cpp:79:11:79:20 | (int)... | | -| test.cpp:79:17:79:20 | val1 | LoadTotalOverlap, Unary | -| test.cpp:79:24:79:33 | (int)... | | -| test.cpp:79:30:79:33 | val2 | LoadTotalOverlap, Unary | -| test.cpp:80:5:80:19 | ... = ... | | -| test.cpp:80:9:80:17 | call to getAValue | Unary, Unique | -| test.cpp:80:9:80:19 | (signed short)... | | -| test.cpp:88:3:88:20 | ... = ... | | -| test.cpp:88:12:88:12 | (void *)... | | -| test.cpp:105:11:105:12 | (Base *)... | | -| test.cpp:105:11:105:12 | pd | InheritanceConversion, InitializeParameter | -| test.cpp:106:14:106:35 | static_cast... | | -| test.cpp:106:33:106:34 | pd | InheritanceConversion, InitializeParameter | -| test.cpp:128:3:128:11 | ... = ... | | -| test.cpp:139:3:139:24 | ... = ... | | -| test.cpp:147:3:147:18 | ... = ... | | -| test.cpp:156:3:156:17 | ... = ... | | diff --git a/cpp/ql/test/library-tests/valuenumbering/GlobalValueNumbering/ast_gvn.expected b/cpp/ql/test/library-tests/valuenumbering/GlobalValueNumbering/ast_gvn.expected new file mode 100644 index 00000000000..d5d46ee0b72 --- /dev/null +++ b/cpp/ql/test/library-tests/valuenumbering/GlobalValueNumbering/ast_gvn.expected @@ -0,0 +1,40 @@ +| test.cpp:5:3:5:3 | x | 5:c3-c3 6:c3-c3 | +| test.cpp:5:7:5:8 | p0 | 5:c7-c8 6:c7-c8 | +| test.cpp:5:7:5:13 | ... + ... | 5:c7-c13 6:c7-c13 7:c7-c7 | +| test.cpp:5:12:5:13 | p1 | 5:c12-c13 6:c12-c13 | +| test.cpp:16:3:16:3 | x | 16:c3-c3 17:c3-c3 | +| test.cpp:16:7:16:8 | p0 | 16:c7-c8 17:c7-c8 | +| test.cpp:16:7:16:13 | ... + ... | 16:c7-c13 17:c7-c13 | +| test.cpp:16:7:16:24 | ... + ... | 16:c7-c24 17:c7-c24 18:c7-c7 | +| test.cpp:16:12:16:13 | p1 | 16:c12-c13 17:c12-c13 | +| test.cpp:16:17:16:24 | global01 | 16:c17-c24 17:c17-c24 | +| test.cpp:29:7:29:8 | p0 | 29:c7-c8 31:c7-c8 | +| test.cpp:29:7:29:13 | ... + ... | 29:c7-c13 31:c7-c13 | +| test.cpp:29:12:29:13 | p1 | 29:c12-c13 31:c12-c13 | +| test.cpp:31:7:31:24 | ... + ... | 31:c7-c24 32:c7-c7 | +| test.cpp:43:7:43:8 | p0 | 43:c7-c8 45:c7-c8 | +| test.cpp:43:7:43:13 | ... + ... | 43:c7-c13 45:c7-c13 | +| test.cpp:43:12:43:13 | p1 | 43:c12-c13 45:c12-c13 | +| test.cpp:44:9:44:9 | 0 | 44:c9-c9 51:c25-c25 53:c18-c21 56:c39-c42 59:c17-c20 88:c12-c12 | +| test.cpp:45:7:45:24 | ... + ... | 45:c7-c24 46:c7-c7 | +| test.cpp:53:10:53:13 | (int)... | 53:c10-c13 56:c21-c24 | +| test.cpp:53:10:53:13 | * ... | 53:c10-c13 56:c21-c24 | +| test.cpp:53:11:53:13 | str | 53:c11-c13 56:c22-c24 | +| test.cpp:53:18:53:21 | 0 | 53:c18-c21 56:c39-c42 59:c17-c20 | +| test.cpp:56:13:56:16 | (int)... | 56:c13-c16 56:c31-c34 59:c9-c12 | +| test.cpp:56:13:56:16 | * ... | 56:c13-c16 56:c31-c34 59:c9-c12 | +| test.cpp:56:14:56:16 | ptr | 56:c14-c16 56:c32-c34 56:c47-c49 59:c10-c12 | +| test.cpp:62:5:62:10 | result | 62:c5-c10 65:c10-c15 | +| test.cpp:77:20:77:30 | (signed short)... | 77:c20-c30 79:c7-c7 | +| test.cpp:79:11:79:14 | vals | 79:c11-c14 79:c24-c27 | +| test.cpp:105:11:105:12 | (Base *)... | 105:c11-c12 106:c14-c35 107:c11-c12 | +| test.cpp:105:11:105:12 | pd | 105:c11-c12 106:c33-c34 | +| test.cpp:105:15:105:15 | b | 105:c15-c15 107:c15-c15 109:c10-c10 | +| test.cpp:125:11:125:12 | pa | 125:c11-c12 126:c11-c12 128:c3-c4 129:c11-c12 | +| test.cpp:125:15:125:15 | x | 125:c15-c15 126:c15-c15 128:c7-c7 | +| test.cpp:136:11:136:18 | global_a | 136:c11-c18 137:c11-c18 139:c3-c10 | +| test.cpp:136:21:136:21 | x | 136:c21-c21 137:c21-c21 139:c13-c13 | +| test.cpp:144:11:144:12 | pa | 144:c11-c12 145:c11-c12 147:c3-c4 149:c11-c12 | +| test.cpp:145:15:145:15 | y | 145:c15-c15 147:c7-c7 | +| test.cpp:153:11:153:18 | global_a | 153:c11-c18 154:c11-c18 156:c3-c10 | +| test.cpp:153:21:153:21 | x | 153:c21-c21 154:c21-c21 | diff --git a/cpp/ql/test/library-tests/valuenumbering/GlobalValueNumbering/GlobalValueNumbering.ql b/cpp/ql/test/library-tests/valuenumbering/GlobalValueNumbering/ast_gvn.ql similarity index 78% rename from cpp/ql/test/library-tests/valuenumbering/GlobalValueNumbering/GlobalValueNumbering.ql rename to cpp/ql/test/library-tests/valuenumbering/GlobalValueNumbering/ast_gvn.ql index 3807d5c36e0..84d0a7b3672 100644 --- a/cpp/ql/test/library-tests/valuenumbering/GlobalValueNumbering/GlobalValueNumbering.ql +++ b/cpp/ql/test/library-tests/valuenumbering/GlobalValueNumbering/ast_gvn.ql @@ -1,5 +1,5 @@ import cpp -import semmle.code.cpp.valuenumbering.GlobalValueNumbering +import semmle.code.cpp.valuenumbering.GlobalValueNumberingImpl from GVN g where strictcount(g.getAnExpr()) > 1 diff --git a/cpp/ql/test/library-tests/valuenumbering/GlobalValueNumbering/ast_uniqueness.expected b/cpp/ql/test/library-tests/valuenumbering/GlobalValueNumbering/ast_uniqueness.expected new file mode 100644 index 00000000000..e69de29bb2d diff --git a/cpp/ql/test/library-tests/valuenumbering/GlobalValueNumbering/Uniqueness.ql b/cpp/ql/test/library-tests/valuenumbering/GlobalValueNumbering/ast_uniqueness.ql similarity index 78% rename from cpp/ql/test/library-tests/valuenumbering/GlobalValueNumbering/Uniqueness.ql rename to cpp/ql/test/library-tests/valuenumbering/GlobalValueNumbering/ast_uniqueness.ql index 5b020d757ee..bc6dbf94bf7 100644 --- a/cpp/ql/test/library-tests/valuenumbering/GlobalValueNumbering/Uniqueness.ql +++ b/cpp/ql/test/library-tests/valuenumbering/GlobalValueNumbering/ast_uniqueness.ql @@ -1,5 +1,5 @@ import cpp -import semmle.code.cpp.valuenumbering.GlobalValueNumbering +import semmle.code.cpp.valuenumbering.GlobalValueNumberingImpl // Every expression should have exactly one GVN. // So this query should have zero results. diff --git a/cpp/ql/test/library-tests/valuenumbering/GlobalValueNumbering/diff_ir_expr.expected b/cpp/ql/test/library-tests/valuenumbering/GlobalValueNumbering/diff_ir_expr.expected index e69de29bb2d..cd44eb8572b 100644 --- a/cpp/ql/test/library-tests/valuenumbering/GlobalValueNumbering/diff_ir_expr.expected +++ b/cpp/ql/test/library-tests/valuenumbering/GlobalValueNumbering/diff_ir_expr.expected @@ -0,0 +1,142 @@ +| test.cpp:5:3:5:13 | ... = ... | test.cpp:5:3:5:13 | ... = ... | AST only | +| test.cpp:6:3:6:13 | ... = ... | test.cpp:6:3:6:13 | ... = ... | AST only | +| test.cpp:7:3:7:7 | ... = ... | test.cpp:7:3:7:7 | ... = ... | AST only | +| test.cpp:10:16:10:16 | 1 | test.cpp:10:16:10:16 | 1 | AST only | +| test.cpp:16:3:16:24 | ... = ... | test.cpp:16:3:16:24 | ... = ... | AST only | +| test.cpp:17:3:17:24 | ... = ... | test.cpp:17:3:17:24 | ... = ... | AST only | +| test.cpp:18:3:18:7 | ... = ... | test.cpp:18:3:18:7 | ... = ... | AST only | +| test.cpp:21:16:21:16 | 2 | test.cpp:21:16:21:16 | 2 | AST only | +| test.cpp:29:3:29:3 | x | test.cpp:31:3:31:3 | x | IR only | +| test.cpp:29:3:29:24 | ... = ... | test.cpp:29:3:29:24 | ... = ... | AST only | +| test.cpp:30:3:30:17 | call to change_global02 | test.cpp:30:3:30:17 | call to change_global02 | AST only | +| test.cpp:31:3:31:3 | x | test.cpp:29:3:29:3 | x | IR only | +| test.cpp:31:3:31:24 | ... = ... | test.cpp:31:3:31:24 | ... = ... | AST only | +| test.cpp:32:3:32:7 | ... = ... | test.cpp:32:3:32:7 | ... = ... | AST only | +| test.cpp:35:16:35:16 | 3 | test.cpp:35:16:35:16 | 3 | AST only | +| test.cpp:43:3:43:3 | x | test.cpp:45:3:45:3 | x | IR only | +| test.cpp:43:3:43:24 | ... = ... | test.cpp:43:3:43:24 | ... = ... | AST only | +| test.cpp:43:7:43:24 | ... + ... | test.cpp:45:7:45:24 | ... + ... | IR only | +| test.cpp:43:7:43:24 | ... + ... | test.cpp:46:7:46:7 | x | IR only | +| test.cpp:43:17:43:24 | global03 | test.cpp:45:17:45:24 | global03 | IR only | +| test.cpp:44:3:44:5 | * ... | test.cpp:44:4:44:5 | p2 | IR only | +| test.cpp:44:3:44:9 | ... = ... | test.cpp:44:3:44:9 | ... = ... | AST only | +| test.cpp:44:4:44:5 | p2 | test.cpp:44:3:44:5 | * ... | IR only | +| test.cpp:44:9:44:9 | 0 | test.cpp:51:25:51:25 | 0 | AST only | +| test.cpp:44:9:44:9 | 0 | test.cpp:53:18:53:21 | (int)... | AST only | +| test.cpp:44:9:44:9 | 0 | test.cpp:56:39:56:42 | (int)... | AST only | +| test.cpp:44:9:44:9 | 0 | test.cpp:59:17:59:20 | (int)... | AST only | +| test.cpp:44:9:44:9 | 0 | test.cpp:88:12:88:12 | 0 | AST only | +| test.cpp:45:3:45:3 | x | test.cpp:43:3:43:3 | x | IR only | +| test.cpp:45:3:45:24 | ... = ... | test.cpp:45:3:45:24 | ... = ... | AST only | +| test.cpp:45:7:45:24 | ... + ... | test.cpp:43:7:43:24 | ... + ... | IR only | +| test.cpp:45:17:45:24 | global03 | test.cpp:43:17:43:24 | global03 | IR only | +| test.cpp:46:3:46:7 | ... = ... | test.cpp:46:3:46:7 | ... = ... | AST only | +| test.cpp:46:7:46:7 | x | test.cpp:43:7:43:24 | ... + ... | IR only | +| test.cpp:51:25:51:25 | 0 | test.cpp:44:9:44:9 | 0 | AST only | +| test.cpp:51:25:51:25 | 0 | test.cpp:53:18:53:21 | (int)... | AST only | +| test.cpp:51:25:51:25 | 0 | test.cpp:56:39:56:42 | (int)... | AST only | +| test.cpp:51:25:51:25 | 0 | test.cpp:59:17:59:20 | (int)... | AST only | +| test.cpp:51:25:51:25 | 0 | test.cpp:88:12:88:12 | 0 | AST only | +| test.cpp:51:25:51:25 | (unsigned int)... | test.cpp:51:25:51:25 | (unsigned int)... | AST only | +| test.cpp:53:10:53:13 | (int)... | test.cpp:53:10:53:13 | (int)... | AST only | +| test.cpp:53:10:53:13 | (int)... | test.cpp:56:21:56:24 | (int)... | AST only | +| test.cpp:53:18:53:21 | (int)... | test.cpp:44:9:44:9 | 0 | AST only | +| test.cpp:53:18:53:21 | (int)... | test.cpp:51:25:51:25 | 0 | AST only | +| test.cpp:53:18:53:21 | (int)... | test.cpp:53:18:53:21 | (int)... | AST only | +| test.cpp:53:18:53:21 | (int)... | test.cpp:56:39:56:42 | (int)... | AST only | +| test.cpp:53:18:53:21 | (int)... | test.cpp:59:17:59:20 | (int)... | AST only | +| test.cpp:53:18:53:21 | (int)... | test.cpp:88:12:88:12 | 0 | AST only | +| test.cpp:55:5:55:15 | ... = ... | test.cpp:55:5:55:15 | ... = ... | AST only | +| test.cpp:56:12:56:25 | (...) | test.cpp:56:12:56:25 | (...) | AST only | +| test.cpp:56:12:56:43 | ... && ... | test.cpp:56:12:56:43 | ... && ... | AST only | +| test.cpp:56:13:56:16 | (int)... | test.cpp:56:13:56:16 | (int)... | AST only | +| test.cpp:56:13:56:16 | (int)... | test.cpp:56:31:56:34 | (int)... | AST only | +| test.cpp:56:13:56:16 | (int)... | test.cpp:59:9:59:12 | (int)... | AST only | +| test.cpp:56:13:56:16 | * ... | test.cpp:56:31:56:34 | * ... | AST only | +| test.cpp:56:13:56:16 | * ... | test.cpp:59:9:59:12 | * ... | AST only | +| test.cpp:56:21:56:24 | (int)... | test.cpp:53:10:53:13 | (int)... | AST only | +| test.cpp:56:21:56:24 | (int)... | test.cpp:56:21:56:24 | (int)... | AST only | +| test.cpp:56:30:56:43 | (...) | test.cpp:56:30:56:43 | (...) | AST only | +| test.cpp:56:31:56:34 | (int)... | test.cpp:56:13:56:16 | (int)... | AST only | +| test.cpp:56:31:56:34 | (int)... | test.cpp:56:31:56:34 | (int)... | AST only | +| test.cpp:56:31:56:34 | (int)... | test.cpp:59:9:59:12 | (int)... | AST only | +| test.cpp:56:31:56:34 | * ... | test.cpp:56:13:56:16 | * ... | AST only | +| test.cpp:56:31:56:34 | * ... | test.cpp:59:9:59:12 | * ... | AST only | +| test.cpp:56:39:56:42 | (int)... | test.cpp:44:9:44:9 | 0 | AST only | +| test.cpp:56:39:56:42 | (int)... | test.cpp:51:25:51:25 | 0 | AST only | +| test.cpp:56:39:56:42 | (int)... | test.cpp:53:18:53:21 | (int)... | AST only | +| test.cpp:56:39:56:42 | (int)... | test.cpp:56:39:56:42 | (int)... | AST only | +| test.cpp:56:39:56:42 | (int)... | test.cpp:59:17:59:20 | (int)... | AST only | +| test.cpp:56:39:56:42 | (int)... | test.cpp:88:12:88:12 | 0 | AST only | +| test.cpp:56:47:56:51 | ... ++ | test.cpp:56:47:56:51 | ... ++ | AST only | +| test.cpp:59:9:59:12 | (int)... | test.cpp:56:13:56:16 | (int)... | AST only | +| test.cpp:59:9:59:12 | (int)... | test.cpp:56:31:56:34 | (int)... | AST only | +| test.cpp:59:9:59:12 | (int)... | test.cpp:59:9:59:12 | (int)... | AST only | +| test.cpp:59:9:59:12 | * ... | test.cpp:56:13:56:16 | * ... | AST only | +| test.cpp:59:9:59:12 | * ... | test.cpp:56:31:56:34 | * ... | AST only | +| test.cpp:59:17:59:20 | (int)... | test.cpp:44:9:44:9 | 0 | AST only | +| test.cpp:59:17:59:20 | (int)... | test.cpp:51:25:51:25 | 0 | AST only | +| test.cpp:59:17:59:20 | (int)... | test.cpp:53:18:53:21 | (int)... | AST only | +| test.cpp:59:17:59:20 | (int)... | test.cpp:56:39:56:42 | (int)... | AST only | +| test.cpp:59:17:59:20 | (int)... | test.cpp:59:17:59:20 | (int)... | AST only | +| test.cpp:59:17:59:20 | (int)... | test.cpp:88:12:88:12 | 0 | AST only | +| test.cpp:62:5:62:12 | ... ++ | test.cpp:62:5:62:12 | ... ++ | AST only | +| test.cpp:77:20:77:28 | call to getAValue | test.cpp:79:7:79:7 | v | IR only | +| test.cpp:77:20:77:30 | (signed short)... | test.cpp:77:20:77:30 | (signed short)... | AST only | +| test.cpp:77:20:77:30 | (signed short)... | test.cpp:79:7:79:7 | v | AST only | +| test.cpp:79:7:79:7 | (int)... | test.cpp:79:7:79:7 | (int)... | AST only | +| test.cpp:79:7:79:7 | v | test.cpp:77:20:77:28 | call to getAValue | IR only | +| test.cpp:79:7:79:7 | v | test.cpp:77:20:77:30 | (signed short)... | AST only | +| test.cpp:79:11:79:20 | (int)... | test.cpp:79:11:79:20 | (int)... | AST only | +| test.cpp:79:24:79:33 | (int)... | test.cpp:79:24:79:33 | (int)... | AST only | +| test.cpp:80:5:80:19 | ... = ... | test.cpp:80:5:80:19 | ... = ... | AST only | +| test.cpp:80:9:80:19 | (signed short)... | test.cpp:80:9:80:19 | (signed short)... | AST only | +| test.cpp:88:3:88:20 | ... = ... | test.cpp:88:3:88:20 | ... = ... | AST only | +| test.cpp:88:12:88:12 | 0 | test.cpp:44:9:44:9 | 0 | AST only | +| test.cpp:88:12:88:12 | 0 | test.cpp:51:25:51:25 | 0 | AST only | +| test.cpp:88:12:88:12 | 0 | test.cpp:53:18:53:21 | (int)... | AST only | +| test.cpp:88:12:88:12 | 0 | test.cpp:56:39:56:42 | (int)... | AST only | +| test.cpp:88:12:88:12 | 0 | test.cpp:59:17:59:20 | (int)... | AST only | +| test.cpp:88:12:88:12 | (void *)... | test.cpp:88:12:88:12 | (void *)... | AST only | +| test.cpp:92:11:92:16 | ... = ... | test.cpp:92:15:92:16 | 10 | IR only | +| test.cpp:92:11:92:16 | ... = ... | test.cpp:93:10:93:10 | x | IR only | +| test.cpp:92:15:92:16 | 10 | test.cpp:92:11:92:16 | ... = ... | IR only | +| test.cpp:92:15:92:16 | 10 | test.cpp:93:10:93:10 | x | IR only | +| test.cpp:93:10:93:10 | x | test.cpp:92:11:92:16 | ... = ... | IR only | +| test.cpp:93:10:93:10 | x | test.cpp:92:15:92:16 | 10 | IR only | +| test.cpp:105:11:105:12 | (Base *)... | test.cpp:105:11:105:12 | (Base *)... | AST only | +| test.cpp:105:11:105:12 | (Base *)... | test.cpp:106:14:106:35 | static_cast... | AST only | +| test.cpp:105:11:105:12 | (Base *)... | test.cpp:107:11:107:12 | pb | AST only | +| test.cpp:105:11:105:12 | pd | test.cpp:107:11:107:12 | pb | IR only | +| test.cpp:106:14:106:35 | static_cast... | test.cpp:105:11:105:12 | (Base *)... | AST only | +| test.cpp:106:14:106:35 | static_cast... | test.cpp:106:14:106:35 | static_cast... | AST only | +| test.cpp:106:14:106:35 | static_cast... | test.cpp:107:11:107:12 | pb | AST only | +| test.cpp:106:33:106:34 | pd | test.cpp:107:11:107:12 | pb | IR only | +| test.cpp:107:11:107:12 | pb | test.cpp:105:11:105:12 | (Base *)... | AST only | +| test.cpp:107:11:107:12 | pb | test.cpp:105:11:105:12 | pd | IR only | +| test.cpp:107:11:107:12 | pb | test.cpp:106:14:106:35 | static_cast... | AST only | +| test.cpp:107:11:107:12 | pb | test.cpp:106:33:106:34 | pd | IR only | +| test.cpp:113:3:113:5 | a | test.cpp:115:3:115:5 | a | IR only | +| test.cpp:115:3:115:5 | a | test.cpp:113:3:113:5 | a | IR only | +| test.cpp:125:15:125:15 | x | test.cpp:128:7:128:7 | x | AST only | +| test.cpp:126:15:126:15 | x | test.cpp:128:7:128:7 | x | AST only | +| test.cpp:128:3:128:11 | ... = ... | test.cpp:128:3:128:11 | ... = ... | AST only | +| test.cpp:128:7:128:7 | x | test.cpp:125:15:125:15 | x | AST only | +| test.cpp:128:7:128:7 | x | test.cpp:126:15:126:15 | x | AST only | +| test.cpp:128:11:128:11 | n | test.cpp:129:15:129:15 | x | IR only | +| test.cpp:129:15:129:15 | x | test.cpp:128:11:128:11 | n | IR only | +| test.cpp:136:21:136:21 | x | test.cpp:137:21:137:21 | x | AST only | +| test.cpp:136:21:136:21 | x | test.cpp:139:13:139:13 | x | AST only | +| test.cpp:137:21:137:21 | x | test.cpp:136:21:136:21 | x | AST only | +| test.cpp:137:21:137:21 | x | test.cpp:139:13:139:13 | x | AST only | +| test.cpp:139:3:139:24 | ... = ... | test.cpp:139:3:139:24 | ... = ... | AST only | +| test.cpp:139:13:139:13 | x | test.cpp:136:21:136:21 | x | AST only | +| test.cpp:139:13:139:13 | x | test.cpp:137:21:137:21 | x | AST only | +| test.cpp:144:15:144:15 | x | test.cpp:149:15:149:15 | x | IR only | +| test.cpp:145:15:145:15 | y | test.cpp:147:7:147:7 | y | AST only | +| test.cpp:147:3:147:18 | ... = ... | test.cpp:147:3:147:18 | ... = ... | AST only | +| test.cpp:147:7:147:7 | y | test.cpp:145:15:145:15 | y | AST only | +| test.cpp:149:15:149:15 | x | test.cpp:144:15:144:15 | x | IR only | +| test.cpp:153:21:153:21 | x | test.cpp:154:21:154:21 | x | AST only | +| test.cpp:154:21:154:21 | x | test.cpp:153:21:153:21 | x | AST only | +| test.cpp:156:3:156:17 | ... = ... | test.cpp:156:3:156:17 | ... = ... | AST only | diff --git a/cpp/ql/test/library-tests/valuenumbering/GlobalValueNumbering/diff_ir_expr.ql b/cpp/ql/test/library-tests/valuenumbering/GlobalValueNumbering/diff_ir_expr.ql index 317c3d97556..7b897f39dbd 100644 --- a/cpp/ql/test/library-tests/valuenumbering/GlobalValueNumbering/diff_ir_expr.ql +++ b/cpp/ql/test/library-tests/valuenumbering/GlobalValueNumbering/diff_ir_expr.ql @@ -1,5 +1,5 @@ import cpp -import semmle.code.cpp.valuenumbering.GlobalValueNumbering as AST +import semmle.code.cpp.valuenumbering.GlobalValueNumberingImpl as AST import semmle.code.cpp.ir.internal.ASTValueNumbering as IR import semmle.code.cpp.ir.IR diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-134/semmle/argv/argvLocal.c b/cpp/ql/test/query-tests/Security/CWE/CWE-134/semmle/argv/argvLocal.c index e83c6061f95..beaad7d0fd3 100644 --- a/cpp/ql/test/query-tests/Security/CWE/CWE-134/semmle/argv/argvLocal.c +++ b/cpp/ql/test/query-tests/Security/CWE/CWE-134/semmle/argv/argvLocal.c @@ -146,19 +146,21 @@ int main(int argc, char **argv) { // BAD: i8 value comes from argv char *i8; - *(&i8 + 1) = argv[1]; + *(&i8) = argv[1]; printf(i8); printWrapper(i8); // BAD: i9 value comes from argv - char *i9; - memcpy(1 ? i9++ : 0, argv[1], 1); + char i9buf[32]; + char *i9 = i9buf; + memcpy(1 ? ++i9 : 0, argv[1], 1); printf(i9); printWrapper(i9); // BAD: i91 value comes from argv - char *i91; - memcpy(0 ? 0 : (char *)((int) i91 * 2), argv[1], 1); + char i91buf[64]; + char *i91 = &i91buf[0]; + memcpy(0 ? 0 : i91, argv[1] + 1, 1); printf(i91); printWrapper(i91); diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-134/semmle/argv/argvLocal.expected b/cpp/ql/test/query-tests/Security/CWE/CWE-134/semmle/argv/argvLocal.expected index ea24f969f34..7f342b0aabe 100644 --- a/cpp/ql/test/query-tests/Security/CWE/CWE-134/semmle/argv/argvLocal.expected +++ b/cpp/ql/test/query-tests/Security/CWE/CWE-134/semmle/argv/argvLocal.expected @@ -18,5 +18,11 @@ | argvLocal.c:136:15:136:18 | -- ... | The value of this argument may come from $@ and is being used as a formatting argument to printWrapper(correct), which calls printf(format) | argvLocal.c:115:13:115:16 | argv | argv | | argvLocal.c:144:9:144:10 | i7 | The value of this argument may come from $@ and is being used as a formatting argument to printf(format) | argvLocal.c:100:7:100:10 | argv | argv | | argvLocal.c:145:15:145:16 | i7 | The value of this argument may come from $@ and is being used as a formatting argument to printWrapper(correct), which calls printf(format) | argvLocal.c:100:7:100:10 | argv | argv | -| argvLocal.c:167:18:167:20 | i10 | The value of this argument may come from $@ and is being used as a formatting argument to printf(format) | argvLocal.c:166:18:166:21 | argv | argv | -| argvLocal.c:168:24:168:26 | i10 | The value of this argument may come from $@ and is being used as a formatting argument to printWrapper(correct), which calls printf(format) | argvLocal.c:166:18:166:21 | argv | argv | +| argvLocal.c:150:9:150:10 | i8 | The value of this argument may come from $@ and is being used as a formatting argument to printf(format) | argvLocal.c:149:11:149:14 | argv | argv | +| argvLocal.c:151:15:151:16 | i8 | The value of this argument may come from $@ and is being used as a formatting argument to printWrapper(correct), which calls printf(format) | argvLocal.c:149:11:149:14 | argv | argv | +| argvLocal.c:157:9:157:10 | i9 | The value of this argument may come from $@ and is being used as a formatting argument to printf(format) | argvLocal.c:156:23:156:26 | argv | argv | +| argvLocal.c:158:15:158:16 | i9 | The value of this argument may come from $@ and is being used as a formatting argument to printWrapper(correct), which calls printf(format) | argvLocal.c:156:23:156:26 | argv | argv | +| argvLocal.c:164:9:164:11 | i91 | The value of this argument may come from $@ and is being used as a formatting argument to printf(format) | argvLocal.c:163:22:163:25 | argv | argv | +| argvLocal.c:165:15:165:17 | i91 | The value of this argument may come from $@ and is being used as a formatting argument to printWrapper(correct), which calls printf(format) | argvLocal.c:163:22:163:25 | argv | argv | +| argvLocal.c:169:18:169:20 | i10 | The value of this argument may come from $@ and is being used as a formatting argument to printf(format) | argvLocal.c:168:18:168:21 | argv | argv | +| argvLocal.c:170:24:170:26 | i10 | The value of this argument may come from $@ and is being used as a formatting argument to printWrapper(correct), which calls printf(format) | argvLocal.c:168:18:168:21 | argv | argv | diff --git a/csharp/autobuilder/Semmle.Autobuild.Tests/Semmle.Autobuild.Tests.csproj b/csharp/autobuilder/Semmle.Autobuild.Tests/Semmle.Autobuild.Tests.csproj index ef4a016e20b..547aaa570b4 100644 --- a/csharp/autobuilder/Semmle.Autobuild.Tests/Semmle.Autobuild.Tests.csproj +++ b/csharp/autobuilder/Semmle.Autobuild.Tests/Semmle.Autobuild.Tests.csproj @@ -4,6 +4,7 @@ Exe netcoreapp3.0 false + win-x64;linux-x64;osx-x64 diff --git a/csharp/autobuilder/Semmle.Autobuild/Semmle.Autobuild.csproj b/csharp/autobuilder/Semmle.Autobuild/Semmle.Autobuild.csproj index 089369c38e3..5e3f2295ad0 100644 --- a/csharp/autobuilder/Semmle.Autobuild/Semmle.Autobuild.csproj +++ b/csharp/autobuilder/Semmle.Autobuild/Semmle.Autobuild.csproj @@ -8,6 +8,7 @@ Exe false + win-x64;linux-x64;osx-x64 diff --git a/csharp/extractor/Semmle.Extraction.CIL.Driver/Semmle.Extraction.CIL.Driver.csproj b/csharp/extractor/Semmle.Extraction.CIL.Driver/Semmle.Extraction.CIL.Driver.csproj index a11aeefa7ae..e7f87c7b36b 100644 --- a/csharp/extractor/Semmle.Extraction.CIL.Driver/Semmle.Extraction.CIL.Driver.csproj +++ b/csharp/extractor/Semmle.Extraction.CIL.Driver/Semmle.Extraction.CIL.Driver.csproj @@ -6,6 +6,7 @@ Semmle.Extraction.CIL.Driver Semmle.Extraction.CIL.Driver false + win-x64;linux-x64;osx-x64 diff --git a/csharp/extractor/Semmle.Extraction.CIL/Semmle.Extraction.CIL.csproj b/csharp/extractor/Semmle.Extraction.CIL/Semmle.Extraction.CIL.csproj index 131c49c845b..9db880787b0 100644 --- a/csharp/extractor/Semmle.Extraction.CIL/Semmle.Extraction.CIL.csproj +++ b/csharp/extractor/Semmle.Extraction.CIL/Semmle.Extraction.CIL.csproj @@ -6,6 +6,7 @@ Semmle.Extraction.CIL false true + win-x64;linux-x64;osx-x64 diff --git a/csharp/extractor/Semmle.Extraction.CSharp.Driver/Semmle.Extraction.CSharp.Driver.csproj b/csharp/extractor/Semmle.Extraction.CSharp.Driver/Semmle.Extraction.CSharp.Driver.csproj index 9113890ddf7..7475350f993 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp.Driver/Semmle.Extraction.CSharp.Driver.csproj +++ b/csharp/extractor/Semmle.Extraction.CSharp.Driver/Semmle.Extraction.CSharp.Driver.csproj @@ -6,6 +6,7 @@ Semmle.Extraction.CSharp.Driver Semmle.Extraction.CSharp.Driver false + win-x64;linux-x64;osx-x64 diff --git a/csharp/extractor/Semmle.Extraction.CSharp.Standalone/Semmle.Extraction.CSharp.Standalone.csproj b/csharp/extractor/Semmle.Extraction.CSharp.Standalone/Semmle.Extraction.CSharp.Standalone.csproj index 5277e352f4c..4cf0274b737 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp.Standalone/Semmle.Extraction.CSharp.Standalone.csproj +++ b/csharp/extractor/Semmle.Extraction.CSharp.Standalone/Semmle.Extraction.CSharp.Standalone.csproj @@ -9,6 +9,7 @@ true false + win-x64;linux-x64;osx-x64 diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Semmle.Extraction.CSharp.csproj b/csharp/extractor/Semmle.Extraction.CSharp/Semmle.Extraction.CSharp.csproj index 5b8e3961ad6..87234841c5f 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp/Semmle.Extraction.CSharp.csproj +++ b/csharp/extractor/Semmle.Extraction.CSharp/Semmle.Extraction.CSharp.csproj @@ -6,6 +6,7 @@ Semmle.Extraction.CSharp false true + win-x64;linux-x64;osx-x64 diff --git a/csharp/extractor/Semmle.Extraction.Tests/Semmle.Extraction.Tests.csproj b/csharp/extractor/Semmle.Extraction.Tests/Semmle.Extraction.Tests.csproj index b003709e37a..c101a5fba83 100644 --- a/csharp/extractor/Semmle.Extraction.Tests/Semmle.Extraction.Tests.csproj +++ b/csharp/extractor/Semmle.Extraction.Tests/Semmle.Extraction.Tests.csproj @@ -4,6 +4,7 @@ Exe netcoreapp3.0 false + win-x64;linux-x64;osx-x64 diff --git a/csharp/extractor/Semmle.Extraction/Semmle.Extraction.csproj b/csharp/extractor/Semmle.Extraction/Semmle.Extraction.csproj index a3387c16065..48e2a00c9f3 100644 --- a/csharp/extractor/Semmle.Extraction/Semmle.Extraction.csproj +++ b/csharp/extractor/Semmle.Extraction/Semmle.Extraction.csproj @@ -6,6 +6,7 @@ Semmle.Extraction false Semmle.Extraction.ruleset + win-x64;linux-x64;osx-x64 diff --git a/csharp/extractor/Semmle.Util.Tests/Semmle.Util.Tests.csproj b/csharp/extractor/Semmle.Util.Tests/Semmle.Util.Tests.csproj index 45f28343e67..d0ef95a4d4c 100644 --- a/csharp/extractor/Semmle.Util.Tests/Semmle.Util.Tests.csproj +++ b/csharp/extractor/Semmle.Util.Tests/Semmle.Util.Tests.csproj @@ -4,6 +4,7 @@ Exe netcoreapp3.0 false + win-x64;linux-x64;osx-x64 diff --git a/csharp/extractor/Semmle.Util/Semmle.Util.csproj b/csharp/extractor/Semmle.Util/Semmle.Util.csproj index 4d35dcae079..4ef3eda35d9 100644 --- a/csharp/extractor/Semmle.Util/Semmle.Util.csproj +++ b/csharp/extractor/Semmle.Util/Semmle.Util.csproj @@ -5,6 +5,7 @@ Semmle.Util Semmle.Util false + win-x64;linux-x64;osx-x64 diff --git a/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl.qll b/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl.qll index 86d44e4fcce..5eb98aefe4e 100644 --- a/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl.qll +++ b/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl.qll @@ -259,44 +259,47 @@ private ReturnPosition viableReturnPos(DataFlowCall call, ReturnKindExt kind) { /** * Holds if `node` is reachable from a source in the given configuration - * ignoring call contexts. + * taking simple call contexts into consideration. */ -private predicate nodeCandFwd1(Node node, Configuration config) { +private predicate nodeCandFwd1(Node node, boolean fromArg, Configuration config) { not fullBarrier(node, config) and ( - config.isSource(node) + config.isSource(node) and + fromArg = false or exists(Node mid | - nodeCandFwd1(mid, config) and + nodeCandFwd1(mid, fromArg, config) and localFlowStep(mid, node, config) ) or exists(Node mid | - nodeCandFwd1(mid, config) and + nodeCandFwd1(mid, fromArg, config) and additionalLocalFlowStep(mid, node, config) ) or exists(Node mid | nodeCandFwd1(mid, config) and - jumpStep(mid, node, config) + jumpStep(mid, node, config) and + fromArg = false ) or exists(Node mid | nodeCandFwd1(mid, config) and - additionalJumpStep(mid, node, config) + additionalJumpStep(mid, node, config) and + fromArg = false ) or // store exists(Node mid | useFieldFlow(config) and - nodeCandFwd1(mid, config) and + nodeCandFwd1(mid, fromArg, config) and storeDirect(mid, _, node) and not outBarrier(mid, config) ) or // read exists(Content f | - nodeCandFwd1Read(f, node, config) and + nodeCandFwd1Read(f, node, fromArg, config) and storeCandFwd1(f, config) and not inBarrier(node, config) ) @@ -304,30 +307,37 @@ private predicate nodeCandFwd1(Node node, Configuration config) { // flow into a callable exists(Node arg | nodeCandFwd1(arg, config) and - viableParamArg(_, node, arg) + viableParamArg(_, node, arg) and + fromArg = true ) or // flow out of a callable - exists(DataFlowCall call, ReturnPosition pos, ReturnKindExt kind | - nodeCandFwd1ReturnPosition(pos, config) and - pos = viableReturnPos(call, kind) and - node = kind.getAnOutNode(call) + exists(DataFlowCall call | + nodeCandFwd1Out(call, node, false, config) and + fromArg = false + or + nodeCandFwd1OutFromArg(call, node, config) and + flowOutCandFwd1(call, fromArg, config) ) ) } -pragma[noinline] -private predicate nodeCandFwd1ReturnPosition(ReturnPosition pos, Configuration config) { +private predicate nodeCandFwd1(Node node, Configuration config) { nodeCandFwd1(node, _, config) } + +pragma[nomagic] +private predicate nodeCandFwd1ReturnPosition( + ReturnPosition pos, boolean fromArg, Configuration config +) { exists(ReturnNodeExt ret | - nodeCandFwd1(ret, config) and + nodeCandFwd1(ret, fromArg, config) and getReturnPosition(ret) = pos ) } pragma[nomagic] -private predicate nodeCandFwd1Read(Content f, Node node, Configuration config) { +private predicate nodeCandFwd1Read(Content f, Node node, boolean fromArg, Configuration config) { exists(Node mid | - nodeCandFwd1(mid, config) and + nodeCandFwd1(mid, fromArg, config) and readDirect(mid, f, node) ) } @@ -335,7 +345,7 @@ private predicate nodeCandFwd1Read(Content f, Node node, Configuration config) { /** * Holds if `f` is the target of a store in the flow covered by `nodeCandFwd1`. */ -pragma[noinline] +pragma[nomagic] private predicate storeCandFwd1(Content f, Configuration config) { exists(Node mid, Node node | not fullBarrier(node, config) and @@ -345,71 +355,120 @@ private predicate storeCandFwd1(Content f, Configuration config) { ) } +pragma[nomagic] +private predicate nodeCandFwd1ReturnKind( + DataFlowCall call, ReturnKindExt kind, boolean fromArg, Configuration config +) { + exists(ReturnPosition pos | + nodeCandFwd1ReturnPosition(pos, fromArg, config) and + pos = viableReturnPos(call, kind) + ) +} + +pragma[nomagic] +private predicate nodeCandFwd1Out( + DataFlowCall call, Node node, boolean fromArg, Configuration config +) { + exists(ReturnKindExt kind | + nodeCandFwd1ReturnKind(call, kind, fromArg, config) and + node = kind.getAnOutNode(call) + ) +} + +pragma[nomagic] +private predicate nodeCandFwd1OutFromArg(DataFlowCall call, Node node, Configuration config) { + nodeCandFwd1Out(call, node, true, config) +} + +/** + * Holds if an argument to `call` is reached in the flow covered by `nodeCandFwd1`. + */ +pragma[nomagic] +private predicate flowOutCandFwd1(DataFlowCall call, boolean fromArg, Configuration config) { + exists(ArgumentNode arg | + nodeCandFwd1(arg, fromArg, config) and + viableParamArg(call, _, arg) + ) +} + bindingset[result, b] private boolean unbindBool(boolean b) { result != b.booleanNot() } /** * Holds if `node` is part of a path from a source to a sink in the given - * configuration ignoring call contexts. + * configuration taking simple call contexts into consideration. */ pragma[nomagic] -private predicate nodeCand1(Node node, Configuration config) { +private predicate nodeCand1(Node node, boolean toReturn, Configuration config) { + nodeCand1_0(node, toReturn, config) and + nodeCandFwd1(node, config) +} + +pragma[nomagic] +private predicate nodeCand1_0(Node node, boolean toReturn, Configuration config) { nodeCandFwd1(node, config) and - config.isSink(node) + config.isSink(node) and + toReturn = false or - nodeCandFwd1(node, unbind(config)) and - ( - exists(Node mid | - localFlowStep(node, mid, config) and - nodeCand1(mid, config) - ) + exists(Node mid | + localFlowStep(node, mid, config) and + nodeCand1(mid, toReturn, config) + ) + or + exists(Node mid | + additionalLocalFlowStep(node, mid, config) and + nodeCand1(mid, toReturn, config) + ) + or + exists(Node mid | + jumpStep(node, mid, config) and + nodeCand1(mid, _, config) and + toReturn = false + ) + or + exists(Node mid | + additionalJumpStep(node, mid, config) and + nodeCand1(mid, _, config) and + toReturn = false + ) + or + // store + exists(Content f | + nodeCand1Store(f, node, toReturn, config) and + readCand1(f, config) + ) + or + // read + exists(Node mid, Content f | + readDirect(node, f, mid) and + storeCandFwd1(f, unbind(config)) and + nodeCand1(mid, toReturn, config) + ) + or + // flow into a callable + exists(DataFlowCall call | + nodeCand1Arg(call, node, false, config) and + toReturn = false or - exists(Node mid | - additionalLocalFlowStep(node, mid, config) and - nodeCand1(mid, config) - ) - or - exists(Node mid | - jumpStep(node, mid, config) and - nodeCand1(mid, config) - ) - or - exists(Node mid | - additionalJumpStep(node, mid, config) and - nodeCand1(mid, config) - ) - or - // store - exists(Content f | - nodeCand1Store(f, node, config) and - readCand1(f, config) - ) - or - // read - exists(Node mid, Content f | - readDirect(node, f, mid) and - storeCandFwd1(f, unbind(config)) and - nodeCand1(mid, config) - ) - or - // flow into a callable - exists(Node param | - viableParamArg(_, param, node) and - nodeCand1(param, config) - ) - or - // flow out of a callable - exists(ReturnPosition pos | - nodeCand1ReturnPosition(pos, config) and - getReturnPosition(node) = pos - ) + nodeCand1ArgToReturn(call, node, config) and + flowInCand1(call, toReturn, config) + ) + or + // flow out of a callable + exists(ReturnPosition pos | + nodeCand1ReturnPosition(pos, config) and + getReturnPosition(node) = pos and + toReturn = true ) } -pragma[noinline] +pragma[nomagic] +private predicate nodeCand1(Node node, Configuration config) { nodeCand1(node, _, config) } + +pragma[nomagic] private predicate nodeCand1ReturnPosition(ReturnPosition pos, Configuration config) { exists(DataFlowCall call, ReturnKindExt kind, Node out | - nodeCand1(out, config) and + nodeCand1(out, _, config) and pos = viableReturnPos(call, kind) and out = kind.getAnOutNode(call) ) @@ -418,21 +477,21 @@ private predicate nodeCand1ReturnPosition(ReturnPosition pos, Configuration conf /** * Holds if `f` is the target of a read in the flow covered by `nodeCand1`. */ -pragma[noinline] +pragma[nomagic] private predicate readCand1(Content f, Configuration config) { exists(Node mid, Node node | useFieldFlow(config) and nodeCandFwd1(node, unbind(config)) and readDirect(node, f, mid) and storeCandFwd1(f, unbind(config)) and - nodeCand1(mid, config) + nodeCand1(mid, _, config) ) } pragma[nomagic] -private predicate nodeCand1Store(Content f, Node node, Configuration config) { +private predicate nodeCand1Store(Content f, Node node, boolean toReturn, Configuration config) { exists(Node mid | - nodeCand1(mid, config) and + nodeCand1(mid, toReturn, config) and storeCandFwd1(f, unbind(config)) and storeDirect(node, f, mid) ) @@ -444,11 +503,45 @@ private predicate nodeCand1Store(Content f, Node node, Configuration config) { */ private predicate readStoreCand1(Content f, Configuration conf) { readCand1(f, conf) and - nodeCand1Store(f, _, conf) + nodeCand1Store(f, _, _, conf) +} + +pragma[nomagic] +private predicate viableParamArgCandFwd1( + DataFlowCall call, ParameterNode p, ArgumentNode arg, Configuration config +) { + viableParamArg(call, p, arg) and + nodeCandFwd1(arg, config) +} + +pragma[nomagic] +private predicate nodeCand1Arg( + DataFlowCall call, ArgumentNode arg, boolean toReturn, Configuration config +) { + exists(ParameterNode p | + nodeCand1(p, toReturn, config) and + viableParamArgCandFwd1(call, p, arg, config) + ) +} + +pragma[nomagic] +private predicate nodeCand1ArgToReturn(DataFlowCall call, ArgumentNode arg, Configuration config) { + nodeCand1Arg(call, arg, true, config) +} + +/** + * Holds if an output from `call` is reached in the flow covered by `nodeCand1`. + */ +pragma[nomagic] +private predicate flowInCand1(DataFlowCall call, boolean toReturn, Configuration config) { + exists(Node out | + nodeCand1(out, toReturn, config) and + nodeCandFwd1OutFromArg(call, out, config) + ) } private predicate throughFlowNodeCand(Node node, Configuration config) { - nodeCand1(node, config) and + nodeCand1(node, true, config) and not fullBarrier(node, config) and not inBarrier(node, config) and not outBarrier(node, config) @@ -2257,13 +2350,12 @@ private class PathNodeSink extends PathNodeImpl, TPathNodeSink { * a callable is recorded by `cc`. */ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCtx sc, AccessPath ap) { - exists(LocalCallContext localCC, AccessPath ap0, Node midnode, Configuration conf | - midnode = mid.getNode() and - conf = mid.getConfiguration() and - cc = mid.getCallContext() and - sc = mid.getSummaryCtx() and - localCC = getLocalCallContext(cc, midnode.getEnclosingCallable()) and - ap0 = mid.getAp() + exists( + AccessPath ap0, Node midnode, Configuration conf, DataFlowCallable enclosing, + LocalCallContext localCC + | + pathIntoLocalStep(mid, midnode, cc, enclosing, sc, ap0, conf) and + localCC = getLocalCallContext(cc, enclosing) | localFlowBigStep(midnode, node, true, conf, localCC) and ap = ap0 @@ -2297,6 +2389,20 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt pathThroughCallable(mid, node, cc, ap) and sc = mid.getSummaryCtx() } +pragma[nomagic] +private predicate pathIntoLocalStep( + PathNodeMid mid, Node midnode, CallContext cc, DataFlowCallable enclosing, SummaryCtx sc, + AccessPath ap0, Configuration conf +) { + midnode = mid.getNode() and + cc = mid.getCallContext() and + conf = mid.getConfiguration() and + localFlowBigStep(midnode, _, _, conf, _) and + enclosing = midnode.getEnclosingCallable() and + sc = mid.getSummaryCtx() and + ap0 = mid.getAp() +} + pragma[nomagic] private predicate readCand(Node node1, Content f, Node node2, Configuration config) { readDirect(node1, f, node2) and diff --git a/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl2.qll b/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl2.qll index 86d44e4fcce..5eb98aefe4e 100644 --- a/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl2.qll +++ b/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl2.qll @@ -259,44 +259,47 @@ private ReturnPosition viableReturnPos(DataFlowCall call, ReturnKindExt kind) { /** * Holds if `node` is reachable from a source in the given configuration - * ignoring call contexts. + * taking simple call contexts into consideration. */ -private predicate nodeCandFwd1(Node node, Configuration config) { +private predicate nodeCandFwd1(Node node, boolean fromArg, Configuration config) { not fullBarrier(node, config) and ( - config.isSource(node) + config.isSource(node) and + fromArg = false or exists(Node mid | - nodeCandFwd1(mid, config) and + nodeCandFwd1(mid, fromArg, config) and localFlowStep(mid, node, config) ) or exists(Node mid | - nodeCandFwd1(mid, config) and + nodeCandFwd1(mid, fromArg, config) and additionalLocalFlowStep(mid, node, config) ) or exists(Node mid | nodeCandFwd1(mid, config) and - jumpStep(mid, node, config) + jumpStep(mid, node, config) and + fromArg = false ) or exists(Node mid | nodeCandFwd1(mid, config) and - additionalJumpStep(mid, node, config) + additionalJumpStep(mid, node, config) and + fromArg = false ) or // store exists(Node mid | useFieldFlow(config) and - nodeCandFwd1(mid, config) and + nodeCandFwd1(mid, fromArg, config) and storeDirect(mid, _, node) and not outBarrier(mid, config) ) or // read exists(Content f | - nodeCandFwd1Read(f, node, config) and + nodeCandFwd1Read(f, node, fromArg, config) and storeCandFwd1(f, config) and not inBarrier(node, config) ) @@ -304,30 +307,37 @@ private predicate nodeCandFwd1(Node node, Configuration config) { // flow into a callable exists(Node arg | nodeCandFwd1(arg, config) and - viableParamArg(_, node, arg) + viableParamArg(_, node, arg) and + fromArg = true ) or // flow out of a callable - exists(DataFlowCall call, ReturnPosition pos, ReturnKindExt kind | - nodeCandFwd1ReturnPosition(pos, config) and - pos = viableReturnPos(call, kind) and - node = kind.getAnOutNode(call) + exists(DataFlowCall call | + nodeCandFwd1Out(call, node, false, config) and + fromArg = false + or + nodeCandFwd1OutFromArg(call, node, config) and + flowOutCandFwd1(call, fromArg, config) ) ) } -pragma[noinline] -private predicate nodeCandFwd1ReturnPosition(ReturnPosition pos, Configuration config) { +private predicate nodeCandFwd1(Node node, Configuration config) { nodeCandFwd1(node, _, config) } + +pragma[nomagic] +private predicate nodeCandFwd1ReturnPosition( + ReturnPosition pos, boolean fromArg, Configuration config +) { exists(ReturnNodeExt ret | - nodeCandFwd1(ret, config) and + nodeCandFwd1(ret, fromArg, config) and getReturnPosition(ret) = pos ) } pragma[nomagic] -private predicate nodeCandFwd1Read(Content f, Node node, Configuration config) { +private predicate nodeCandFwd1Read(Content f, Node node, boolean fromArg, Configuration config) { exists(Node mid | - nodeCandFwd1(mid, config) and + nodeCandFwd1(mid, fromArg, config) and readDirect(mid, f, node) ) } @@ -335,7 +345,7 @@ private predicate nodeCandFwd1Read(Content f, Node node, Configuration config) { /** * Holds if `f` is the target of a store in the flow covered by `nodeCandFwd1`. */ -pragma[noinline] +pragma[nomagic] private predicate storeCandFwd1(Content f, Configuration config) { exists(Node mid, Node node | not fullBarrier(node, config) and @@ -345,71 +355,120 @@ private predicate storeCandFwd1(Content f, Configuration config) { ) } +pragma[nomagic] +private predicate nodeCandFwd1ReturnKind( + DataFlowCall call, ReturnKindExt kind, boolean fromArg, Configuration config +) { + exists(ReturnPosition pos | + nodeCandFwd1ReturnPosition(pos, fromArg, config) and + pos = viableReturnPos(call, kind) + ) +} + +pragma[nomagic] +private predicate nodeCandFwd1Out( + DataFlowCall call, Node node, boolean fromArg, Configuration config +) { + exists(ReturnKindExt kind | + nodeCandFwd1ReturnKind(call, kind, fromArg, config) and + node = kind.getAnOutNode(call) + ) +} + +pragma[nomagic] +private predicate nodeCandFwd1OutFromArg(DataFlowCall call, Node node, Configuration config) { + nodeCandFwd1Out(call, node, true, config) +} + +/** + * Holds if an argument to `call` is reached in the flow covered by `nodeCandFwd1`. + */ +pragma[nomagic] +private predicate flowOutCandFwd1(DataFlowCall call, boolean fromArg, Configuration config) { + exists(ArgumentNode arg | + nodeCandFwd1(arg, fromArg, config) and + viableParamArg(call, _, arg) + ) +} + bindingset[result, b] private boolean unbindBool(boolean b) { result != b.booleanNot() } /** * Holds if `node` is part of a path from a source to a sink in the given - * configuration ignoring call contexts. + * configuration taking simple call contexts into consideration. */ pragma[nomagic] -private predicate nodeCand1(Node node, Configuration config) { +private predicate nodeCand1(Node node, boolean toReturn, Configuration config) { + nodeCand1_0(node, toReturn, config) and + nodeCandFwd1(node, config) +} + +pragma[nomagic] +private predicate nodeCand1_0(Node node, boolean toReturn, Configuration config) { nodeCandFwd1(node, config) and - config.isSink(node) + config.isSink(node) and + toReturn = false or - nodeCandFwd1(node, unbind(config)) and - ( - exists(Node mid | - localFlowStep(node, mid, config) and - nodeCand1(mid, config) - ) + exists(Node mid | + localFlowStep(node, mid, config) and + nodeCand1(mid, toReturn, config) + ) + or + exists(Node mid | + additionalLocalFlowStep(node, mid, config) and + nodeCand1(mid, toReturn, config) + ) + or + exists(Node mid | + jumpStep(node, mid, config) and + nodeCand1(mid, _, config) and + toReturn = false + ) + or + exists(Node mid | + additionalJumpStep(node, mid, config) and + nodeCand1(mid, _, config) and + toReturn = false + ) + or + // store + exists(Content f | + nodeCand1Store(f, node, toReturn, config) and + readCand1(f, config) + ) + or + // read + exists(Node mid, Content f | + readDirect(node, f, mid) and + storeCandFwd1(f, unbind(config)) and + nodeCand1(mid, toReturn, config) + ) + or + // flow into a callable + exists(DataFlowCall call | + nodeCand1Arg(call, node, false, config) and + toReturn = false or - exists(Node mid | - additionalLocalFlowStep(node, mid, config) and - nodeCand1(mid, config) - ) - or - exists(Node mid | - jumpStep(node, mid, config) and - nodeCand1(mid, config) - ) - or - exists(Node mid | - additionalJumpStep(node, mid, config) and - nodeCand1(mid, config) - ) - or - // store - exists(Content f | - nodeCand1Store(f, node, config) and - readCand1(f, config) - ) - or - // read - exists(Node mid, Content f | - readDirect(node, f, mid) and - storeCandFwd1(f, unbind(config)) and - nodeCand1(mid, config) - ) - or - // flow into a callable - exists(Node param | - viableParamArg(_, param, node) and - nodeCand1(param, config) - ) - or - // flow out of a callable - exists(ReturnPosition pos | - nodeCand1ReturnPosition(pos, config) and - getReturnPosition(node) = pos - ) + nodeCand1ArgToReturn(call, node, config) and + flowInCand1(call, toReturn, config) + ) + or + // flow out of a callable + exists(ReturnPosition pos | + nodeCand1ReturnPosition(pos, config) and + getReturnPosition(node) = pos and + toReturn = true ) } -pragma[noinline] +pragma[nomagic] +private predicate nodeCand1(Node node, Configuration config) { nodeCand1(node, _, config) } + +pragma[nomagic] private predicate nodeCand1ReturnPosition(ReturnPosition pos, Configuration config) { exists(DataFlowCall call, ReturnKindExt kind, Node out | - nodeCand1(out, config) and + nodeCand1(out, _, config) and pos = viableReturnPos(call, kind) and out = kind.getAnOutNode(call) ) @@ -418,21 +477,21 @@ private predicate nodeCand1ReturnPosition(ReturnPosition pos, Configuration conf /** * Holds if `f` is the target of a read in the flow covered by `nodeCand1`. */ -pragma[noinline] +pragma[nomagic] private predicate readCand1(Content f, Configuration config) { exists(Node mid, Node node | useFieldFlow(config) and nodeCandFwd1(node, unbind(config)) and readDirect(node, f, mid) and storeCandFwd1(f, unbind(config)) and - nodeCand1(mid, config) + nodeCand1(mid, _, config) ) } pragma[nomagic] -private predicate nodeCand1Store(Content f, Node node, Configuration config) { +private predicate nodeCand1Store(Content f, Node node, boolean toReturn, Configuration config) { exists(Node mid | - nodeCand1(mid, config) and + nodeCand1(mid, toReturn, config) and storeCandFwd1(f, unbind(config)) and storeDirect(node, f, mid) ) @@ -444,11 +503,45 @@ private predicate nodeCand1Store(Content f, Node node, Configuration config) { */ private predicate readStoreCand1(Content f, Configuration conf) { readCand1(f, conf) and - nodeCand1Store(f, _, conf) + nodeCand1Store(f, _, _, conf) +} + +pragma[nomagic] +private predicate viableParamArgCandFwd1( + DataFlowCall call, ParameterNode p, ArgumentNode arg, Configuration config +) { + viableParamArg(call, p, arg) and + nodeCandFwd1(arg, config) +} + +pragma[nomagic] +private predicate nodeCand1Arg( + DataFlowCall call, ArgumentNode arg, boolean toReturn, Configuration config +) { + exists(ParameterNode p | + nodeCand1(p, toReturn, config) and + viableParamArgCandFwd1(call, p, arg, config) + ) +} + +pragma[nomagic] +private predicate nodeCand1ArgToReturn(DataFlowCall call, ArgumentNode arg, Configuration config) { + nodeCand1Arg(call, arg, true, config) +} + +/** + * Holds if an output from `call` is reached in the flow covered by `nodeCand1`. + */ +pragma[nomagic] +private predicate flowInCand1(DataFlowCall call, boolean toReturn, Configuration config) { + exists(Node out | + nodeCand1(out, toReturn, config) and + nodeCandFwd1OutFromArg(call, out, config) + ) } private predicate throughFlowNodeCand(Node node, Configuration config) { - nodeCand1(node, config) and + nodeCand1(node, true, config) and not fullBarrier(node, config) and not inBarrier(node, config) and not outBarrier(node, config) @@ -2257,13 +2350,12 @@ private class PathNodeSink extends PathNodeImpl, TPathNodeSink { * a callable is recorded by `cc`. */ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCtx sc, AccessPath ap) { - exists(LocalCallContext localCC, AccessPath ap0, Node midnode, Configuration conf | - midnode = mid.getNode() and - conf = mid.getConfiguration() and - cc = mid.getCallContext() and - sc = mid.getSummaryCtx() and - localCC = getLocalCallContext(cc, midnode.getEnclosingCallable()) and - ap0 = mid.getAp() + exists( + AccessPath ap0, Node midnode, Configuration conf, DataFlowCallable enclosing, + LocalCallContext localCC + | + pathIntoLocalStep(mid, midnode, cc, enclosing, sc, ap0, conf) and + localCC = getLocalCallContext(cc, enclosing) | localFlowBigStep(midnode, node, true, conf, localCC) and ap = ap0 @@ -2297,6 +2389,20 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt pathThroughCallable(mid, node, cc, ap) and sc = mid.getSummaryCtx() } +pragma[nomagic] +private predicate pathIntoLocalStep( + PathNodeMid mid, Node midnode, CallContext cc, DataFlowCallable enclosing, SummaryCtx sc, + AccessPath ap0, Configuration conf +) { + midnode = mid.getNode() and + cc = mid.getCallContext() and + conf = mid.getConfiguration() and + localFlowBigStep(midnode, _, _, conf, _) and + enclosing = midnode.getEnclosingCallable() and + sc = mid.getSummaryCtx() and + ap0 = mid.getAp() +} + pragma[nomagic] private predicate readCand(Node node1, Content f, Node node2, Configuration config) { readDirect(node1, f, node2) and diff --git a/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl3.qll b/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl3.qll index 86d44e4fcce..5eb98aefe4e 100644 --- a/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl3.qll +++ b/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl3.qll @@ -259,44 +259,47 @@ private ReturnPosition viableReturnPos(DataFlowCall call, ReturnKindExt kind) { /** * Holds if `node` is reachable from a source in the given configuration - * ignoring call contexts. + * taking simple call contexts into consideration. */ -private predicate nodeCandFwd1(Node node, Configuration config) { +private predicate nodeCandFwd1(Node node, boolean fromArg, Configuration config) { not fullBarrier(node, config) and ( - config.isSource(node) + config.isSource(node) and + fromArg = false or exists(Node mid | - nodeCandFwd1(mid, config) and + nodeCandFwd1(mid, fromArg, config) and localFlowStep(mid, node, config) ) or exists(Node mid | - nodeCandFwd1(mid, config) and + nodeCandFwd1(mid, fromArg, config) and additionalLocalFlowStep(mid, node, config) ) or exists(Node mid | nodeCandFwd1(mid, config) and - jumpStep(mid, node, config) + jumpStep(mid, node, config) and + fromArg = false ) or exists(Node mid | nodeCandFwd1(mid, config) and - additionalJumpStep(mid, node, config) + additionalJumpStep(mid, node, config) and + fromArg = false ) or // store exists(Node mid | useFieldFlow(config) and - nodeCandFwd1(mid, config) and + nodeCandFwd1(mid, fromArg, config) and storeDirect(mid, _, node) and not outBarrier(mid, config) ) or // read exists(Content f | - nodeCandFwd1Read(f, node, config) and + nodeCandFwd1Read(f, node, fromArg, config) and storeCandFwd1(f, config) and not inBarrier(node, config) ) @@ -304,30 +307,37 @@ private predicate nodeCandFwd1(Node node, Configuration config) { // flow into a callable exists(Node arg | nodeCandFwd1(arg, config) and - viableParamArg(_, node, arg) + viableParamArg(_, node, arg) and + fromArg = true ) or // flow out of a callable - exists(DataFlowCall call, ReturnPosition pos, ReturnKindExt kind | - nodeCandFwd1ReturnPosition(pos, config) and - pos = viableReturnPos(call, kind) and - node = kind.getAnOutNode(call) + exists(DataFlowCall call | + nodeCandFwd1Out(call, node, false, config) and + fromArg = false + or + nodeCandFwd1OutFromArg(call, node, config) and + flowOutCandFwd1(call, fromArg, config) ) ) } -pragma[noinline] -private predicate nodeCandFwd1ReturnPosition(ReturnPosition pos, Configuration config) { +private predicate nodeCandFwd1(Node node, Configuration config) { nodeCandFwd1(node, _, config) } + +pragma[nomagic] +private predicate nodeCandFwd1ReturnPosition( + ReturnPosition pos, boolean fromArg, Configuration config +) { exists(ReturnNodeExt ret | - nodeCandFwd1(ret, config) and + nodeCandFwd1(ret, fromArg, config) and getReturnPosition(ret) = pos ) } pragma[nomagic] -private predicate nodeCandFwd1Read(Content f, Node node, Configuration config) { +private predicate nodeCandFwd1Read(Content f, Node node, boolean fromArg, Configuration config) { exists(Node mid | - nodeCandFwd1(mid, config) and + nodeCandFwd1(mid, fromArg, config) and readDirect(mid, f, node) ) } @@ -335,7 +345,7 @@ private predicate nodeCandFwd1Read(Content f, Node node, Configuration config) { /** * Holds if `f` is the target of a store in the flow covered by `nodeCandFwd1`. */ -pragma[noinline] +pragma[nomagic] private predicate storeCandFwd1(Content f, Configuration config) { exists(Node mid, Node node | not fullBarrier(node, config) and @@ -345,71 +355,120 @@ private predicate storeCandFwd1(Content f, Configuration config) { ) } +pragma[nomagic] +private predicate nodeCandFwd1ReturnKind( + DataFlowCall call, ReturnKindExt kind, boolean fromArg, Configuration config +) { + exists(ReturnPosition pos | + nodeCandFwd1ReturnPosition(pos, fromArg, config) and + pos = viableReturnPos(call, kind) + ) +} + +pragma[nomagic] +private predicate nodeCandFwd1Out( + DataFlowCall call, Node node, boolean fromArg, Configuration config +) { + exists(ReturnKindExt kind | + nodeCandFwd1ReturnKind(call, kind, fromArg, config) and + node = kind.getAnOutNode(call) + ) +} + +pragma[nomagic] +private predicate nodeCandFwd1OutFromArg(DataFlowCall call, Node node, Configuration config) { + nodeCandFwd1Out(call, node, true, config) +} + +/** + * Holds if an argument to `call` is reached in the flow covered by `nodeCandFwd1`. + */ +pragma[nomagic] +private predicate flowOutCandFwd1(DataFlowCall call, boolean fromArg, Configuration config) { + exists(ArgumentNode arg | + nodeCandFwd1(arg, fromArg, config) and + viableParamArg(call, _, arg) + ) +} + bindingset[result, b] private boolean unbindBool(boolean b) { result != b.booleanNot() } /** * Holds if `node` is part of a path from a source to a sink in the given - * configuration ignoring call contexts. + * configuration taking simple call contexts into consideration. */ pragma[nomagic] -private predicate nodeCand1(Node node, Configuration config) { +private predicate nodeCand1(Node node, boolean toReturn, Configuration config) { + nodeCand1_0(node, toReturn, config) and + nodeCandFwd1(node, config) +} + +pragma[nomagic] +private predicate nodeCand1_0(Node node, boolean toReturn, Configuration config) { nodeCandFwd1(node, config) and - config.isSink(node) + config.isSink(node) and + toReturn = false or - nodeCandFwd1(node, unbind(config)) and - ( - exists(Node mid | - localFlowStep(node, mid, config) and - nodeCand1(mid, config) - ) + exists(Node mid | + localFlowStep(node, mid, config) and + nodeCand1(mid, toReturn, config) + ) + or + exists(Node mid | + additionalLocalFlowStep(node, mid, config) and + nodeCand1(mid, toReturn, config) + ) + or + exists(Node mid | + jumpStep(node, mid, config) and + nodeCand1(mid, _, config) and + toReturn = false + ) + or + exists(Node mid | + additionalJumpStep(node, mid, config) and + nodeCand1(mid, _, config) and + toReturn = false + ) + or + // store + exists(Content f | + nodeCand1Store(f, node, toReturn, config) and + readCand1(f, config) + ) + or + // read + exists(Node mid, Content f | + readDirect(node, f, mid) and + storeCandFwd1(f, unbind(config)) and + nodeCand1(mid, toReturn, config) + ) + or + // flow into a callable + exists(DataFlowCall call | + nodeCand1Arg(call, node, false, config) and + toReturn = false or - exists(Node mid | - additionalLocalFlowStep(node, mid, config) and - nodeCand1(mid, config) - ) - or - exists(Node mid | - jumpStep(node, mid, config) and - nodeCand1(mid, config) - ) - or - exists(Node mid | - additionalJumpStep(node, mid, config) and - nodeCand1(mid, config) - ) - or - // store - exists(Content f | - nodeCand1Store(f, node, config) and - readCand1(f, config) - ) - or - // read - exists(Node mid, Content f | - readDirect(node, f, mid) and - storeCandFwd1(f, unbind(config)) and - nodeCand1(mid, config) - ) - or - // flow into a callable - exists(Node param | - viableParamArg(_, param, node) and - nodeCand1(param, config) - ) - or - // flow out of a callable - exists(ReturnPosition pos | - nodeCand1ReturnPosition(pos, config) and - getReturnPosition(node) = pos - ) + nodeCand1ArgToReturn(call, node, config) and + flowInCand1(call, toReturn, config) + ) + or + // flow out of a callable + exists(ReturnPosition pos | + nodeCand1ReturnPosition(pos, config) and + getReturnPosition(node) = pos and + toReturn = true ) } -pragma[noinline] +pragma[nomagic] +private predicate nodeCand1(Node node, Configuration config) { nodeCand1(node, _, config) } + +pragma[nomagic] private predicate nodeCand1ReturnPosition(ReturnPosition pos, Configuration config) { exists(DataFlowCall call, ReturnKindExt kind, Node out | - nodeCand1(out, config) and + nodeCand1(out, _, config) and pos = viableReturnPos(call, kind) and out = kind.getAnOutNode(call) ) @@ -418,21 +477,21 @@ private predicate nodeCand1ReturnPosition(ReturnPosition pos, Configuration conf /** * Holds if `f` is the target of a read in the flow covered by `nodeCand1`. */ -pragma[noinline] +pragma[nomagic] private predicate readCand1(Content f, Configuration config) { exists(Node mid, Node node | useFieldFlow(config) and nodeCandFwd1(node, unbind(config)) and readDirect(node, f, mid) and storeCandFwd1(f, unbind(config)) and - nodeCand1(mid, config) + nodeCand1(mid, _, config) ) } pragma[nomagic] -private predicate nodeCand1Store(Content f, Node node, Configuration config) { +private predicate nodeCand1Store(Content f, Node node, boolean toReturn, Configuration config) { exists(Node mid | - nodeCand1(mid, config) and + nodeCand1(mid, toReturn, config) and storeCandFwd1(f, unbind(config)) and storeDirect(node, f, mid) ) @@ -444,11 +503,45 @@ private predicate nodeCand1Store(Content f, Node node, Configuration config) { */ private predicate readStoreCand1(Content f, Configuration conf) { readCand1(f, conf) and - nodeCand1Store(f, _, conf) + nodeCand1Store(f, _, _, conf) +} + +pragma[nomagic] +private predicate viableParamArgCandFwd1( + DataFlowCall call, ParameterNode p, ArgumentNode arg, Configuration config +) { + viableParamArg(call, p, arg) and + nodeCandFwd1(arg, config) +} + +pragma[nomagic] +private predicate nodeCand1Arg( + DataFlowCall call, ArgumentNode arg, boolean toReturn, Configuration config +) { + exists(ParameterNode p | + nodeCand1(p, toReturn, config) and + viableParamArgCandFwd1(call, p, arg, config) + ) +} + +pragma[nomagic] +private predicate nodeCand1ArgToReturn(DataFlowCall call, ArgumentNode arg, Configuration config) { + nodeCand1Arg(call, arg, true, config) +} + +/** + * Holds if an output from `call` is reached in the flow covered by `nodeCand1`. + */ +pragma[nomagic] +private predicate flowInCand1(DataFlowCall call, boolean toReturn, Configuration config) { + exists(Node out | + nodeCand1(out, toReturn, config) and + nodeCandFwd1OutFromArg(call, out, config) + ) } private predicate throughFlowNodeCand(Node node, Configuration config) { - nodeCand1(node, config) and + nodeCand1(node, true, config) and not fullBarrier(node, config) and not inBarrier(node, config) and not outBarrier(node, config) @@ -2257,13 +2350,12 @@ private class PathNodeSink extends PathNodeImpl, TPathNodeSink { * a callable is recorded by `cc`. */ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCtx sc, AccessPath ap) { - exists(LocalCallContext localCC, AccessPath ap0, Node midnode, Configuration conf | - midnode = mid.getNode() and - conf = mid.getConfiguration() and - cc = mid.getCallContext() and - sc = mid.getSummaryCtx() and - localCC = getLocalCallContext(cc, midnode.getEnclosingCallable()) and - ap0 = mid.getAp() + exists( + AccessPath ap0, Node midnode, Configuration conf, DataFlowCallable enclosing, + LocalCallContext localCC + | + pathIntoLocalStep(mid, midnode, cc, enclosing, sc, ap0, conf) and + localCC = getLocalCallContext(cc, enclosing) | localFlowBigStep(midnode, node, true, conf, localCC) and ap = ap0 @@ -2297,6 +2389,20 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt pathThroughCallable(mid, node, cc, ap) and sc = mid.getSummaryCtx() } +pragma[nomagic] +private predicate pathIntoLocalStep( + PathNodeMid mid, Node midnode, CallContext cc, DataFlowCallable enclosing, SummaryCtx sc, + AccessPath ap0, Configuration conf +) { + midnode = mid.getNode() and + cc = mid.getCallContext() and + conf = mid.getConfiguration() and + localFlowBigStep(midnode, _, _, conf, _) and + enclosing = midnode.getEnclosingCallable() and + sc = mid.getSummaryCtx() and + ap0 = mid.getAp() +} + pragma[nomagic] private predicate readCand(Node node1, Content f, Node node2, Configuration config) { readDirect(node1, f, node2) and diff --git a/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl4.qll b/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl4.qll index 86d44e4fcce..5eb98aefe4e 100644 --- a/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl4.qll +++ b/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl4.qll @@ -259,44 +259,47 @@ private ReturnPosition viableReturnPos(DataFlowCall call, ReturnKindExt kind) { /** * Holds if `node` is reachable from a source in the given configuration - * ignoring call contexts. + * taking simple call contexts into consideration. */ -private predicate nodeCandFwd1(Node node, Configuration config) { +private predicate nodeCandFwd1(Node node, boolean fromArg, Configuration config) { not fullBarrier(node, config) and ( - config.isSource(node) + config.isSource(node) and + fromArg = false or exists(Node mid | - nodeCandFwd1(mid, config) and + nodeCandFwd1(mid, fromArg, config) and localFlowStep(mid, node, config) ) or exists(Node mid | - nodeCandFwd1(mid, config) and + nodeCandFwd1(mid, fromArg, config) and additionalLocalFlowStep(mid, node, config) ) or exists(Node mid | nodeCandFwd1(mid, config) and - jumpStep(mid, node, config) + jumpStep(mid, node, config) and + fromArg = false ) or exists(Node mid | nodeCandFwd1(mid, config) and - additionalJumpStep(mid, node, config) + additionalJumpStep(mid, node, config) and + fromArg = false ) or // store exists(Node mid | useFieldFlow(config) and - nodeCandFwd1(mid, config) and + nodeCandFwd1(mid, fromArg, config) and storeDirect(mid, _, node) and not outBarrier(mid, config) ) or // read exists(Content f | - nodeCandFwd1Read(f, node, config) and + nodeCandFwd1Read(f, node, fromArg, config) and storeCandFwd1(f, config) and not inBarrier(node, config) ) @@ -304,30 +307,37 @@ private predicate nodeCandFwd1(Node node, Configuration config) { // flow into a callable exists(Node arg | nodeCandFwd1(arg, config) and - viableParamArg(_, node, arg) + viableParamArg(_, node, arg) and + fromArg = true ) or // flow out of a callable - exists(DataFlowCall call, ReturnPosition pos, ReturnKindExt kind | - nodeCandFwd1ReturnPosition(pos, config) and - pos = viableReturnPos(call, kind) and - node = kind.getAnOutNode(call) + exists(DataFlowCall call | + nodeCandFwd1Out(call, node, false, config) and + fromArg = false + or + nodeCandFwd1OutFromArg(call, node, config) and + flowOutCandFwd1(call, fromArg, config) ) ) } -pragma[noinline] -private predicate nodeCandFwd1ReturnPosition(ReturnPosition pos, Configuration config) { +private predicate nodeCandFwd1(Node node, Configuration config) { nodeCandFwd1(node, _, config) } + +pragma[nomagic] +private predicate nodeCandFwd1ReturnPosition( + ReturnPosition pos, boolean fromArg, Configuration config +) { exists(ReturnNodeExt ret | - nodeCandFwd1(ret, config) and + nodeCandFwd1(ret, fromArg, config) and getReturnPosition(ret) = pos ) } pragma[nomagic] -private predicate nodeCandFwd1Read(Content f, Node node, Configuration config) { +private predicate nodeCandFwd1Read(Content f, Node node, boolean fromArg, Configuration config) { exists(Node mid | - nodeCandFwd1(mid, config) and + nodeCandFwd1(mid, fromArg, config) and readDirect(mid, f, node) ) } @@ -335,7 +345,7 @@ private predicate nodeCandFwd1Read(Content f, Node node, Configuration config) { /** * Holds if `f` is the target of a store in the flow covered by `nodeCandFwd1`. */ -pragma[noinline] +pragma[nomagic] private predicate storeCandFwd1(Content f, Configuration config) { exists(Node mid, Node node | not fullBarrier(node, config) and @@ -345,71 +355,120 @@ private predicate storeCandFwd1(Content f, Configuration config) { ) } +pragma[nomagic] +private predicate nodeCandFwd1ReturnKind( + DataFlowCall call, ReturnKindExt kind, boolean fromArg, Configuration config +) { + exists(ReturnPosition pos | + nodeCandFwd1ReturnPosition(pos, fromArg, config) and + pos = viableReturnPos(call, kind) + ) +} + +pragma[nomagic] +private predicate nodeCandFwd1Out( + DataFlowCall call, Node node, boolean fromArg, Configuration config +) { + exists(ReturnKindExt kind | + nodeCandFwd1ReturnKind(call, kind, fromArg, config) and + node = kind.getAnOutNode(call) + ) +} + +pragma[nomagic] +private predicate nodeCandFwd1OutFromArg(DataFlowCall call, Node node, Configuration config) { + nodeCandFwd1Out(call, node, true, config) +} + +/** + * Holds if an argument to `call` is reached in the flow covered by `nodeCandFwd1`. + */ +pragma[nomagic] +private predicate flowOutCandFwd1(DataFlowCall call, boolean fromArg, Configuration config) { + exists(ArgumentNode arg | + nodeCandFwd1(arg, fromArg, config) and + viableParamArg(call, _, arg) + ) +} + bindingset[result, b] private boolean unbindBool(boolean b) { result != b.booleanNot() } /** * Holds if `node` is part of a path from a source to a sink in the given - * configuration ignoring call contexts. + * configuration taking simple call contexts into consideration. */ pragma[nomagic] -private predicate nodeCand1(Node node, Configuration config) { +private predicate nodeCand1(Node node, boolean toReturn, Configuration config) { + nodeCand1_0(node, toReturn, config) and + nodeCandFwd1(node, config) +} + +pragma[nomagic] +private predicate nodeCand1_0(Node node, boolean toReturn, Configuration config) { nodeCandFwd1(node, config) and - config.isSink(node) + config.isSink(node) and + toReturn = false or - nodeCandFwd1(node, unbind(config)) and - ( - exists(Node mid | - localFlowStep(node, mid, config) and - nodeCand1(mid, config) - ) + exists(Node mid | + localFlowStep(node, mid, config) and + nodeCand1(mid, toReturn, config) + ) + or + exists(Node mid | + additionalLocalFlowStep(node, mid, config) and + nodeCand1(mid, toReturn, config) + ) + or + exists(Node mid | + jumpStep(node, mid, config) and + nodeCand1(mid, _, config) and + toReturn = false + ) + or + exists(Node mid | + additionalJumpStep(node, mid, config) and + nodeCand1(mid, _, config) and + toReturn = false + ) + or + // store + exists(Content f | + nodeCand1Store(f, node, toReturn, config) and + readCand1(f, config) + ) + or + // read + exists(Node mid, Content f | + readDirect(node, f, mid) and + storeCandFwd1(f, unbind(config)) and + nodeCand1(mid, toReturn, config) + ) + or + // flow into a callable + exists(DataFlowCall call | + nodeCand1Arg(call, node, false, config) and + toReturn = false or - exists(Node mid | - additionalLocalFlowStep(node, mid, config) and - nodeCand1(mid, config) - ) - or - exists(Node mid | - jumpStep(node, mid, config) and - nodeCand1(mid, config) - ) - or - exists(Node mid | - additionalJumpStep(node, mid, config) and - nodeCand1(mid, config) - ) - or - // store - exists(Content f | - nodeCand1Store(f, node, config) and - readCand1(f, config) - ) - or - // read - exists(Node mid, Content f | - readDirect(node, f, mid) and - storeCandFwd1(f, unbind(config)) and - nodeCand1(mid, config) - ) - or - // flow into a callable - exists(Node param | - viableParamArg(_, param, node) and - nodeCand1(param, config) - ) - or - // flow out of a callable - exists(ReturnPosition pos | - nodeCand1ReturnPosition(pos, config) and - getReturnPosition(node) = pos - ) + nodeCand1ArgToReturn(call, node, config) and + flowInCand1(call, toReturn, config) + ) + or + // flow out of a callable + exists(ReturnPosition pos | + nodeCand1ReturnPosition(pos, config) and + getReturnPosition(node) = pos and + toReturn = true ) } -pragma[noinline] +pragma[nomagic] +private predicate nodeCand1(Node node, Configuration config) { nodeCand1(node, _, config) } + +pragma[nomagic] private predicate nodeCand1ReturnPosition(ReturnPosition pos, Configuration config) { exists(DataFlowCall call, ReturnKindExt kind, Node out | - nodeCand1(out, config) and + nodeCand1(out, _, config) and pos = viableReturnPos(call, kind) and out = kind.getAnOutNode(call) ) @@ -418,21 +477,21 @@ private predicate nodeCand1ReturnPosition(ReturnPosition pos, Configuration conf /** * Holds if `f` is the target of a read in the flow covered by `nodeCand1`. */ -pragma[noinline] +pragma[nomagic] private predicate readCand1(Content f, Configuration config) { exists(Node mid, Node node | useFieldFlow(config) and nodeCandFwd1(node, unbind(config)) and readDirect(node, f, mid) and storeCandFwd1(f, unbind(config)) and - nodeCand1(mid, config) + nodeCand1(mid, _, config) ) } pragma[nomagic] -private predicate nodeCand1Store(Content f, Node node, Configuration config) { +private predicate nodeCand1Store(Content f, Node node, boolean toReturn, Configuration config) { exists(Node mid | - nodeCand1(mid, config) and + nodeCand1(mid, toReturn, config) and storeCandFwd1(f, unbind(config)) and storeDirect(node, f, mid) ) @@ -444,11 +503,45 @@ private predicate nodeCand1Store(Content f, Node node, Configuration config) { */ private predicate readStoreCand1(Content f, Configuration conf) { readCand1(f, conf) and - nodeCand1Store(f, _, conf) + nodeCand1Store(f, _, _, conf) +} + +pragma[nomagic] +private predicate viableParamArgCandFwd1( + DataFlowCall call, ParameterNode p, ArgumentNode arg, Configuration config +) { + viableParamArg(call, p, arg) and + nodeCandFwd1(arg, config) +} + +pragma[nomagic] +private predicate nodeCand1Arg( + DataFlowCall call, ArgumentNode arg, boolean toReturn, Configuration config +) { + exists(ParameterNode p | + nodeCand1(p, toReturn, config) and + viableParamArgCandFwd1(call, p, arg, config) + ) +} + +pragma[nomagic] +private predicate nodeCand1ArgToReturn(DataFlowCall call, ArgumentNode arg, Configuration config) { + nodeCand1Arg(call, arg, true, config) +} + +/** + * Holds if an output from `call` is reached in the flow covered by `nodeCand1`. + */ +pragma[nomagic] +private predicate flowInCand1(DataFlowCall call, boolean toReturn, Configuration config) { + exists(Node out | + nodeCand1(out, toReturn, config) and + nodeCandFwd1OutFromArg(call, out, config) + ) } private predicate throughFlowNodeCand(Node node, Configuration config) { - nodeCand1(node, config) and + nodeCand1(node, true, config) and not fullBarrier(node, config) and not inBarrier(node, config) and not outBarrier(node, config) @@ -2257,13 +2350,12 @@ private class PathNodeSink extends PathNodeImpl, TPathNodeSink { * a callable is recorded by `cc`. */ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCtx sc, AccessPath ap) { - exists(LocalCallContext localCC, AccessPath ap0, Node midnode, Configuration conf | - midnode = mid.getNode() and - conf = mid.getConfiguration() and - cc = mid.getCallContext() and - sc = mid.getSummaryCtx() and - localCC = getLocalCallContext(cc, midnode.getEnclosingCallable()) and - ap0 = mid.getAp() + exists( + AccessPath ap0, Node midnode, Configuration conf, DataFlowCallable enclosing, + LocalCallContext localCC + | + pathIntoLocalStep(mid, midnode, cc, enclosing, sc, ap0, conf) and + localCC = getLocalCallContext(cc, enclosing) | localFlowBigStep(midnode, node, true, conf, localCC) and ap = ap0 @@ -2297,6 +2389,20 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt pathThroughCallable(mid, node, cc, ap) and sc = mid.getSummaryCtx() } +pragma[nomagic] +private predicate pathIntoLocalStep( + PathNodeMid mid, Node midnode, CallContext cc, DataFlowCallable enclosing, SummaryCtx sc, + AccessPath ap0, Configuration conf +) { + midnode = mid.getNode() and + cc = mid.getCallContext() and + conf = mid.getConfiguration() and + localFlowBigStep(midnode, _, _, conf, _) and + enclosing = midnode.getEnclosingCallable() and + sc = mid.getSummaryCtx() and + ap0 = mid.getAp() +} + pragma[nomagic] private predicate readCand(Node node1, Content f, Node node2, Configuration config) { readDirect(node1, f, node2) and diff --git a/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl5.qll b/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl5.qll index 86d44e4fcce..5eb98aefe4e 100644 --- a/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl5.qll +++ b/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl5.qll @@ -259,44 +259,47 @@ private ReturnPosition viableReturnPos(DataFlowCall call, ReturnKindExt kind) { /** * Holds if `node` is reachable from a source in the given configuration - * ignoring call contexts. + * taking simple call contexts into consideration. */ -private predicate nodeCandFwd1(Node node, Configuration config) { +private predicate nodeCandFwd1(Node node, boolean fromArg, Configuration config) { not fullBarrier(node, config) and ( - config.isSource(node) + config.isSource(node) and + fromArg = false or exists(Node mid | - nodeCandFwd1(mid, config) and + nodeCandFwd1(mid, fromArg, config) and localFlowStep(mid, node, config) ) or exists(Node mid | - nodeCandFwd1(mid, config) and + nodeCandFwd1(mid, fromArg, config) and additionalLocalFlowStep(mid, node, config) ) or exists(Node mid | nodeCandFwd1(mid, config) and - jumpStep(mid, node, config) + jumpStep(mid, node, config) and + fromArg = false ) or exists(Node mid | nodeCandFwd1(mid, config) and - additionalJumpStep(mid, node, config) + additionalJumpStep(mid, node, config) and + fromArg = false ) or // store exists(Node mid | useFieldFlow(config) and - nodeCandFwd1(mid, config) and + nodeCandFwd1(mid, fromArg, config) and storeDirect(mid, _, node) and not outBarrier(mid, config) ) or // read exists(Content f | - nodeCandFwd1Read(f, node, config) and + nodeCandFwd1Read(f, node, fromArg, config) and storeCandFwd1(f, config) and not inBarrier(node, config) ) @@ -304,30 +307,37 @@ private predicate nodeCandFwd1(Node node, Configuration config) { // flow into a callable exists(Node arg | nodeCandFwd1(arg, config) and - viableParamArg(_, node, arg) + viableParamArg(_, node, arg) and + fromArg = true ) or // flow out of a callable - exists(DataFlowCall call, ReturnPosition pos, ReturnKindExt kind | - nodeCandFwd1ReturnPosition(pos, config) and - pos = viableReturnPos(call, kind) and - node = kind.getAnOutNode(call) + exists(DataFlowCall call | + nodeCandFwd1Out(call, node, false, config) and + fromArg = false + or + nodeCandFwd1OutFromArg(call, node, config) and + flowOutCandFwd1(call, fromArg, config) ) ) } -pragma[noinline] -private predicate nodeCandFwd1ReturnPosition(ReturnPosition pos, Configuration config) { +private predicate nodeCandFwd1(Node node, Configuration config) { nodeCandFwd1(node, _, config) } + +pragma[nomagic] +private predicate nodeCandFwd1ReturnPosition( + ReturnPosition pos, boolean fromArg, Configuration config +) { exists(ReturnNodeExt ret | - nodeCandFwd1(ret, config) and + nodeCandFwd1(ret, fromArg, config) and getReturnPosition(ret) = pos ) } pragma[nomagic] -private predicate nodeCandFwd1Read(Content f, Node node, Configuration config) { +private predicate nodeCandFwd1Read(Content f, Node node, boolean fromArg, Configuration config) { exists(Node mid | - nodeCandFwd1(mid, config) and + nodeCandFwd1(mid, fromArg, config) and readDirect(mid, f, node) ) } @@ -335,7 +345,7 @@ private predicate nodeCandFwd1Read(Content f, Node node, Configuration config) { /** * Holds if `f` is the target of a store in the flow covered by `nodeCandFwd1`. */ -pragma[noinline] +pragma[nomagic] private predicate storeCandFwd1(Content f, Configuration config) { exists(Node mid, Node node | not fullBarrier(node, config) and @@ -345,71 +355,120 @@ private predicate storeCandFwd1(Content f, Configuration config) { ) } +pragma[nomagic] +private predicate nodeCandFwd1ReturnKind( + DataFlowCall call, ReturnKindExt kind, boolean fromArg, Configuration config +) { + exists(ReturnPosition pos | + nodeCandFwd1ReturnPosition(pos, fromArg, config) and + pos = viableReturnPos(call, kind) + ) +} + +pragma[nomagic] +private predicate nodeCandFwd1Out( + DataFlowCall call, Node node, boolean fromArg, Configuration config +) { + exists(ReturnKindExt kind | + nodeCandFwd1ReturnKind(call, kind, fromArg, config) and + node = kind.getAnOutNode(call) + ) +} + +pragma[nomagic] +private predicate nodeCandFwd1OutFromArg(DataFlowCall call, Node node, Configuration config) { + nodeCandFwd1Out(call, node, true, config) +} + +/** + * Holds if an argument to `call` is reached in the flow covered by `nodeCandFwd1`. + */ +pragma[nomagic] +private predicate flowOutCandFwd1(DataFlowCall call, boolean fromArg, Configuration config) { + exists(ArgumentNode arg | + nodeCandFwd1(arg, fromArg, config) and + viableParamArg(call, _, arg) + ) +} + bindingset[result, b] private boolean unbindBool(boolean b) { result != b.booleanNot() } /** * Holds if `node` is part of a path from a source to a sink in the given - * configuration ignoring call contexts. + * configuration taking simple call contexts into consideration. */ pragma[nomagic] -private predicate nodeCand1(Node node, Configuration config) { +private predicate nodeCand1(Node node, boolean toReturn, Configuration config) { + nodeCand1_0(node, toReturn, config) and + nodeCandFwd1(node, config) +} + +pragma[nomagic] +private predicate nodeCand1_0(Node node, boolean toReturn, Configuration config) { nodeCandFwd1(node, config) and - config.isSink(node) + config.isSink(node) and + toReturn = false or - nodeCandFwd1(node, unbind(config)) and - ( - exists(Node mid | - localFlowStep(node, mid, config) and - nodeCand1(mid, config) - ) + exists(Node mid | + localFlowStep(node, mid, config) and + nodeCand1(mid, toReturn, config) + ) + or + exists(Node mid | + additionalLocalFlowStep(node, mid, config) and + nodeCand1(mid, toReturn, config) + ) + or + exists(Node mid | + jumpStep(node, mid, config) and + nodeCand1(mid, _, config) and + toReturn = false + ) + or + exists(Node mid | + additionalJumpStep(node, mid, config) and + nodeCand1(mid, _, config) and + toReturn = false + ) + or + // store + exists(Content f | + nodeCand1Store(f, node, toReturn, config) and + readCand1(f, config) + ) + or + // read + exists(Node mid, Content f | + readDirect(node, f, mid) and + storeCandFwd1(f, unbind(config)) and + nodeCand1(mid, toReturn, config) + ) + or + // flow into a callable + exists(DataFlowCall call | + nodeCand1Arg(call, node, false, config) and + toReturn = false or - exists(Node mid | - additionalLocalFlowStep(node, mid, config) and - nodeCand1(mid, config) - ) - or - exists(Node mid | - jumpStep(node, mid, config) and - nodeCand1(mid, config) - ) - or - exists(Node mid | - additionalJumpStep(node, mid, config) and - nodeCand1(mid, config) - ) - or - // store - exists(Content f | - nodeCand1Store(f, node, config) and - readCand1(f, config) - ) - or - // read - exists(Node mid, Content f | - readDirect(node, f, mid) and - storeCandFwd1(f, unbind(config)) and - nodeCand1(mid, config) - ) - or - // flow into a callable - exists(Node param | - viableParamArg(_, param, node) and - nodeCand1(param, config) - ) - or - // flow out of a callable - exists(ReturnPosition pos | - nodeCand1ReturnPosition(pos, config) and - getReturnPosition(node) = pos - ) + nodeCand1ArgToReturn(call, node, config) and + flowInCand1(call, toReturn, config) + ) + or + // flow out of a callable + exists(ReturnPosition pos | + nodeCand1ReturnPosition(pos, config) and + getReturnPosition(node) = pos and + toReturn = true ) } -pragma[noinline] +pragma[nomagic] +private predicate nodeCand1(Node node, Configuration config) { nodeCand1(node, _, config) } + +pragma[nomagic] private predicate nodeCand1ReturnPosition(ReturnPosition pos, Configuration config) { exists(DataFlowCall call, ReturnKindExt kind, Node out | - nodeCand1(out, config) and + nodeCand1(out, _, config) and pos = viableReturnPos(call, kind) and out = kind.getAnOutNode(call) ) @@ -418,21 +477,21 @@ private predicate nodeCand1ReturnPosition(ReturnPosition pos, Configuration conf /** * Holds if `f` is the target of a read in the flow covered by `nodeCand1`. */ -pragma[noinline] +pragma[nomagic] private predicate readCand1(Content f, Configuration config) { exists(Node mid, Node node | useFieldFlow(config) and nodeCandFwd1(node, unbind(config)) and readDirect(node, f, mid) and storeCandFwd1(f, unbind(config)) and - nodeCand1(mid, config) + nodeCand1(mid, _, config) ) } pragma[nomagic] -private predicate nodeCand1Store(Content f, Node node, Configuration config) { +private predicate nodeCand1Store(Content f, Node node, boolean toReturn, Configuration config) { exists(Node mid | - nodeCand1(mid, config) and + nodeCand1(mid, toReturn, config) and storeCandFwd1(f, unbind(config)) and storeDirect(node, f, mid) ) @@ -444,11 +503,45 @@ private predicate nodeCand1Store(Content f, Node node, Configuration config) { */ private predicate readStoreCand1(Content f, Configuration conf) { readCand1(f, conf) and - nodeCand1Store(f, _, conf) + nodeCand1Store(f, _, _, conf) +} + +pragma[nomagic] +private predicate viableParamArgCandFwd1( + DataFlowCall call, ParameterNode p, ArgumentNode arg, Configuration config +) { + viableParamArg(call, p, arg) and + nodeCandFwd1(arg, config) +} + +pragma[nomagic] +private predicate nodeCand1Arg( + DataFlowCall call, ArgumentNode arg, boolean toReturn, Configuration config +) { + exists(ParameterNode p | + nodeCand1(p, toReturn, config) and + viableParamArgCandFwd1(call, p, arg, config) + ) +} + +pragma[nomagic] +private predicate nodeCand1ArgToReturn(DataFlowCall call, ArgumentNode arg, Configuration config) { + nodeCand1Arg(call, arg, true, config) +} + +/** + * Holds if an output from `call` is reached in the flow covered by `nodeCand1`. + */ +pragma[nomagic] +private predicate flowInCand1(DataFlowCall call, boolean toReturn, Configuration config) { + exists(Node out | + nodeCand1(out, toReturn, config) and + nodeCandFwd1OutFromArg(call, out, config) + ) } private predicate throughFlowNodeCand(Node node, Configuration config) { - nodeCand1(node, config) and + nodeCand1(node, true, config) and not fullBarrier(node, config) and not inBarrier(node, config) and not outBarrier(node, config) @@ -2257,13 +2350,12 @@ private class PathNodeSink extends PathNodeImpl, TPathNodeSink { * a callable is recorded by `cc`. */ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCtx sc, AccessPath ap) { - exists(LocalCallContext localCC, AccessPath ap0, Node midnode, Configuration conf | - midnode = mid.getNode() and - conf = mid.getConfiguration() and - cc = mid.getCallContext() and - sc = mid.getSummaryCtx() and - localCC = getLocalCallContext(cc, midnode.getEnclosingCallable()) and - ap0 = mid.getAp() + exists( + AccessPath ap0, Node midnode, Configuration conf, DataFlowCallable enclosing, + LocalCallContext localCC + | + pathIntoLocalStep(mid, midnode, cc, enclosing, sc, ap0, conf) and + localCC = getLocalCallContext(cc, enclosing) | localFlowBigStep(midnode, node, true, conf, localCC) and ap = ap0 @@ -2297,6 +2389,20 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt pathThroughCallable(mid, node, cc, ap) and sc = mid.getSummaryCtx() } +pragma[nomagic] +private predicate pathIntoLocalStep( + PathNodeMid mid, Node midnode, CallContext cc, DataFlowCallable enclosing, SummaryCtx sc, + AccessPath ap0, Configuration conf +) { + midnode = mid.getNode() and + cc = mid.getCallContext() and + conf = mid.getConfiguration() and + localFlowBigStep(midnode, _, _, conf, _) and + enclosing = midnode.getEnclosingCallable() and + sc = mid.getSummaryCtx() and + ap0 = mid.getAp() +} + pragma[nomagic] private predicate readCand(Node node1, Content f, Node node2, Configuration config) { readDirect(node1, f, node2) and diff --git a/csharp/ql/src/semmle/code/csharp/ir/implementation/raw/Instruction.qll b/csharp/ql/src/semmle/code/csharp/ir/implementation/raw/Instruction.qll index 0796c5468ca..52cf46a5f0e 100644 --- a/csharp/ql/src/semmle/code/csharp/ir/implementation/raw/Instruction.qll +++ b/csharp/ql/src/semmle/code/csharp/ir/implementation/raw/Instruction.qll @@ -266,6 +266,16 @@ module InstructionSanity { funcText = Language::getIdentityString(func.getFunction()) ) } + + query predicate switchInstructionWithoutDefaultEdge( + SwitchInstruction switchInstr, string message, IRFunction func, string funcText + ) { + not exists(switchInstr.getDefaultSuccessor()) and + message = + "SwitchInstruction " + switchInstr.toString() + " without a DefaultEdge in function '$@'." and + func = switchInstr.getEnclosingIRFunction() and + funcText = Language::getIdentityString(func.getFunction()) + } } /** diff --git a/csharp/ql/src/semmle/code/csharp/ir/implementation/raw/gvn/ValueNumbering.qll b/csharp/ql/src/semmle/code/csharp/ir/implementation/raw/gvn/ValueNumbering.qll index 1575609fb2f..161f69936e9 100644 --- a/csharp/ql/src/semmle/code/csharp/ir/implementation/raw/gvn/ValueNumbering.qll +++ b/csharp/ql/src/semmle/code/csharp/ir/implementation/raw/gvn/ValueNumbering.qll @@ -27,19 +27,19 @@ class ValueNumber extends TValueNumber { final Language::Location getLocation() { if exists(Instruction i | - i = getAnInstruction() and not i.getLocation() instanceof UnknownLocation + i = getAnInstruction() and not i.getLocation() instanceof Language::UnknownLocation ) then result = min(Language::Location l | - l = getAnInstruction().getLocation() and not l instanceof UnknownLocation + l = getAnInstruction().getLocation() and not l instanceof Language::UnknownLocation | l order by l.getFile().getAbsolutePath(), l.getStartLine(), l.getStartColumn(), l.getEndLine(), l.getEndColumn() ) - else result instanceof UnknownDefaultLocation + else result instanceof Language::UnknownDefaultLocation } /** diff --git a/csharp/ql/src/semmle/code/csharp/ir/implementation/raw/gvn/internal/ValueNumberingImports.qll b/csharp/ql/src/semmle/code/csharp/ir/implementation/raw/gvn/internal/ValueNumberingImports.qll index 555cb581d37..3d200900445 100644 --- a/csharp/ql/src/semmle/code/csharp/ir/implementation/raw/gvn/internal/ValueNumberingImports.qll +++ b/csharp/ql/src/semmle/code/csharp/ir/implementation/raw/gvn/internal/ValueNumberingImports.qll @@ -1 +1,3 @@ import semmle.code.csharp.ir.internal.Overlap +import semmle.code.csharp.ir.internal.IRCSharpLanguage as Language +import semmle.code.csharp.ir.implementation.unaliased_ssa.IR diff --git a/csharp/ql/src/semmle/code/csharp/ir/implementation/raw/gvn/internal/ValueNumberingInternal.qll b/csharp/ql/src/semmle/code/csharp/ir/implementation/raw/gvn/internal/ValueNumberingInternal.qll index 9408469a867..169b0ef7ccf 100644 --- a/csharp/ql/src/semmle/code/csharp/ir/implementation/raw/gvn/internal/ValueNumberingInternal.qll +++ b/csharp/ql/src/semmle/code/csharp/ir/implementation/raw/gvn/internal/ValueNumberingInternal.qll @@ -1,17 +1,10 @@ private import ValueNumberingImports -import semmle.code.csharp.ir.internal.IRCSharpLanguage as Language -import semmle.code.csharp.ir.implementation.raw.IR -private import semmle.code.csharp.Location - -class UnknownLocation = EmptyLocation; - -class UnknownDefaultLocation = EmptyLocation; newtype TValueNumber = - TVariableAddressValueNumber(IRFunction irFunc, IRVariable var) { - variableAddressValueNumber(_, irFunc, var) + TVariableAddressValueNumber(IRFunction irFunc, Language::AST ast) { + variableAddressValueNumber(_, irFunc, ast) } or - TInitializeParameterValueNumber(IRFunction irFunc, IRVariable var) { + TInitializeParameterValueNumber(IRFunction irFunc, Language::AST var) { initializeParameterValueNumber(_, irFunc, var) } or TInitializeThisValueNumber(IRFunction irFunc) { initializeThisValueNumber(_, irFunc) } or @@ -25,19 +18,18 @@ newtype TValueNumber = fieldAddressValueNumber(_, irFunc, field, objectAddress) } or TBinaryValueNumber( - IRFunction irFunc, Opcode opcode, IRType type, TValueNumber leftOperand, - TValueNumber rightOperand + IRFunction irFunc, Opcode opcode, TValueNumber leftOperand, TValueNumber rightOperand ) { - binaryValueNumber(_, irFunc, opcode, type, leftOperand, rightOperand) + binaryValueNumber(_, irFunc, opcode, leftOperand, rightOperand) } or TPointerArithmeticValueNumber( - IRFunction irFunc, Opcode opcode, IRType type, int elementSize, TValueNumber leftOperand, + IRFunction irFunc, Opcode opcode, int elementSize, TValueNumber leftOperand, TValueNumber rightOperand ) { - pointerArithmeticValueNumber(_, irFunc, opcode, type, elementSize, leftOperand, rightOperand) + pointerArithmeticValueNumber(_, irFunc, opcode, elementSize, leftOperand, rightOperand) } or - TUnaryValueNumber(IRFunction irFunc, Opcode opcode, IRType type, TValueNumber operand) { - unaryValueNumber(_, irFunc, opcode, type, operand) + TUnaryValueNumber(IRFunction irFunc, Opcode opcode, TValueNumber operand) { + unaryValueNumber(_, irFunc, opcode, operand) } or TInheritanceConversionValueNumber( IRFunction irFunc, Opcode opcode, Language::Class baseClass, Language::Class derivedClass, @@ -45,6 +37,11 @@ newtype TValueNumber = ) { inheritanceConversionValueNumber(_, irFunc, opcode, baseClass, derivedClass, operand) } or + TLoadTotalOverlapValueNumber( + IRFunction irFunc, IRType type, TValueNumber memOperand, TValueNumber operand + ) { + loadTotalOverlapValueNumber(_, irFunc, type, memOperand, operand) + } or TUniqueValueNumber(IRFunction irFunc, Instruction instr) { uniqueValueNumber(instr, irFunc) } /** @@ -61,12 +58,18 @@ newtype TValueNumber = * The use of `p.x` on line 3 is linked to the definition of `p` on line 1 as well, but is not * congruent to that definition because `p.x` accesses only a subset of the memory defined by `p`. */ -private class CongruentCopyInstruction extends CopyInstruction { +class CongruentCopyInstruction extends CopyInstruction { CongruentCopyInstruction() { this.getSourceValueOperand().getDefinitionOverlap() instanceof MustExactlyOverlap } } +class LoadTotalOverlapInstruction extends LoadInstruction { + LoadTotalOverlapInstruction() { + this.getSourceValueOperand().getDefinitionOverlap() instanceof MustTotallyOverlap + } +} + /** * Holds if this library knows how to assign a value number to the specified instruction, other than * a `unique` value number that is never shared by multiple instructions. @@ -91,20 +94,42 @@ private predicate numberableInstruction(Instruction instr) { instr instanceof PointerArithmeticInstruction or instr instanceof CongruentCopyInstruction + or + instr instanceof LoadTotalOverlapInstruction +} + +private predicate filteredNumberableInstruction(Instruction instr) { + // count rather than strictcount to handle missing AST elements + // separate instanceof and inline casts to avoid failed casts with a count of 0 + instr instanceof VariableAddressInstruction and + count(instr.(VariableAddressInstruction).getIRVariable().getAST()) != 1 + or + instr instanceof ConstantInstruction and + count(instr.getResultIRType()) != 1 + or + instr instanceof FieldAddressInstruction and + count(instr.(FieldAddressInstruction).getField()) != 1 } private predicate variableAddressValueNumber( - VariableAddressInstruction instr, IRFunction irFunc, IRVariable var + VariableAddressInstruction instr, IRFunction irFunc, Language::AST ast ) { instr.getEnclosingIRFunction() = irFunc and - instr.getIRVariable() = var + // The underlying AST element is used as value-numbering key instead of the + // `IRVariable` to work around a problem where a variable or expression with + // multiple types gives rise to multiple `IRVariable`s. + instr.getIRVariable().getAST() = ast and + strictcount(instr.getIRVariable().getAST()) = 1 } private predicate initializeParameterValueNumber( - InitializeParameterInstruction instr, IRFunction irFunc, IRVariable var + InitializeParameterInstruction instr, IRFunction irFunc, Language::AST var ) { instr.getEnclosingIRFunction() = irFunc and - instr.getIRVariable() = var + // The underlying AST element is used as value-numbering key instead of the + // `IRVariable` to work around a problem where a variable or expression with + // multiple types gives rise to multiple `IRVariable`s. + instr.getIRVariable().getAST() = var } private predicate initializeThisValueNumber(InitializeThisInstruction instr, IRFunction irFunc) { @@ -115,6 +140,7 @@ private predicate constantValueNumber( ConstantInstruction instr, IRFunction irFunc, IRType type, string value ) { instr.getEnclosingIRFunction() = irFunc and + strictcount(instr.getResultIRType()) = 1 and instr.getResultIRType() = type and instr.getValue() = value } @@ -133,41 +159,40 @@ private predicate fieldAddressValueNumber( ) { instr.getEnclosingIRFunction() = irFunc and instr.getField() = field and + strictcount(instr.getField()) = 1 and tvalueNumber(instr.getObjectAddress()) = objectAddress } private predicate binaryValueNumber( - BinaryInstruction instr, IRFunction irFunc, Opcode opcode, IRType type, TValueNumber leftOperand, + BinaryInstruction instr, IRFunction irFunc, Opcode opcode, TValueNumber leftOperand, TValueNumber rightOperand ) { instr.getEnclosingIRFunction() = irFunc and not instr instanceof PointerArithmeticInstruction and instr.getOpcode() = opcode and - instr.getResultIRType() = type and tvalueNumber(instr.getLeft()) = leftOperand and tvalueNumber(instr.getRight()) = rightOperand } private predicate pointerArithmeticValueNumber( - PointerArithmeticInstruction instr, IRFunction irFunc, Opcode opcode, IRType type, - int elementSize, TValueNumber leftOperand, TValueNumber rightOperand + PointerArithmeticInstruction instr, IRFunction irFunc, Opcode opcode, int elementSize, + TValueNumber leftOperand, TValueNumber rightOperand ) { instr.getEnclosingIRFunction() = irFunc and instr.getOpcode() = opcode and - instr.getResultIRType() = type and instr.getElementSize() = elementSize and tvalueNumber(instr.getLeft()) = leftOperand and tvalueNumber(instr.getRight()) = rightOperand } private predicate unaryValueNumber( - UnaryInstruction instr, IRFunction irFunc, Opcode opcode, IRType type, TValueNumber operand + UnaryInstruction instr, IRFunction irFunc, Opcode opcode, TValueNumber operand ) { instr.getEnclosingIRFunction() = irFunc and not instr instanceof InheritanceConversionInstruction and not instr instanceof CopyInstruction and + not instr instanceof FieldAddressInstruction and instr.getOpcode() = opcode and - instr.getResultIRType() = type and tvalueNumber(instr.getUnary()) = operand } @@ -182,6 +207,16 @@ private predicate inheritanceConversionValueNumber( tvalueNumber(instr.getUnary()) = operand } +private predicate loadTotalOverlapValueNumber( + LoadTotalOverlapInstruction instr, IRFunction irFunc, IRType type, TValueNumber memOperand, + TValueNumber operand +) { + instr.getEnclosingIRFunction() = irFunc and + tvalueNumber(instr.getAnOperand().(MemoryOperand).getAnyDef()) = memOperand and + tvalueNumberOfOperand(instr.getAnOperand().(AddressOperand)) = operand and + instr.getResultIRType() = type +} + /** * Holds if `instr` should be assigned a unique value number because this library does not know how * to determine if two instances of that instruction are equivalent. @@ -189,7 +224,11 @@ private predicate inheritanceConversionValueNumber( private predicate uniqueValueNumber(Instruction instr, IRFunction irFunc) { instr.getEnclosingIRFunction() = irFunc and not instr.getResultIRType() instanceof IRVoidType and - not numberableInstruction(instr) + ( + not numberableInstruction(instr) + or + filteredNumberableInstruction(instr) + ) } /** @@ -205,6 +244,12 @@ TValueNumber tvalueNumber(Instruction instr) { ) } +/** + * Gets the value number assigned to the exact definition of `op`, if any. + * Returns at most one result. + */ +TValueNumber tvalueNumberOfOperand(Operand op) { result = tvalueNumber(op.getDef()) } + /** * Gets the value number assigned to `instr`, if any, unless that instruction is assigned a unique * value number. @@ -213,12 +258,12 @@ private TValueNumber nonUniqueValueNumber(Instruction instr) { exists(IRFunction irFunc | irFunc = instr.getEnclosingIRFunction() and ( - exists(IRVariable var | - variableAddressValueNumber(instr, irFunc, var) and - result = TVariableAddressValueNumber(irFunc, var) + exists(Language::AST ast | + variableAddressValueNumber(instr, irFunc, ast) and + result = TVariableAddressValueNumber(irFunc, ast) ) or - exists(IRVariable var | + exists(Language::AST var | initializeParameterValueNumber(instr, irFunc, var) and result = TInitializeParameterValueNumber(irFunc, var) ) @@ -226,7 +271,7 @@ private TValueNumber nonUniqueValueNumber(Instruction instr) { initializeThisValueNumber(instr, irFunc) and result = TInitializeThisValueNumber(irFunc) or - exists(IRType type, string value | + exists(string value, IRType type | constantValueNumber(instr, irFunc, type, value) and result = TConstantValueNumber(irFunc, type, value) ) @@ -241,14 +286,14 @@ private TValueNumber nonUniqueValueNumber(Instruction instr) { result = TFieldAddressValueNumber(irFunc, field, objectAddress) ) or - exists(Opcode opcode, IRType type, TValueNumber leftOperand, TValueNumber rightOperand | - binaryValueNumber(instr, irFunc, opcode, type, leftOperand, rightOperand) and - result = TBinaryValueNumber(irFunc, opcode, type, leftOperand, rightOperand) + exists(Opcode opcode, TValueNumber leftOperand, TValueNumber rightOperand | + binaryValueNumber(instr, irFunc, opcode, leftOperand, rightOperand) and + result = TBinaryValueNumber(irFunc, opcode, leftOperand, rightOperand) ) or - exists(Opcode opcode, IRType type, TValueNumber operand | - unaryValueNumber(instr, irFunc, opcode, type, operand) and - result = TUnaryValueNumber(irFunc, opcode, type, operand) + exists(Opcode opcode, TValueNumber operand | + unaryValueNumber(instr, irFunc, opcode, operand) and + result = TUnaryValueNumber(irFunc, opcode, operand) ) or exists( @@ -258,14 +303,15 @@ private TValueNumber nonUniqueValueNumber(Instruction instr) { result = TInheritanceConversionValueNumber(irFunc, opcode, baseClass, derivedClass, operand) ) or - exists( - Opcode opcode, IRType type, int elementSize, TValueNumber leftOperand, - TValueNumber rightOperand - | - pointerArithmeticValueNumber(instr, irFunc, opcode, type, elementSize, leftOperand, - rightOperand) and + exists(Opcode opcode, int elementSize, TValueNumber leftOperand, TValueNumber rightOperand | + pointerArithmeticValueNumber(instr, irFunc, opcode, elementSize, leftOperand, rightOperand) and result = - TPointerArithmeticValueNumber(irFunc, opcode, type, elementSize, leftOperand, rightOperand) + TPointerArithmeticValueNumber(irFunc, opcode, elementSize, leftOperand, rightOperand) + ) + or + exists(IRType type, TValueNumber memOperand, TValueNumber operand | + loadTotalOverlapValueNumber(instr, irFunc, type, memOperand, operand) and + result = TLoadTotalOverlapValueNumber(irFunc, type, memOperand, operand) ) or // The value number of a copy is just the value number of its source value. diff --git a/csharp/ql/src/semmle/code/csharp/ir/implementation/raw/internal/TranslatedStmt.qll b/csharp/ql/src/semmle/code/csharp/ir/implementation/raw/internal/TranslatedStmt.qll index 5ead54f0a8a..0ec51bd9190 100644 --- a/csharp/ql/src/semmle/code/csharp/ir/implementation/raw/internal/TranslatedStmt.qll +++ b/csharp/ql/src/semmle/code/csharp/ir/implementation/raw/internal/TranslatedStmt.qll @@ -834,6 +834,11 @@ class TranslatedSwitchStmt extends TranslatedStmt { kind = this.getCaseEdge(caseStmt) and result = getTranslatedStmt(caseStmt).getFirstInstruction() ) + or + not exists(stmt.getDefaultCase()) and + tag = SwitchBranchTag() and + kind instanceof DefaultEdge and + result = getParent().getChildSuccessor(this) } private EdgeKind getCaseEdge(CaseStmt caseStmt) { diff --git a/csharp/ql/src/semmle/code/csharp/ir/implementation/unaliased_ssa/Instruction.qll b/csharp/ql/src/semmle/code/csharp/ir/implementation/unaliased_ssa/Instruction.qll index 0796c5468ca..52cf46a5f0e 100644 --- a/csharp/ql/src/semmle/code/csharp/ir/implementation/unaliased_ssa/Instruction.qll +++ b/csharp/ql/src/semmle/code/csharp/ir/implementation/unaliased_ssa/Instruction.qll @@ -266,6 +266,16 @@ module InstructionSanity { funcText = Language::getIdentityString(func.getFunction()) ) } + + query predicate switchInstructionWithoutDefaultEdge( + SwitchInstruction switchInstr, string message, IRFunction func, string funcText + ) { + not exists(switchInstr.getDefaultSuccessor()) and + message = + "SwitchInstruction " + switchInstr.toString() + " without a DefaultEdge in function '$@'." and + func = switchInstr.getEnclosingIRFunction() and + funcText = Language::getIdentityString(func.getFunction()) + } } /** diff --git a/csharp/ql/src/semmle/code/csharp/ir/implementation/unaliased_ssa/gvn/ValueNumbering.qll b/csharp/ql/src/semmle/code/csharp/ir/implementation/unaliased_ssa/gvn/ValueNumbering.qll index 1575609fb2f..161f69936e9 100644 --- a/csharp/ql/src/semmle/code/csharp/ir/implementation/unaliased_ssa/gvn/ValueNumbering.qll +++ b/csharp/ql/src/semmle/code/csharp/ir/implementation/unaliased_ssa/gvn/ValueNumbering.qll @@ -27,19 +27,19 @@ class ValueNumber extends TValueNumber { final Language::Location getLocation() { if exists(Instruction i | - i = getAnInstruction() and not i.getLocation() instanceof UnknownLocation + i = getAnInstruction() and not i.getLocation() instanceof Language::UnknownLocation ) then result = min(Language::Location l | - l = getAnInstruction().getLocation() and not l instanceof UnknownLocation + l = getAnInstruction().getLocation() and not l instanceof Language::UnknownLocation | l order by l.getFile().getAbsolutePath(), l.getStartLine(), l.getStartColumn(), l.getEndLine(), l.getEndColumn() ) - else result instanceof UnknownDefaultLocation + else result instanceof Language::UnknownDefaultLocation } /** diff --git a/csharp/ql/src/semmle/code/csharp/ir/implementation/unaliased_ssa/gvn/internal/ValueNumberingImports.qll b/csharp/ql/src/semmle/code/csharp/ir/implementation/unaliased_ssa/gvn/internal/ValueNumberingImports.qll index 555cb581d37..3d200900445 100644 --- a/csharp/ql/src/semmle/code/csharp/ir/implementation/unaliased_ssa/gvn/internal/ValueNumberingImports.qll +++ b/csharp/ql/src/semmle/code/csharp/ir/implementation/unaliased_ssa/gvn/internal/ValueNumberingImports.qll @@ -1 +1,3 @@ import semmle.code.csharp.ir.internal.Overlap +import semmle.code.csharp.ir.internal.IRCSharpLanguage as Language +import semmle.code.csharp.ir.implementation.unaliased_ssa.IR diff --git a/csharp/ql/src/semmle/code/csharp/ir/implementation/unaliased_ssa/gvn/internal/ValueNumberingInternal.qll b/csharp/ql/src/semmle/code/csharp/ir/implementation/unaliased_ssa/gvn/internal/ValueNumberingInternal.qll index fc0177a2e2c..169b0ef7ccf 100644 --- a/csharp/ql/src/semmle/code/csharp/ir/implementation/unaliased_ssa/gvn/internal/ValueNumberingInternal.qll +++ b/csharp/ql/src/semmle/code/csharp/ir/implementation/unaliased_ssa/gvn/internal/ValueNumberingInternal.qll @@ -1,17 +1,10 @@ private import ValueNumberingImports -import semmle.code.csharp.ir.internal.IRCSharpLanguage as Language -import semmle.code.csharp.ir.implementation.unaliased_ssa.IR -private import semmle.code.csharp.Location - -class UnknownLocation = EmptyLocation; - -class UnknownDefaultLocation = EmptyLocation; newtype TValueNumber = - TVariableAddressValueNumber(IRFunction irFunc, IRVariable var) { - variableAddressValueNumber(_, irFunc, var) + TVariableAddressValueNumber(IRFunction irFunc, Language::AST ast) { + variableAddressValueNumber(_, irFunc, ast) } or - TInitializeParameterValueNumber(IRFunction irFunc, IRVariable var) { + TInitializeParameterValueNumber(IRFunction irFunc, Language::AST var) { initializeParameterValueNumber(_, irFunc, var) } or TInitializeThisValueNumber(IRFunction irFunc) { initializeThisValueNumber(_, irFunc) } or @@ -25,19 +18,18 @@ newtype TValueNumber = fieldAddressValueNumber(_, irFunc, field, objectAddress) } or TBinaryValueNumber( - IRFunction irFunc, Opcode opcode, IRType type, TValueNumber leftOperand, - TValueNumber rightOperand + IRFunction irFunc, Opcode opcode, TValueNumber leftOperand, TValueNumber rightOperand ) { - binaryValueNumber(_, irFunc, opcode, type, leftOperand, rightOperand) + binaryValueNumber(_, irFunc, opcode, leftOperand, rightOperand) } or TPointerArithmeticValueNumber( - IRFunction irFunc, Opcode opcode, IRType type, int elementSize, TValueNumber leftOperand, + IRFunction irFunc, Opcode opcode, int elementSize, TValueNumber leftOperand, TValueNumber rightOperand ) { - pointerArithmeticValueNumber(_, irFunc, opcode, type, elementSize, leftOperand, rightOperand) + pointerArithmeticValueNumber(_, irFunc, opcode, elementSize, leftOperand, rightOperand) } or - TUnaryValueNumber(IRFunction irFunc, Opcode opcode, IRType type, TValueNumber operand) { - unaryValueNumber(_, irFunc, opcode, type, operand) + TUnaryValueNumber(IRFunction irFunc, Opcode opcode, TValueNumber operand) { + unaryValueNumber(_, irFunc, opcode, operand) } or TInheritanceConversionValueNumber( IRFunction irFunc, Opcode opcode, Language::Class baseClass, Language::Class derivedClass, @@ -106,18 +98,38 @@ private predicate numberableInstruction(Instruction instr) { instr instanceof LoadTotalOverlapInstruction } +private predicate filteredNumberableInstruction(Instruction instr) { + // count rather than strictcount to handle missing AST elements + // separate instanceof and inline casts to avoid failed casts with a count of 0 + instr instanceof VariableAddressInstruction and + count(instr.(VariableAddressInstruction).getIRVariable().getAST()) != 1 + or + instr instanceof ConstantInstruction and + count(instr.getResultIRType()) != 1 + or + instr instanceof FieldAddressInstruction and + count(instr.(FieldAddressInstruction).getField()) != 1 +} + private predicate variableAddressValueNumber( - VariableAddressInstruction instr, IRFunction irFunc, IRVariable var + VariableAddressInstruction instr, IRFunction irFunc, Language::AST ast ) { instr.getEnclosingIRFunction() = irFunc and - instr.getIRVariable() = var + // The underlying AST element is used as value-numbering key instead of the + // `IRVariable` to work around a problem where a variable or expression with + // multiple types gives rise to multiple `IRVariable`s. + instr.getIRVariable().getAST() = ast and + strictcount(instr.getIRVariable().getAST()) = 1 } private predicate initializeParameterValueNumber( - InitializeParameterInstruction instr, IRFunction irFunc, IRVariable var + InitializeParameterInstruction instr, IRFunction irFunc, Language::AST var ) { instr.getEnclosingIRFunction() = irFunc and - instr.getIRVariable() = var + // The underlying AST element is used as value-numbering key instead of the + // `IRVariable` to work around a problem where a variable or expression with + // multiple types gives rise to multiple `IRVariable`s. + instr.getIRVariable().getAST() = var } private predicate initializeThisValueNumber(InitializeThisInstruction instr, IRFunction irFunc) { @@ -128,6 +140,7 @@ private predicate constantValueNumber( ConstantInstruction instr, IRFunction irFunc, IRType type, string value ) { instr.getEnclosingIRFunction() = irFunc and + strictcount(instr.getResultIRType()) = 1 and instr.getResultIRType() = type and instr.getValue() = value } @@ -146,41 +159,40 @@ private predicate fieldAddressValueNumber( ) { instr.getEnclosingIRFunction() = irFunc and instr.getField() = field and + strictcount(instr.getField()) = 1 and tvalueNumber(instr.getObjectAddress()) = objectAddress } private predicate binaryValueNumber( - BinaryInstruction instr, IRFunction irFunc, Opcode opcode, IRType type, TValueNumber leftOperand, + BinaryInstruction instr, IRFunction irFunc, Opcode opcode, TValueNumber leftOperand, TValueNumber rightOperand ) { instr.getEnclosingIRFunction() = irFunc and not instr instanceof PointerArithmeticInstruction and instr.getOpcode() = opcode and - instr.getResultIRType() = type and tvalueNumber(instr.getLeft()) = leftOperand and tvalueNumber(instr.getRight()) = rightOperand } private predicate pointerArithmeticValueNumber( - PointerArithmeticInstruction instr, IRFunction irFunc, Opcode opcode, IRType type, - int elementSize, TValueNumber leftOperand, TValueNumber rightOperand + PointerArithmeticInstruction instr, IRFunction irFunc, Opcode opcode, int elementSize, + TValueNumber leftOperand, TValueNumber rightOperand ) { instr.getEnclosingIRFunction() = irFunc and instr.getOpcode() = opcode and - instr.getResultIRType() = type and instr.getElementSize() = elementSize and tvalueNumber(instr.getLeft()) = leftOperand and tvalueNumber(instr.getRight()) = rightOperand } private predicate unaryValueNumber( - UnaryInstruction instr, IRFunction irFunc, Opcode opcode, IRType type, TValueNumber operand + UnaryInstruction instr, IRFunction irFunc, Opcode opcode, TValueNumber operand ) { instr.getEnclosingIRFunction() = irFunc and not instr instanceof InheritanceConversionInstruction and not instr instanceof CopyInstruction and + not instr instanceof FieldAddressInstruction and instr.getOpcode() = opcode and - instr.getResultIRType() = type and tvalueNumber(instr.getUnary()) = operand } @@ -200,9 +212,9 @@ private predicate loadTotalOverlapValueNumber( TValueNumber operand ) { instr.getEnclosingIRFunction() = irFunc and - instr.getResultIRType() = type and tvalueNumber(instr.getAnOperand().(MemoryOperand).getAnyDef()) = memOperand and - tvalueNumberOfOperand(instr.getAnOperand().(AddressOperand)) = operand + tvalueNumberOfOperand(instr.getAnOperand().(AddressOperand)) = operand and + instr.getResultIRType() = type } /** @@ -212,7 +224,11 @@ private predicate loadTotalOverlapValueNumber( private predicate uniqueValueNumber(Instruction instr, IRFunction irFunc) { instr.getEnclosingIRFunction() = irFunc and not instr.getResultIRType() instanceof IRVoidType and - not numberableInstruction(instr) + ( + not numberableInstruction(instr) + or + filteredNumberableInstruction(instr) + ) } /** @@ -242,12 +258,12 @@ private TValueNumber nonUniqueValueNumber(Instruction instr) { exists(IRFunction irFunc | irFunc = instr.getEnclosingIRFunction() and ( - exists(IRVariable var | - variableAddressValueNumber(instr, irFunc, var) and - result = TVariableAddressValueNumber(irFunc, var) + exists(Language::AST ast | + variableAddressValueNumber(instr, irFunc, ast) and + result = TVariableAddressValueNumber(irFunc, ast) ) or - exists(IRVariable var | + exists(Language::AST var | initializeParameterValueNumber(instr, irFunc, var) and result = TInitializeParameterValueNumber(irFunc, var) ) @@ -255,7 +271,7 @@ private TValueNumber nonUniqueValueNumber(Instruction instr) { initializeThisValueNumber(instr, irFunc) and result = TInitializeThisValueNumber(irFunc) or - exists(IRType type, string value | + exists(string value, IRType type | constantValueNumber(instr, irFunc, type, value) and result = TConstantValueNumber(irFunc, type, value) ) @@ -270,14 +286,14 @@ private TValueNumber nonUniqueValueNumber(Instruction instr) { result = TFieldAddressValueNumber(irFunc, field, objectAddress) ) or - exists(Opcode opcode, IRType type, TValueNumber leftOperand, TValueNumber rightOperand | - binaryValueNumber(instr, irFunc, opcode, type, leftOperand, rightOperand) and - result = TBinaryValueNumber(irFunc, opcode, type, leftOperand, rightOperand) + exists(Opcode opcode, TValueNumber leftOperand, TValueNumber rightOperand | + binaryValueNumber(instr, irFunc, opcode, leftOperand, rightOperand) and + result = TBinaryValueNumber(irFunc, opcode, leftOperand, rightOperand) ) or - exists(Opcode opcode, IRType type, TValueNumber operand | - unaryValueNumber(instr, irFunc, opcode, type, operand) and - result = TUnaryValueNumber(irFunc, opcode, type, operand) + exists(Opcode opcode, TValueNumber operand | + unaryValueNumber(instr, irFunc, opcode, operand) and + result = TUnaryValueNumber(irFunc, opcode, operand) ) or exists( @@ -287,14 +303,10 @@ private TValueNumber nonUniqueValueNumber(Instruction instr) { result = TInheritanceConversionValueNumber(irFunc, opcode, baseClass, derivedClass, operand) ) or - exists( - Opcode opcode, IRType type, int elementSize, TValueNumber leftOperand, - TValueNumber rightOperand - | - pointerArithmeticValueNumber(instr, irFunc, opcode, type, elementSize, leftOperand, - rightOperand) and + exists(Opcode opcode, int elementSize, TValueNumber leftOperand, TValueNumber rightOperand | + pointerArithmeticValueNumber(instr, irFunc, opcode, elementSize, leftOperand, rightOperand) and result = - TPointerArithmeticValueNumber(irFunc, opcode, type, elementSize, leftOperand, rightOperand) + TPointerArithmeticValueNumber(irFunc, opcode, elementSize, leftOperand, rightOperand) ) or exists(IRType type, TValueNumber memOperand, TValueNumber operand | 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 cd4e96a25a3..a8fb448f8d0 100644 --- a/csharp/ql/src/semmle/code/csharp/ir/internal/IRCSharpLanguage.qll +++ b/csharp/ql/src/semmle/code/csharp/ir/internal/IRCSharpLanguage.qll @@ -10,6 +10,10 @@ class Function = CSharp::Callable; class Location = CSharp::Location; +class UnknownLocation = CSharp::EmptyLocation; + +class UnknownDefaultLocation = CSharp::EmptyLocation; + class File = CSharp::File; class AST = CSharp::Element; diff --git a/csharp/ql/test/library-tests/ir/ir/raw_ir_sanity.expected b/csharp/ql/test/library-tests/ir/ir/raw_ir_sanity.expected index 9f132fa6f28..4c0b8578a17 100644 --- a/csharp/ql/test/library-tests/ir/ir/raw_ir_sanity.expected +++ b/csharp/ql/test/library-tests/ir/ir/raw_ir_sanity.expected @@ -15,6 +15,7 @@ containsLoopOfForwardEdges lostReachability backEdgeCountMismatch useNotDominatedByDefinition +switchInstructionWithoutDefaultEdge missingCanonicalLanguageType multipleCanonicalLanguageTypes missingIRType diff --git a/csharp/ql/test/library-tests/ir/ir/unaliased_ssa_sanity.expected b/csharp/ql/test/library-tests/ir/ir/unaliased_ssa_sanity.expected index 9f132fa6f28..4c0b8578a17 100644 --- a/csharp/ql/test/library-tests/ir/ir/unaliased_ssa_sanity.expected +++ b/csharp/ql/test/library-tests/ir/ir/unaliased_ssa_sanity.expected @@ -15,6 +15,7 @@ containsLoopOfForwardEdges lostReachability backEdgeCountMismatch useNotDominatedByDefinition +switchInstructionWithoutDefaultEdge missingCanonicalLanguageType multipleCanonicalLanguageTypes missingIRType diff --git a/docs/language/global-sphinx-files/_templates/layout.html b/docs/language/global-sphinx-files/_templates/layout.html index 7faadbefbe8..f3e19f3ffac 100644 --- a/docs/language/global-sphinx-files/_templates/layout.html +++ b/docs/language/global-sphinx-files/_templates/layout.html @@ -66,7 +66,6 @@ Tools Queries Reference - Blog help.semmle.com
diff --git a/docs/language/learn-ql/advanced/advanced-ql.rst b/docs/language/learn-ql/advanced/advanced-ql.rst deleted file mode 100644 index 051d649cf42..00000000000 --- a/docs/language/learn-ql/advanced/advanced-ql.rst +++ /dev/null @@ -1,15 +0,0 @@ -Advanced QL -=========== - -.. toctree:: - :glob: - :hidden: - - ./* - - -Topics on advanced uses of QL. These topics assume that you are familiar with QL and the basics of query writing. - -- :doc:`Choosing appropriate ways to constrain types ` -- :doc:`Determining the most specific types of a variable ` -- :doc:`Monotonic aggregates in QL ` diff --git a/docs/language/learn-ql/advanced/constraining-types.rst b/docs/language/learn-ql/advanced/constraining-types.rst deleted file mode 100644 index 3a8d2e76637..00000000000 --- a/docs/language/learn-ql/advanced/constraining-types.rst +++ /dev/null @@ -1,73 +0,0 @@ -Constraining types -================== - -Type constraint methods ------------------------ - -.. pull-quote:: - - Note - - The examples below use the CodeQL library for Java. All libraries support using these methods to constrain variables, the only difference is in the names of the classes used. - -There are several ways of imposing type constraints on variables: - -- Use an ``instanceof`` test, for example: - - .. code-block:: ql - - import java - - from Type t - where t instanceof Class - select t - -- Use a cast, for example: - - .. code-block:: ql - - import java - - from Type t - where t.(Class).getASupertype().hasName("List") - select t - -- Set the variable equal to another variable with the required type using exists, for example: - - .. code-block:: ql - - import java - - from Type t - where exists(Class c | - c = t - and c.getASupertype().hasName("List") - ) - select t - -- Pass the variable to a predicate that expects a variable of the required type, for example: - - .. code-block:: ql - - import java - - predicate derivedFromList(Class c) { - c.getASupertype().hasName("List") - } - - from Type t - where derivedFromList(t) - select t - -All of these methods constrain the variable ``t`` to have values of type ``Class``. - -Choosing between the methods ----------------------------- - -These methods have advantages and disadvantages depending on the extent to which you need to work with the variable as the new type: - -- ``instanceof`` only checks that the variable has the required type. -- A cast gives you one, and only one, access to the variable as the new type. -- Creating a new variable with ``exists`` or a predicate parameter allows repeated access to the variable as the new type. - - - Note that due to the ability to overload predicates in a class, predicates that belong to a class must be supplied with arguments of the exact type they specify, and so this technique will not work in that situation. diff --git a/docs/language/learn-ql/advanced/determining-specific-types-variables.rst b/docs/language/learn-ql/advanced/determining-specific-types-variables.rst deleted file mode 100644 index 7deadde39c9..00000000000 --- a/docs/language/learn-ql/advanced/determining-specific-types-variables.rst +++ /dev/null @@ -1,28 +0,0 @@ -Determining the most specific types of a variable -================================================= - -It is sometimes useful to be able to determine what types an entity has -- especially when you are unfamiliar with the library used by a query. To help with this, there is a predicate called ``getAQlClass()``, which returns the most specific QL types of the entity that it is called on. - -This can be useful when you are not sure of the most precise class of a value. Discovering a more precise class can allow you to cast to it and use predicates that are not available on the more general class. - -Example -------- - -If you were working with a Java database, you might use ``getAQlClass()`` on every ``Expr`` in a callable called ``c``: - -**Java example** - -.. code-block:: ql - - import java - - from Expr e, Callable c - where - c.getDeclaringType().hasQualifiedName("my.namespace.name", "MyClass") - and c.getName() = "c" - and e.getEnclosingCallable() = c - select e, e.getAQlClass() - -The result of this query is a list of the most specific types of every ``Expr`` in that function. You will see multiple results for some expressions because that expression is represented by more than one type. - -For example, ``StringLiteral``\ s like ``"Hello"`` in Java belong to both the ``StringLiteral`` class (a specialization of the ``Literal`` class) and the ``CompileTimeConstantExpr`` class. So any instances of ``StringLiteral``\ s in the results will produce more than one result, one for each of the classes to which they belong. diff --git a/docs/language/learn-ql/advanced/monotonic-aggregates.rst b/docs/language/learn-ql/advanced/monotonic-aggregates.rst deleted file mode 100644 index 161776dbcf6..00000000000 --- a/docs/language/learn-ql/advanced/monotonic-aggregates.rst +++ /dev/null @@ -1,237 +0,0 @@ -Monotonic aggregates in QL -========================== - -In addition to standard aggregates, QL also supports *monotonic* aggregates. These are a slightly different way of computing aggregates which have some advantages, notably the ability to be used recursively, which normal aggregates do not have. You can enable them in a scope by adding theツ\ ``language[monotonicAggregates]`` pragma on a predicate, class, or module. - -Syntax ------- - -A QL aggregate is written in the following way: - -.. code-block:: ql - - aggregate(Type t, ... | range | expression) - -Where ``range`` is a QL formula, and ``expression`` is a QL expression. For example, you might write: - -.. code-block:: ql - - sum(Employee e | managedByMe(e) | e.getSalary()) - -to add up the salaries of the employees you manage. - -An aggregate is a QL expression. This means it may be equated to a variable or passed as an argument to a method or predicate, just like other expressions. - -The aggregate functions that are available are: - -- ``count`` - counts the expression values -- ``strictcount`` - counts the expression values, but fails if the range ever fails -- ``sum`` - sums the expression values -- ``strictsum`` - sums the expression values, but fails if the range ever fails -- ``avg`` - averages the expression values -- ``max`` - takes the maximum of the expression values -- ``min`` - takes the minimum of the expression values -- ``rank`` - ranks the variable values by their expression value - -``strictcount``, ``strictsum`` and ``rank`` are a little unusual, and are discussed in more detail `below <#aggregate-variants>`__. - -Semantics ---------- - -For most usages, aggregates have very straightforward behavior. They can be thought of according to the following recipe: - - For every combination of values for the declared **variables**, for which the **range** holds, take one value of the **expression** and apply the **aggregation function** to the resulting values. - -How does this work? Let us take the simple example given above of calculating the sum of your employees' salaries. Suppose that your employees are Alice, Ben, and Charles, whose salaries are $30k, $40k, and $50k respectively. Then to calculate ``result``, we follow our recipe: - -+----------------------------------------------------------------+-------------------------------------+ -| For every combination of values for the declared **variables** | Alice, Ben, Charles, Denis, Edna... | -+================================================================+=====================================+ -| for which the **range** holds | Alice, Ben, Charles | -+----------------------------------------------------------------+-------------------------------------+ -| take one value of theツ\ **expression** | $50k, $30k, $50k | -+----------------------------------------------------------------+-------------------------------------+ -| and apply the **aggregation function** to the resulting values | sum($50k, $30k, $50k) = $130k | -+----------------------------------------------------------------+-------------------------------------+ - -Missing expression values -~~~~~~~~~~~~~~~~~~~~~~~~~ - -Our recipe is simple enough when there is precisely one value of the expression for each value given by the range - but what happens if there is not? QL is a relational language, so this is quite possible. Consider the following QL code: - -.. code-block:: ql - - int totalProjectTime() { - result = sum(Employee e | managedByMe(e) | e.getAProject().getRunTime()) - } - -What happens when an employee does not have any projects? In this case when we follow our recipe, there will be **no** values available for the expression, and so we will fail to produce any results at all. Let us suppose that Charles has no projects, then our recipe goes as follows: - -+----------------------------------------------------------------+-------------------------------------+ -| For every combination of values for the declared **variables** | Alice, Ben, Charles, Denis, Edna... | -+================================================================+=====================================+ -| for which the **range** holds | Alice, Ben, Charles | -+----------------------------------------------------------------+-------------------------------------+ -| take one value of theツ\ **expression** | Project A, Project B, ```` | -+----------------------------------------------------------------+-------------------------------------+ -| and apply the **aggregation function** to the resulting values | ツ | -+----------------------------------------------------------------+-------------------------------------+ - -This is probably an error in defining the query. The writer probably intended something more like this: - -.. code-block:: ql - - int totalProjectTime() { - result = sum(Project p | exists(Employee e | managedByMe(e) | p = e.getAProject()) | p.getRunTime()) - } - -This will correctly ignore employees who have no projects, although it will still fail if some projects do not have a run time. - -This kind of failure can be benign, and it is crucial to the proper behavior of recursive aggregation. - -Multiple expression values -~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Not only may QL expressions have no values, they may have multiple values. Consider the following example: - -.. code-block:: ql - - int getProjectCostEstimate(Employee e) { - result = sum(Project p | e.getAProject() = p | p.getAnEstimate()) - } - -Let us suppose that ``Project.getAnEstimate`` is populated by asking a selection of people for an estimate on the project cost. In this case, the expression will return **multiple** estimates (as is common for QL methods named ``getA*``). For our example, suppose that an employee owns Project C and Project F, and the estimates for Project C are ($20k, $30k) and for Project F there is only ($40k). Then, following our recipe: - -+----------------------------------------------------------------+------------------------------------------------------+ -| For every combination of values for the declared **variables** | Project A, Project B, Project C... | -+================================================================+======================================================+ -| for which the **range** holds | Project C, Project F | -+----------------------------------------------------------------+------------------------------------------------------+ -| take one value of theツ\ **expression** | ($20k, $40K) **or** ($30k, $40k) | -+----------------------------------------------------------------+------------------------------------------------------+ -| and apply the **aggregation function** to the resulting values | sum($20k, $40k) = $60k **or** sum($30k, $40k) = $70k | -+----------------------------------------------------------------+------------------------------------------------------+ - -So in this case we actually get **two** values for ``result``. Since there were two possibilities for ``getAnEstimate`` for Project C, we got a total of 2 (for Project C) x 1 (for Project F) = 2 combinations of estimates, each of which gives a different value when aggregated. If there were more estimates for Project F, or more projects, each with their own set of estimates, then we would get more output values for the aggregate - one for each possible assignment of estimates to projects. - -This means that ``getProjectCostEstimate`` gives us a spread of options, capturing the range of different possible values we might get depending on which estimates are correct. - -Thinking about possible assignments to the variables also provides a different perspective on the previous section. An aggregation can fail to produce a value if the expression has no values for one of the aggregated entities because then there are **no** possible assignments of expression values to aggregated entities. - -The multivalued aspect of monotonic aggregates is less commonly used because monotonic aggregates with multiple expression values will often produce a large number of results, reflecting the various possible expression values. This is rarely what is intended, and can be expensive to compute (min, max, and count are exceptions to this, and have linear performance in all cases). - -If you have an unintentionally multivalued expression, this can usually be resolved by moving the multivalued part to the range and binding it to a new aggregation variable. - -Recursion -~~~~~~~~~ - -Aggregates **may** be used recursively, but the recursive call may only appear in the expression, and not in the range. For example, we might define a predicate to calculate the distance of a node in a graph from the leaves as follows: - -.. code-block:: ql - - int depth(Node n) { - if not exists(n.getAChild()) - then result = 0 - else result = 1 + max(Node child | child = n.getAChild() | depth(child)) - } - -Here the recursive call is in the expression, which is legal. - -The recursive semantics for aggregates are the same as the recursive semantics for the rest of QL. If you understand how aggregates work in the non-recursive case then you should not find it difficult to use them recursively. However, it is worth seeing how the evaluation of a recursive aggregation proceeds. - -Consider the depth example we just saw with the following graph as input (arrows point from children to parents): - -|image0| - -Then the evaluation of the ``depth`` predicate proceeds as follows: - -+-----------+--------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ -| **Stage** | **depth** | **Comments** | -+===========+============================================+==========================================================================================================================================================================+ -| 0 | ツ | We always begin with the empty set. | -+-----------+--------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ -| 1 | ``(0, b), (0, d), (0, e)`` | The nodes with no children have depth 0. The recursive step for **a** and **c** fails to produce a value, since some of their children do not have values for ``depth``. | -+-----------+--------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ -| 2 | ``(0, b), (0, d), (0, e), (1, c)`` | The recursive step for **c** succeeds, since ``depth`` now has a value for all its children (**d** and **e**). The recursive step for **a** still fails. | -+-----------+--------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ -| 3 | ``(0, b), (0, d), (0, e), (1, c), (2, a)`` | The recursive step for **a** succeeds, since ``depth`` now has a value for all its children (**b** and **c**). | -+-----------+--------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ - -Here we can see that at the intermediate stages it is very important for the aggregate to fail if some of the children lack a value - this prevents erroneous values being added. - -Aggregate variants ------------------- - -Strict aggregates -~~~~~~~~~~~~~~~~~ - -The aggregates ``strictsum`` and ``strictcount`` are known as "strict" aggregates. This means that if there are no possible assignments to the aggregation variables that satisfy the range, then the aggregate fails to produce any values, instead of defaulting to zero (which is the behavior of ``sum`` and ``count``). This is useful if you're only interested in cases where the range of the aggregate is valid. For example, the query: - -.. code-block:: ql - - from Employee e - select e, sum(Project p | e.getAProject() = p | p.costToDate()) - -produces zeros for employees who have no projects. This may just clutter up the results, whereas: - -.. code-block:: ql - - from Employee e - select e, strictsum(Project p | e.getAProject() = p | p.costToDate()) - -will only produce results for employees who actually have projects. - -Rank -~~~~ - -Rank is a slightly unusual aggregate. It takes the possible values of the expression and ranks them, returning both the value and the corresponding rank. This has some special syntax to assign the rank to a variable. For example, the query: - -.. code-block:: ql - - from int salary, int salaryRank - where salary = rank[salaryRank](Employee e | managedByMe(e) | e.getSalary()) - select salary, salaryRank - -assigns, for each person I manage, their salary to ``salary``, and the rank of their salary to ``salaryRank``. In our running example, the results would be: - -+------------+----------------+ -| ``salary`` | ``salaryRank`` | -+============+================+ -| $50k | 1 | -+------------+----------------+ -| $30k | 3 | -+------------+----------------+ - -Note that the ranking does not ignore duplicates. Since there are two employees (Alice and Charles) with salary $50k, the two $50k salaries "tie" for first place, and the $30k salary is ranked in third. - -If you wanted to rank the employees themselves by salary, you could write the following query: - -.. code-block:: ql - - from Employee employee, int salaryRank - where employee.salary() = rank[salaryRank](Employee e | managedByMe(e) | e.salary()) - select employee, salaryRank order by salaryRank desc - -Abbreviated aggregates -~~~~~~~~~~~~~~~~~~~~~~ - -As we've described them so far, aggregates have three parts: a set of variable declarations, a range, and an expression. However, often it's unnecessarily verbose to write all three, when the intention is clear from context. QL allows you to abbreviate your aggregates in a number of ways. - -+---------------------------------------------+---------------------------------------------------+--------------------------------------------------------+------------------------------------------------------------+ -| Abbreviated form | Equivalent full form | Example | Result | -+=============================================+===================================================+========================================================+============================================================+ -| ``aggregate(expression)`` | ``aggregate(Type var | expression = var | var)`` | ``avg(e.getAProject().getRunTime())`` | The average run time of the projects belonging to ``e``. | -+---------------------------------------------+---------------------------------------------------+--------------------------------------------------------+------------------------------------------------------------+ -| ``aggregate(Type var, ... | | expression)`` | ``aggregate(Type var, ... | any() | expression)`` | ``min(Employee e | | e.getSalary())`` | The lowest salary of **any** employee. | -+---------------------------------------------+---------------------------------------------------+--------------------------------------------------------+------------------------------------------------------------+ -| ``aggregate(Type var)`` | ``aggregate(Type var | any () | var)`` | ``count(Employee e)`` | The total number of employees. | -+---------------------------------------------+---------------------------------------------------+--------------------------------------------------------+------------------------------------------------------------+ -| ``aggregate(Type var | range)`` | ``aggregate(Type var | range | var)`` | ``count(Employee e | managedByMe(e))`` | The number of employees managed by you. | -+---------------------------------------------+---------------------------------------------------+--------------------------------------------------------+------------------------------------------------------------+ -| ``count(Type var, ... | range)`` | ``count(Type var, ... | range | 1)`` | ``count(Employee e1, Employee e2 | e1.worksWith(e2))`` | The number of pairs of employees who work with each other. | -+---------------------------------------------+---------------------------------------------------+--------------------------------------------------------+------------------------------------------------------------+ - -These abbreviations are valid for any aggregate, except for the last, which is only valid for ``count``. - -.. |image0| image:: ../../images/monotonic-aggregates-graph.png - diff --git a/docs/language/learn-ql/index.rst b/docs/language/learn-ql/index.rst index add850f5806..f25bf8bb644 100644 --- a/docs/language/learn-ql/index.rst +++ b/docs/language/learn-ql/index.rst @@ -73,8 +73,8 @@ For more information on using CodeQL to query code written in a specific languag javascript/ql-for-javascript python/ql-for-python -Advanced QL and technical information -************************************* +Technical information +********************* For more technical information see: @@ -82,7 +82,6 @@ For more technical information see: :maxdepth: 2 :includehidden: - advanced/advanced-ql technical-info Reference topics diff --git a/docs/language/learn-ql/writing-queries/debugging-queries.rst b/docs/language/learn-ql/writing-queries/debugging-queries.rst index 68bb1bae9b7..b0b03321354 100644 --- a/docs/language/learn-ql/writing-queries/debugging-queries.rst +++ b/docs/language/learn-ql/writing-queries/debugging-queries.rst @@ -62,6 +62,28 @@ is preferred over:: From the type context, the query optimizer deduces that some parts of the program are redundant and removes them, or *specializes* them. +Determine the most specific types of a variable +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +If you are unfamiliar with the library used in a query, you can use CodeQL to determine what types an entity has. There is a predicate called ``getAQlClass()``, which returns the most specific QL types of the entity that it is called on. + +For example, if you were working with a Java database, you might use ``getAQlClass()`` on every ``Expr`` in a callable called ``c``: + +.. code-block:: ql + + import java + + from Expr e, Callable c + where + c.getDeclaringType().hasQualifiedName("my.namespace.name", "MyClass") + and c.getName() = "c" + and e.getEnclosingCallable() = c + select e, e.getAQlClass() + +The result of this query is a list of the most specific types of every ``Expr`` in that function. You will see multiple results for expressions that are represented by more than one type, so it will likely return a very large table of results. + +Use ``getAQlClass()`` as a debugging tool, but don't include it in the final version of your query, as it slows down performance. + Avoid complex recursion ~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/java/ql/src/Security/CWE/CWE-078/ExecUnescaped.java b/java/ql/src/Security/CWE/CWE-078/ExecUnescaped.java index 1529c1fd203..ab831d15873 100644 --- a/java/ql/src/Security/CWE/CWE-078/ExecUnescaped.java +++ b/java/ql/src/Security/CWE/CWE-078/ExecUnescaped.java @@ -4,7 +4,7 @@ class Test { { String latlonCoords = args[1]; Runtime rt = Runtime.getRuntime(); - Process exec = rt.exec("cmd.exe /C latlon2utm.exe -" + latlonCoords); + Process exec = rt.exec("cmd.exe /C latlon2utm.exe " + latlonCoords); } // GOOD: use an array of arguments instead of executing a string diff --git a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll index 86d44e4fcce..5eb98aefe4e 100644 --- a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll +++ b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll @@ -259,44 +259,47 @@ private ReturnPosition viableReturnPos(DataFlowCall call, ReturnKindExt kind) { /** * Holds if `node` is reachable from a source in the given configuration - * ignoring call contexts. + * taking simple call contexts into consideration. */ -private predicate nodeCandFwd1(Node node, Configuration config) { +private predicate nodeCandFwd1(Node node, boolean fromArg, Configuration config) { not fullBarrier(node, config) and ( - config.isSource(node) + config.isSource(node) and + fromArg = false or exists(Node mid | - nodeCandFwd1(mid, config) and + nodeCandFwd1(mid, fromArg, config) and localFlowStep(mid, node, config) ) or exists(Node mid | - nodeCandFwd1(mid, config) and + nodeCandFwd1(mid, fromArg, config) and additionalLocalFlowStep(mid, node, config) ) or exists(Node mid | nodeCandFwd1(mid, config) and - jumpStep(mid, node, config) + jumpStep(mid, node, config) and + fromArg = false ) or exists(Node mid | nodeCandFwd1(mid, config) and - additionalJumpStep(mid, node, config) + additionalJumpStep(mid, node, config) and + fromArg = false ) or // store exists(Node mid | useFieldFlow(config) and - nodeCandFwd1(mid, config) and + nodeCandFwd1(mid, fromArg, config) and storeDirect(mid, _, node) and not outBarrier(mid, config) ) or // read exists(Content f | - nodeCandFwd1Read(f, node, config) and + nodeCandFwd1Read(f, node, fromArg, config) and storeCandFwd1(f, config) and not inBarrier(node, config) ) @@ -304,30 +307,37 @@ private predicate nodeCandFwd1(Node node, Configuration config) { // flow into a callable exists(Node arg | nodeCandFwd1(arg, config) and - viableParamArg(_, node, arg) + viableParamArg(_, node, arg) and + fromArg = true ) or // flow out of a callable - exists(DataFlowCall call, ReturnPosition pos, ReturnKindExt kind | - nodeCandFwd1ReturnPosition(pos, config) and - pos = viableReturnPos(call, kind) and - node = kind.getAnOutNode(call) + exists(DataFlowCall call | + nodeCandFwd1Out(call, node, false, config) and + fromArg = false + or + nodeCandFwd1OutFromArg(call, node, config) and + flowOutCandFwd1(call, fromArg, config) ) ) } -pragma[noinline] -private predicate nodeCandFwd1ReturnPosition(ReturnPosition pos, Configuration config) { +private predicate nodeCandFwd1(Node node, Configuration config) { nodeCandFwd1(node, _, config) } + +pragma[nomagic] +private predicate nodeCandFwd1ReturnPosition( + ReturnPosition pos, boolean fromArg, Configuration config +) { exists(ReturnNodeExt ret | - nodeCandFwd1(ret, config) and + nodeCandFwd1(ret, fromArg, config) and getReturnPosition(ret) = pos ) } pragma[nomagic] -private predicate nodeCandFwd1Read(Content f, Node node, Configuration config) { +private predicate nodeCandFwd1Read(Content f, Node node, boolean fromArg, Configuration config) { exists(Node mid | - nodeCandFwd1(mid, config) and + nodeCandFwd1(mid, fromArg, config) and readDirect(mid, f, node) ) } @@ -335,7 +345,7 @@ private predicate nodeCandFwd1Read(Content f, Node node, Configuration config) { /** * Holds if `f` is the target of a store in the flow covered by `nodeCandFwd1`. */ -pragma[noinline] +pragma[nomagic] private predicate storeCandFwd1(Content f, Configuration config) { exists(Node mid, Node node | not fullBarrier(node, config) and @@ -345,71 +355,120 @@ private predicate storeCandFwd1(Content f, Configuration config) { ) } +pragma[nomagic] +private predicate nodeCandFwd1ReturnKind( + DataFlowCall call, ReturnKindExt kind, boolean fromArg, Configuration config +) { + exists(ReturnPosition pos | + nodeCandFwd1ReturnPosition(pos, fromArg, config) and + pos = viableReturnPos(call, kind) + ) +} + +pragma[nomagic] +private predicate nodeCandFwd1Out( + DataFlowCall call, Node node, boolean fromArg, Configuration config +) { + exists(ReturnKindExt kind | + nodeCandFwd1ReturnKind(call, kind, fromArg, config) and + node = kind.getAnOutNode(call) + ) +} + +pragma[nomagic] +private predicate nodeCandFwd1OutFromArg(DataFlowCall call, Node node, Configuration config) { + nodeCandFwd1Out(call, node, true, config) +} + +/** + * Holds if an argument to `call` is reached in the flow covered by `nodeCandFwd1`. + */ +pragma[nomagic] +private predicate flowOutCandFwd1(DataFlowCall call, boolean fromArg, Configuration config) { + exists(ArgumentNode arg | + nodeCandFwd1(arg, fromArg, config) and + viableParamArg(call, _, arg) + ) +} + bindingset[result, b] private boolean unbindBool(boolean b) { result != b.booleanNot() } /** * Holds if `node` is part of a path from a source to a sink in the given - * configuration ignoring call contexts. + * configuration taking simple call contexts into consideration. */ pragma[nomagic] -private predicate nodeCand1(Node node, Configuration config) { +private predicate nodeCand1(Node node, boolean toReturn, Configuration config) { + nodeCand1_0(node, toReturn, config) and + nodeCandFwd1(node, config) +} + +pragma[nomagic] +private predicate nodeCand1_0(Node node, boolean toReturn, Configuration config) { nodeCandFwd1(node, config) and - config.isSink(node) + config.isSink(node) and + toReturn = false or - nodeCandFwd1(node, unbind(config)) and - ( - exists(Node mid | - localFlowStep(node, mid, config) and - nodeCand1(mid, config) - ) + exists(Node mid | + localFlowStep(node, mid, config) and + nodeCand1(mid, toReturn, config) + ) + or + exists(Node mid | + additionalLocalFlowStep(node, mid, config) and + nodeCand1(mid, toReturn, config) + ) + or + exists(Node mid | + jumpStep(node, mid, config) and + nodeCand1(mid, _, config) and + toReturn = false + ) + or + exists(Node mid | + additionalJumpStep(node, mid, config) and + nodeCand1(mid, _, config) and + toReturn = false + ) + or + // store + exists(Content f | + nodeCand1Store(f, node, toReturn, config) and + readCand1(f, config) + ) + or + // read + exists(Node mid, Content f | + readDirect(node, f, mid) and + storeCandFwd1(f, unbind(config)) and + nodeCand1(mid, toReturn, config) + ) + or + // flow into a callable + exists(DataFlowCall call | + nodeCand1Arg(call, node, false, config) and + toReturn = false or - exists(Node mid | - additionalLocalFlowStep(node, mid, config) and - nodeCand1(mid, config) - ) - or - exists(Node mid | - jumpStep(node, mid, config) and - nodeCand1(mid, config) - ) - or - exists(Node mid | - additionalJumpStep(node, mid, config) and - nodeCand1(mid, config) - ) - or - // store - exists(Content f | - nodeCand1Store(f, node, config) and - readCand1(f, config) - ) - or - // read - exists(Node mid, Content f | - readDirect(node, f, mid) and - storeCandFwd1(f, unbind(config)) and - nodeCand1(mid, config) - ) - or - // flow into a callable - exists(Node param | - viableParamArg(_, param, node) and - nodeCand1(param, config) - ) - or - // flow out of a callable - exists(ReturnPosition pos | - nodeCand1ReturnPosition(pos, config) and - getReturnPosition(node) = pos - ) + nodeCand1ArgToReturn(call, node, config) and + flowInCand1(call, toReturn, config) + ) + or + // flow out of a callable + exists(ReturnPosition pos | + nodeCand1ReturnPosition(pos, config) and + getReturnPosition(node) = pos and + toReturn = true ) } -pragma[noinline] +pragma[nomagic] +private predicate nodeCand1(Node node, Configuration config) { nodeCand1(node, _, config) } + +pragma[nomagic] private predicate nodeCand1ReturnPosition(ReturnPosition pos, Configuration config) { exists(DataFlowCall call, ReturnKindExt kind, Node out | - nodeCand1(out, config) and + nodeCand1(out, _, config) and pos = viableReturnPos(call, kind) and out = kind.getAnOutNode(call) ) @@ -418,21 +477,21 @@ private predicate nodeCand1ReturnPosition(ReturnPosition pos, Configuration conf /** * Holds if `f` is the target of a read in the flow covered by `nodeCand1`. */ -pragma[noinline] +pragma[nomagic] private predicate readCand1(Content f, Configuration config) { exists(Node mid, Node node | useFieldFlow(config) and nodeCandFwd1(node, unbind(config)) and readDirect(node, f, mid) and storeCandFwd1(f, unbind(config)) and - nodeCand1(mid, config) + nodeCand1(mid, _, config) ) } pragma[nomagic] -private predicate nodeCand1Store(Content f, Node node, Configuration config) { +private predicate nodeCand1Store(Content f, Node node, boolean toReturn, Configuration config) { exists(Node mid | - nodeCand1(mid, config) and + nodeCand1(mid, toReturn, config) and storeCandFwd1(f, unbind(config)) and storeDirect(node, f, mid) ) @@ -444,11 +503,45 @@ private predicate nodeCand1Store(Content f, Node node, Configuration config) { */ private predicate readStoreCand1(Content f, Configuration conf) { readCand1(f, conf) and - nodeCand1Store(f, _, conf) + nodeCand1Store(f, _, _, conf) +} + +pragma[nomagic] +private predicate viableParamArgCandFwd1( + DataFlowCall call, ParameterNode p, ArgumentNode arg, Configuration config +) { + viableParamArg(call, p, arg) and + nodeCandFwd1(arg, config) +} + +pragma[nomagic] +private predicate nodeCand1Arg( + DataFlowCall call, ArgumentNode arg, boolean toReturn, Configuration config +) { + exists(ParameterNode p | + nodeCand1(p, toReturn, config) and + viableParamArgCandFwd1(call, p, arg, config) + ) +} + +pragma[nomagic] +private predicate nodeCand1ArgToReturn(DataFlowCall call, ArgumentNode arg, Configuration config) { + nodeCand1Arg(call, arg, true, config) +} + +/** + * Holds if an output from `call` is reached in the flow covered by `nodeCand1`. + */ +pragma[nomagic] +private predicate flowInCand1(DataFlowCall call, boolean toReturn, Configuration config) { + exists(Node out | + nodeCand1(out, toReturn, config) and + nodeCandFwd1OutFromArg(call, out, config) + ) } private predicate throughFlowNodeCand(Node node, Configuration config) { - nodeCand1(node, config) and + nodeCand1(node, true, config) and not fullBarrier(node, config) and not inBarrier(node, config) and not outBarrier(node, config) @@ -2257,13 +2350,12 @@ private class PathNodeSink extends PathNodeImpl, TPathNodeSink { * a callable is recorded by `cc`. */ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCtx sc, AccessPath ap) { - exists(LocalCallContext localCC, AccessPath ap0, Node midnode, Configuration conf | - midnode = mid.getNode() and - conf = mid.getConfiguration() and - cc = mid.getCallContext() and - sc = mid.getSummaryCtx() and - localCC = getLocalCallContext(cc, midnode.getEnclosingCallable()) and - ap0 = mid.getAp() + exists( + AccessPath ap0, Node midnode, Configuration conf, DataFlowCallable enclosing, + LocalCallContext localCC + | + pathIntoLocalStep(mid, midnode, cc, enclosing, sc, ap0, conf) and + localCC = getLocalCallContext(cc, enclosing) | localFlowBigStep(midnode, node, true, conf, localCC) and ap = ap0 @@ -2297,6 +2389,20 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt pathThroughCallable(mid, node, cc, ap) and sc = mid.getSummaryCtx() } +pragma[nomagic] +private predicate pathIntoLocalStep( + PathNodeMid mid, Node midnode, CallContext cc, DataFlowCallable enclosing, SummaryCtx sc, + AccessPath ap0, Configuration conf +) { + midnode = mid.getNode() and + cc = mid.getCallContext() and + conf = mid.getConfiguration() and + localFlowBigStep(midnode, _, _, conf, _) and + enclosing = midnode.getEnclosingCallable() and + sc = mid.getSummaryCtx() and + ap0 = mid.getAp() +} + pragma[nomagic] private predicate readCand(Node node1, Content f, Node node2, Configuration config) { readDirect(node1, f, node2) and diff --git a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl2.qll b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl2.qll index 86d44e4fcce..5eb98aefe4e 100644 --- a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl2.qll +++ b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl2.qll @@ -259,44 +259,47 @@ private ReturnPosition viableReturnPos(DataFlowCall call, ReturnKindExt kind) { /** * Holds if `node` is reachable from a source in the given configuration - * ignoring call contexts. + * taking simple call contexts into consideration. */ -private predicate nodeCandFwd1(Node node, Configuration config) { +private predicate nodeCandFwd1(Node node, boolean fromArg, Configuration config) { not fullBarrier(node, config) and ( - config.isSource(node) + config.isSource(node) and + fromArg = false or exists(Node mid | - nodeCandFwd1(mid, config) and + nodeCandFwd1(mid, fromArg, config) and localFlowStep(mid, node, config) ) or exists(Node mid | - nodeCandFwd1(mid, config) and + nodeCandFwd1(mid, fromArg, config) and additionalLocalFlowStep(mid, node, config) ) or exists(Node mid | nodeCandFwd1(mid, config) and - jumpStep(mid, node, config) + jumpStep(mid, node, config) and + fromArg = false ) or exists(Node mid | nodeCandFwd1(mid, config) and - additionalJumpStep(mid, node, config) + additionalJumpStep(mid, node, config) and + fromArg = false ) or // store exists(Node mid | useFieldFlow(config) and - nodeCandFwd1(mid, config) and + nodeCandFwd1(mid, fromArg, config) and storeDirect(mid, _, node) and not outBarrier(mid, config) ) or // read exists(Content f | - nodeCandFwd1Read(f, node, config) and + nodeCandFwd1Read(f, node, fromArg, config) and storeCandFwd1(f, config) and not inBarrier(node, config) ) @@ -304,30 +307,37 @@ private predicate nodeCandFwd1(Node node, Configuration config) { // flow into a callable exists(Node arg | nodeCandFwd1(arg, config) and - viableParamArg(_, node, arg) + viableParamArg(_, node, arg) and + fromArg = true ) or // flow out of a callable - exists(DataFlowCall call, ReturnPosition pos, ReturnKindExt kind | - nodeCandFwd1ReturnPosition(pos, config) and - pos = viableReturnPos(call, kind) and - node = kind.getAnOutNode(call) + exists(DataFlowCall call | + nodeCandFwd1Out(call, node, false, config) and + fromArg = false + or + nodeCandFwd1OutFromArg(call, node, config) and + flowOutCandFwd1(call, fromArg, config) ) ) } -pragma[noinline] -private predicate nodeCandFwd1ReturnPosition(ReturnPosition pos, Configuration config) { +private predicate nodeCandFwd1(Node node, Configuration config) { nodeCandFwd1(node, _, config) } + +pragma[nomagic] +private predicate nodeCandFwd1ReturnPosition( + ReturnPosition pos, boolean fromArg, Configuration config +) { exists(ReturnNodeExt ret | - nodeCandFwd1(ret, config) and + nodeCandFwd1(ret, fromArg, config) and getReturnPosition(ret) = pos ) } pragma[nomagic] -private predicate nodeCandFwd1Read(Content f, Node node, Configuration config) { +private predicate nodeCandFwd1Read(Content f, Node node, boolean fromArg, Configuration config) { exists(Node mid | - nodeCandFwd1(mid, config) and + nodeCandFwd1(mid, fromArg, config) and readDirect(mid, f, node) ) } @@ -335,7 +345,7 @@ private predicate nodeCandFwd1Read(Content f, Node node, Configuration config) { /** * Holds if `f` is the target of a store in the flow covered by `nodeCandFwd1`. */ -pragma[noinline] +pragma[nomagic] private predicate storeCandFwd1(Content f, Configuration config) { exists(Node mid, Node node | not fullBarrier(node, config) and @@ -345,71 +355,120 @@ private predicate storeCandFwd1(Content f, Configuration config) { ) } +pragma[nomagic] +private predicate nodeCandFwd1ReturnKind( + DataFlowCall call, ReturnKindExt kind, boolean fromArg, Configuration config +) { + exists(ReturnPosition pos | + nodeCandFwd1ReturnPosition(pos, fromArg, config) and + pos = viableReturnPos(call, kind) + ) +} + +pragma[nomagic] +private predicate nodeCandFwd1Out( + DataFlowCall call, Node node, boolean fromArg, Configuration config +) { + exists(ReturnKindExt kind | + nodeCandFwd1ReturnKind(call, kind, fromArg, config) and + node = kind.getAnOutNode(call) + ) +} + +pragma[nomagic] +private predicate nodeCandFwd1OutFromArg(DataFlowCall call, Node node, Configuration config) { + nodeCandFwd1Out(call, node, true, config) +} + +/** + * Holds if an argument to `call` is reached in the flow covered by `nodeCandFwd1`. + */ +pragma[nomagic] +private predicate flowOutCandFwd1(DataFlowCall call, boolean fromArg, Configuration config) { + exists(ArgumentNode arg | + nodeCandFwd1(arg, fromArg, config) and + viableParamArg(call, _, arg) + ) +} + bindingset[result, b] private boolean unbindBool(boolean b) { result != b.booleanNot() } /** * Holds if `node` is part of a path from a source to a sink in the given - * configuration ignoring call contexts. + * configuration taking simple call contexts into consideration. */ pragma[nomagic] -private predicate nodeCand1(Node node, Configuration config) { +private predicate nodeCand1(Node node, boolean toReturn, Configuration config) { + nodeCand1_0(node, toReturn, config) and + nodeCandFwd1(node, config) +} + +pragma[nomagic] +private predicate nodeCand1_0(Node node, boolean toReturn, Configuration config) { nodeCandFwd1(node, config) and - config.isSink(node) + config.isSink(node) and + toReturn = false or - nodeCandFwd1(node, unbind(config)) and - ( - exists(Node mid | - localFlowStep(node, mid, config) and - nodeCand1(mid, config) - ) + exists(Node mid | + localFlowStep(node, mid, config) and + nodeCand1(mid, toReturn, config) + ) + or + exists(Node mid | + additionalLocalFlowStep(node, mid, config) and + nodeCand1(mid, toReturn, config) + ) + or + exists(Node mid | + jumpStep(node, mid, config) and + nodeCand1(mid, _, config) and + toReturn = false + ) + or + exists(Node mid | + additionalJumpStep(node, mid, config) and + nodeCand1(mid, _, config) and + toReturn = false + ) + or + // store + exists(Content f | + nodeCand1Store(f, node, toReturn, config) and + readCand1(f, config) + ) + or + // read + exists(Node mid, Content f | + readDirect(node, f, mid) and + storeCandFwd1(f, unbind(config)) and + nodeCand1(mid, toReturn, config) + ) + or + // flow into a callable + exists(DataFlowCall call | + nodeCand1Arg(call, node, false, config) and + toReturn = false or - exists(Node mid | - additionalLocalFlowStep(node, mid, config) and - nodeCand1(mid, config) - ) - or - exists(Node mid | - jumpStep(node, mid, config) and - nodeCand1(mid, config) - ) - or - exists(Node mid | - additionalJumpStep(node, mid, config) and - nodeCand1(mid, config) - ) - or - // store - exists(Content f | - nodeCand1Store(f, node, config) and - readCand1(f, config) - ) - or - // read - exists(Node mid, Content f | - readDirect(node, f, mid) and - storeCandFwd1(f, unbind(config)) and - nodeCand1(mid, config) - ) - or - // flow into a callable - exists(Node param | - viableParamArg(_, param, node) and - nodeCand1(param, config) - ) - or - // flow out of a callable - exists(ReturnPosition pos | - nodeCand1ReturnPosition(pos, config) and - getReturnPosition(node) = pos - ) + nodeCand1ArgToReturn(call, node, config) and + flowInCand1(call, toReturn, config) + ) + or + // flow out of a callable + exists(ReturnPosition pos | + nodeCand1ReturnPosition(pos, config) and + getReturnPosition(node) = pos and + toReturn = true ) } -pragma[noinline] +pragma[nomagic] +private predicate nodeCand1(Node node, Configuration config) { nodeCand1(node, _, config) } + +pragma[nomagic] private predicate nodeCand1ReturnPosition(ReturnPosition pos, Configuration config) { exists(DataFlowCall call, ReturnKindExt kind, Node out | - nodeCand1(out, config) and + nodeCand1(out, _, config) and pos = viableReturnPos(call, kind) and out = kind.getAnOutNode(call) ) @@ -418,21 +477,21 @@ private predicate nodeCand1ReturnPosition(ReturnPosition pos, Configuration conf /** * Holds if `f` is the target of a read in the flow covered by `nodeCand1`. */ -pragma[noinline] +pragma[nomagic] private predicate readCand1(Content f, Configuration config) { exists(Node mid, Node node | useFieldFlow(config) and nodeCandFwd1(node, unbind(config)) and readDirect(node, f, mid) and storeCandFwd1(f, unbind(config)) and - nodeCand1(mid, config) + nodeCand1(mid, _, config) ) } pragma[nomagic] -private predicate nodeCand1Store(Content f, Node node, Configuration config) { +private predicate nodeCand1Store(Content f, Node node, boolean toReturn, Configuration config) { exists(Node mid | - nodeCand1(mid, config) and + nodeCand1(mid, toReturn, config) and storeCandFwd1(f, unbind(config)) and storeDirect(node, f, mid) ) @@ -444,11 +503,45 @@ private predicate nodeCand1Store(Content f, Node node, Configuration config) { */ private predicate readStoreCand1(Content f, Configuration conf) { readCand1(f, conf) and - nodeCand1Store(f, _, conf) + nodeCand1Store(f, _, _, conf) +} + +pragma[nomagic] +private predicate viableParamArgCandFwd1( + DataFlowCall call, ParameterNode p, ArgumentNode arg, Configuration config +) { + viableParamArg(call, p, arg) and + nodeCandFwd1(arg, config) +} + +pragma[nomagic] +private predicate nodeCand1Arg( + DataFlowCall call, ArgumentNode arg, boolean toReturn, Configuration config +) { + exists(ParameterNode p | + nodeCand1(p, toReturn, config) and + viableParamArgCandFwd1(call, p, arg, config) + ) +} + +pragma[nomagic] +private predicate nodeCand1ArgToReturn(DataFlowCall call, ArgumentNode arg, Configuration config) { + nodeCand1Arg(call, arg, true, config) +} + +/** + * Holds if an output from `call` is reached in the flow covered by `nodeCand1`. + */ +pragma[nomagic] +private predicate flowInCand1(DataFlowCall call, boolean toReturn, Configuration config) { + exists(Node out | + nodeCand1(out, toReturn, config) and + nodeCandFwd1OutFromArg(call, out, config) + ) } private predicate throughFlowNodeCand(Node node, Configuration config) { - nodeCand1(node, config) and + nodeCand1(node, true, config) and not fullBarrier(node, config) and not inBarrier(node, config) and not outBarrier(node, config) @@ -2257,13 +2350,12 @@ private class PathNodeSink extends PathNodeImpl, TPathNodeSink { * a callable is recorded by `cc`. */ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCtx sc, AccessPath ap) { - exists(LocalCallContext localCC, AccessPath ap0, Node midnode, Configuration conf | - midnode = mid.getNode() and - conf = mid.getConfiguration() and - cc = mid.getCallContext() and - sc = mid.getSummaryCtx() and - localCC = getLocalCallContext(cc, midnode.getEnclosingCallable()) and - ap0 = mid.getAp() + exists( + AccessPath ap0, Node midnode, Configuration conf, DataFlowCallable enclosing, + LocalCallContext localCC + | + pathIntoLocalStep(mid, midnode, cc, enclosing, sc, ap0, conf) and + localCC = getLocalCallContext(cc, enclosing) | localFlowBigStep(midnode, node, true, conf, localCC) and ap = ap0 @@ -2297,6 +2389,20 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt pathThroughCallable(mid, node, cc, ap) and sc = mid.getSummaryCtx() } +pragma[nomagic] +private predicate pathIntoLocalStep( + PathNodeMid mid, Node midnode, CallContext cc, DataFlowCallable enclosing, SummaryCtx sc, + AccessPath ap0, Configuration conf +) { + midnode = mid.getNode() and + cc = mid.getCallContext() and + conf = mid.getConfiguration() and + localFlowBigStep(midnode, _, _, conf, _) and + enclosing = midnode.getEnclosingCallable() and + sc = mid.getSummaryCtx() and + ap0 = mid.getAp() +} + pragma[nomagic] private predicate readCand(Node node1, Content f, Node node2, Configuration config) { readDirect(node1, f, node2) and diff --git a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl3.qll b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl3.qll index 86d44e4fcce..5eb98aefe4e 100644 --- a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl3.qll +++ b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl3.qll @@ -259,44 +259,47 @@ private ReturnPosition viableReturnPos(DataFlowCall call, ReturnKindExt kind) { /** * Holds if `node` is reachable from a source in the given configuration - * ignoring call contexts. + * taking simple call contexts into consideration. */ -private predicate nodeCandFwd1(Node node, Configuration config) { +private predicate nodeCandFwd1(Node node, boolean fromArg, Configuration config) { not fullBarrier(node, config) and ( - config.isSource(node) + config.isSource(node) and + fromArg = false or exists(Node mid | - nodeCandFwd1(mid, config) and + nodeCandFwd1(mid, fromArg, config) and localFlowStep(mid, node, config) ) or exists(Node mid | - nodeCandFwd1(mid, config) and + nodeCandFwd1(mid, fromArg, config) and additionalLocalFlowStep(mid, node, config) ) or exists(Node mid | nodeCandFwd1(mid, config) and - jumpStep(mid, node, config) + jumpStep(mid, node, config) and + fromArg = false ) or exists(Node mid | nodeCandFwd1(mid, config) and - additionalJumpStep(mid, node, config) + additionalJumpStep(mid, node, config) and + fromArg = false ) or // store exists(Node mid | useFieldFlow(config) and - nodeCandFwd1(mid, config) and + nodeCandFwd1(mid, fromArg, config) and storeDirect(mid, _, node) and not outBarrier(mid, config) ) or // read exists(Content f | - nodeCandFwd1Read(f, node, config) and + nodeCandFwd1Read(f, node, fromArg, config) and storeCandFwd1(f, config) and not inBarrier(node, config) ) @@ -304,30 +307,37 @@ private predicate nodeCandFwd1(Node node, Configuration config) { // flow into a callable exists(Node arg | nodeCandFwd1(arg, config) and - viableParamArg(_, node, arg) + viableParamArg(_, node, arg) and + fromArg = true ) or // flow out of a callable - exists(DataFlowCall call, ReturnPosition pos, ReturnKindExt kind | - nodeCandFwd1ReturnPosition(pos, config) and - pos = viableReturnPos(call, kind) and - node = kind.getAnOutNode(call) + exists(DataFlowCall call | + nodeCandFwd1Out(call, node, false, config) and + fromArg = false + or + nodeCandFwd1OutFromArg(call, node, config) and + flowOutCandFwd1(call, fromArg, config) ) ) } -pragma[noinline] -private predicate nodeCandFwd1ReturnPosition(ReturnPosition pos, Configuration config) { +private predicate nodeCandFwd1(Node node, Configuration config) { nodeCandFwd1(node, _, config) } + +pragma[nomagic] +private predicate nodeCandFwd1ReturnPosition( + ReturnPosition pos, boolean fromArg, Configuration config +) { exists(ReturnNodeExt ret | - nodeCandFwd1(ret, config) and + nodeCandFwd1(ret, fromArg, config) and getReturnPosition(ret) = pos ) } pragma[nomagic] -private predicate nodeCandFwd1Read(Content f, Node node, Configuration config) { +private predicate nodeCandFwd1Read(Content f, Node node, boolean fromArg, Configuration config) { exists(Node mid | - nodeCandFwd1(mid, config) and + nodeCandFwd1(mid, fromArg, config) and readDirect(mid, f, node) ) } @@ -335,7 +345,7 @@ private predicate nodeCandFwd1Read(Content f, Node node, Configuration config) { /** * Holds if `f` is the target of a store in the flow covered by `nodeCandFwd1`. */ -pragma[noinline] +pragma[nomagic] private predicate storeCandFwd1(Content f, Configuration config) { exists(Node mid, Node node | not fullBarrier(node, config) and @@ -345,71 +355,120 @@ private predicate storeCandFwd1(Content f, Configuration config) { ) } +pragma[nomagic] +private predicate nodeCandFwd1ReturnKind( + DataFlowCall call, ReturnKindExt kind, boolean fromArg, Configuration config +) { + exists(ReturnPosition pos | + nodeCandFwd1ReturnPosition(pos, fromArg, config) and + pos = viableReturnPos(call, kind) + ) +} + +pragma[nomagic] +private predicate nodeCandFwd1Out( + DataFlowCall call, Node node, boolean fromArg, Configuration config +) { + exists(ReturnKindExt kind | + nodeCandFwd1ReturnKind(call, kind, fromArg, config) and + node = kind.getAnOutNode(call) + ) +} + +pragma[nomagic] +private predicate nodeCandFwd1OutFromArg(DataFlowCall call, Node node, Configuration config) { + nodeCandFwd1Out(call, node, true, config) +} + +/** + * Holds if an argument to `call` is reached in the flow covered by `nodeCandFwd1`. + */ +pragma[nomagic] +private predicate flowOutCandFwd1(DataFlowCall call, boolean fromArg, Configuration config) { + exists(ArgumentNode arg | + nodeCandFwd1(arg, fromArg, config) and + viableParamArg(call, _, arg) + ) +} + bindingset[result, b] private boolean unbindBool(boolean b) { result != b.booleanNot() } /** * Holds if `node` is part of a path from a source to a sink in the given - * configuration ignoring call contexts. + * configuration taking simple call contexts into consideration. */ pragma[nomagic] -private predicate nodeCand1(Node node, Configuration config) { +private predicate nodeCand1(Node node, boolean toReturn, Configuration config) { + nodeCand1_0(node, toReturn, config) and + nodeCandFwd1(node, config) +} + +pragma[nomagic] +private predicate nodeCand1_0(Node node, boolean toReturn, Configuration config) { nodeCandFwd1(node, config) and - config.isSink(node) + config.isSink(node) and + toReturn = false or - nodeCandFwd1(node, unbind(config)) and - ( - exists(Node mid | - localFlowStep(node, mid, config) and - nodeCand1(mid, config) - ) + exists(Node mid | + localFlowStep(node, mid, config) and + nodeCand1(mid, toReturn, config) + ) + or + exists(Node mid | + additionalLocalFlowStep(node, mid, config) and + nodeCand1(mid, toReturn, config) + ) + or + exists(Node mid | + jumpStep(node, mid, config) and + nodeCand1(mid, _, config) and + toReturn = false + ) + or + exists(Node mid | + additionalJumpStep(node, mid, config) and + nodeCand1(mid, _, config) and + toReturn = false + ) + or + // store + exists(Content f | + nodeCand1Store(f, node, toReturn, config) and + readCand1(f, config) + ) + or + // read + exists(Node mid, Content f | + readDirect(node, f, mid) and + storeCandFwd1(f, unbind(config)) and + nodeCand1(mid, toReturn, config) + ) + or + // flow into a callable + exists(DataFlowCall call | + nodeCand1Arg(call, node, false, config) and + toReturn = false or - exists(Node mid | - additionalLocalFlowStep(node, mid, config) and - nodeCand1(mid, config) - ) - or - exists(Node mid | - jumpStep(node, mid, config) and - nodeCand1(mid, config) - ) - or - exists(Node mid | - additionalJumpStep(node, mid, config) and - nodeCand1(mid, config) - ) - or - // store - exists(Content f | - nodeCand1Store(f, node, config) and - readCand1(f, config) - ) - or - // read - exists(Node mid, Content f | - readDirect(node, f, mid) and - storeCandFwd1(f, unbind(config)) and - nodeCand1(mid, config) - ) - or - // flow into a callable - exists(Node param | - viableParamArg(_, param, node) and - nodeCand1(param, config) - ) - or - // flow out of a callable - exists(ReturnPosition pos | - nodeCand1ReturnPosition(pos, config) and - getReturnPosition(node) = pos - ) + nodeCand1ArgToReturn(call, node, config) and + flowInCand1(call, toReturn, config) + ) + or + // flow out of a callable + exists(ReturnPosition pos | + nodeCand1ReturnPosition(pos, config) and + getReturnPosition(node) = pos and + toReturn = true ) } -pragma[noinline] +pragma[nomagic] +private predicate nodeCand1(Node node, Configuration config) { nodeCand1(node, _, config) } + +pragma[nomagic] private predicate nodeCand1ReturnPosition(ReturnPosition pos, Configuration config) { exists(DataFlowCall call, ReturnKindExt kind, Node out | - nodeCand1(out, config) and + nodeCand1(out, _, config) and pos = viableReturnPos(call, kind) and out = kind.getAnOutNode(call) ) @@ -418,21 +477,21 @@ private predicate nodeCand1ReturnPosition(ReturnPosition pos, Configuration conf /** * Holds if `f` is the target of a read in the flow covered by `nodeCand1`. */ -pragma[noinline] +pragma[nomagic] private predicate readCand1(Content f, Configuration config) { exists(Node mid, Node node | useFieldFlow(config) and nodeCandFwd1(node, unbind(config)) and readDirect(node, f, mid) and storeCandFwd1(f, unbind(config)) and - nodeCand1(mid, config) + nodeCand1(mid, _, config) ) } pragma[nomagic] -private predicate nodeCand1Store(Content f, Node node, Configuration config) { +private predicate nodeCand1Store(Content f, Node node, boolean toReturn, Configuration config) { exists(Node mid | - nodeCand1(mid, config) and + nodeCand1(mid, toReturn, config) and storeCandFwd1(f, unbind(config)) and storeDirect(node, f, mid) ) @@ -444,11 +503,45 @@ private predicate nodeCand1Store(Content f, Node node, Configuration config) { */ private predicate readStoreCand1(Content f, Configuration conf) { readCand1(f, conf) and - nodeCand1Store(f, _, conf) + nodeCand1Store(f, _, _, conf) +} + +pragma[nomagic] +private predicate viableParamArgCandFwd1( + DataFlowCall call, ParameterNode p, ArgumentNode arg, Configuration config +) { + viableParamArg(call, p, arg) and + nodeCandFwd1(arg, config) +} + +pragma[nomagic] +private predicate nodeCand1Arg( + DataFlowCall call, ArgumentNode arg, boolean toReturn, Configuration config +) { + exists(ParameterNode p | + nodeCand1(p, toReturn, config) and + viableParamArgCandFwd1(call, p, arg, config) + ) +} + +pragma[nomagic] +private predicate nodeCand1ArgToReturn(DataFlowCall call, ArgumentNode arg, Configuration config) { + nodeCand1Arg(call, arg, true, config) +} + +/** + * Holds if an output from `call` is reached in the flow covered by `nodeCand1`. + */ +pragma[nomagic] +private predicate flowInCand1(DataFlowCall call, boolean toReturn, Configuration config) { + exists(Node out | + nodeCand1(out, toReturn, config) and + nodeCandFwd1OutFromArg(call, out, config) + ) } private predicate throughFlowNodeCand(Node node, Configuration config) { - nodeCand1(node, config) and + nodeCand1(node, true, config) and not fullBarrier(node, config) and not inBarrier(node, config) and not outBarrier(node, config) @@ -2257,13 +2350,12 @@ private class PathNodeSink extends PathNodeImpl, TPathNodeSink { * a callable is recorded by `cc`. */ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCtx sc, AccessPath ap) { - exists(LocalCallContext localCC, AccessPath ap0, Node midnode, Configuration conf | - midnode = mid.getNode() and - conf = mid.getConfiguration() and - cc = mid.getCallContext() and - sc = mid.getSummaryCtx() and - localCC = getLocalCallContext(cc, midnode.getEnclosingCallable()) and - ap0 = mid.getAp() + exists( + AccessPath ap0, Node midnode, Configuration conf, DataFlowCallable enclosing, + LocalCallContext localCC + | + pathIntoLocalStep(mid, midnode, cc, enclosing, sc, ap0, conf) and + localCC = getLocalCallContext(cc, enclosing) | localFlowBigStep(midnode, node, true, conf, localCC) and ap = ap0 @@ -2297,6 +2389,20 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt pathThroughCallable(mid, node, cc, ap) and sc = mid.getSummaryCtx() } +pragma[nomagic] +private predicate pathIntoLocalStep( + PathNodeMid mid, Node midnode, CallContext cc, DataFlowCallable enclosing, SummaryCtx sc, + AccessPath ap0, Configuration conf +) { + midnode = mid.getNode() and + cc = mid.getCallContext() and + conf = mid.getConfiguration() and + localFlowBigStep(midnode, _, _, conf, _) and + enclosing = midnode.getEnclosingCallable() and + sc = mid.getSummaryCtx() and + ap0 = mid.getAp() +} + pragma[nomagic] private predicate readCand(Node node1, Content f, Node node2, Configuration config) { readDirect(node1, f, node2) and diff --git a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl4.qll b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl4.qll index 86d44e4fcce..5eb98aefe4e 100644 --- a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl4.qll +++ b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl4.qll @@ -259,44 +259,47 @@ private ReturnPosition viableReturnPos(DataFlowCall call, ReturnKindExt kind) { /** * Holds if `node` is reachable from a source in the given configuration - * ignoring call contexts. + * taking simple call contexts into consideration. */ -private predicate nodeCandFwd1(Node node, Configuration config) { +private predicate nodeCandFwd1(Node node, boolean fromArg, Configuration config) { not fullBarrier(node, config) and ( - config.isSource(node) + config.isSource(node) and + fromArg = false or exists(Node mid | - nodeCandFwd1(mid, config) and + nodeCandFwd1(mid, fromArg, config) and localFlowStep(mid, node, config) ) or exists(Node mid | - nodeCandFwd1(mid, config) and + nodeCandFwd1(mid, fromArg, config) and additionalLocalFlowStep(mid, node, config) ) or exists(Node mid | nodeCandFwd1(mid, config) and - jumpStep(mid, node, config) + jumpStep(mid, node, config) and + fromArg = false ) or exists(Node mid | nodeCandFwd1(mid, config) and - additionalJumpStep(mid, node, config) + additionalJumpStep(mid, node, config) and + fromArg = false ) or // store exists(Node mid | useFieldFlow(config) and - nodeCandFwd1(mid, config) and + nodeCandFwd1(mid, fromArg, config) and storeDirect(mid, _, node) and not outBarrier(mid, config) ) or // read exists(Content f | - nodeCandFwd1Read(f, node, config) and + nodeCandFwd1Read(f, node, fromArg, config) and storeCandFwd1(f, config) and not inBarrier(node, config) ) @@ -304,30 +307,37 @@ private predicate nodeCandFwd1(Node node, Configuration config) { // flow into a callable exists(Node arg | nodeCandFwd1(arg, config) and - viableParamArg(_, node, arg) + viableParamArg(_, node, arg) and + fromArg = true ) or // flow out of a callable - exists(DataFlowCall call, ReturnPosition pos, ReturnKindExt kind | - nodeCandFwd1ReturnPosition(pos, config) and - pos = viableReturnPos(call, kind) and - node = kind.getAnOutNode(call) + exists(DataFlowCall call | + nodeCandFwd1Out(call, node, false, config) and + fromArg = false + or + nodeCandFwd1OutFromArg(call, node, config) and + flowOutCandFwd1(call, fromArg, config) ) ) } -pragma[noinline] -private predicate nodeCandFwd1ReturnPosition(ReturnPosition pos, Configuration config) { +private predicate nodeCandFwd1(Node node, Configuration config) { nodeCandFwd1(node, _, config) } + +pragma[nomagic] +private predicate nodeCandFwd1ReturnPosition( + ReturnPosition pos, boolean fromArg, Configuration config +) { exists(ReturnNodeExt ret | - nodeCandFwd1(ret, config) and + nodeCandFwd1(ret, fromArg, config) and getReturnPosition(ret) = pos ) } pragma[nomagic] -private predicate nodeCandFwd1Read(Content f, Node node, Configuration config) { +private predicate nodeCandFwd1Read(Content f, Node node, boolean fromArg, Configuration config) { exists(Node mid | - nodeCandFwd1(mid, config) and + nodeCandFwd1(mid, fromArg, config) and readDirect(mid, f, node) ) } @@ -335,7 +345,7 @@ private predicate nodeCandFwd1Read(Content f, Node node, Configuration config) { /** * Holds if `f` is the target of a store in the flow covered by `nodeCandFwd1`. */ -pragma[noinline] +pragma[nomagic] private predicate storeCandFwd1(Content f, Configuration config) { exists(Node mid, Node node | not fullBarrier(node, config) and @@ -345,71 +355,120 @@ private predicate storeCandFwd1(Content f, Configuration config) { ) } +pragma[nomagic] +private predicate nodeCandFwd1ReturnKind( + DataFlowCall call, ReturnKindExt kind, boolean fromArg, Configuration config +) { + exists(ReturnPosition pos | + nodeCandFwd1ReturnPosition(pos, fromArg, config) and + pos = viableReturnPos(call, kind) + ) +} + +pragma[nomagic] +private predicate nodeCandFwd1Out( + DataFlowCall call, Node node, boolean fromArg, Configuration config +) { + exists(ReturnKindExt kind | + nodeCandFwd1ReturnKind(call, kind, fromArg, config) and + node = kind.getAnOutNode(call) + ) +} + +pragma[nomagic] +private predicate nodeCandFwd1OutFromArg(DataFlowCall call, Node node, Configuration config) { + nodeCandFwd1Out(call, node, true, config) +} + +/** + * Holds if an argument to `call` is reached in the flow covered by `nodeCandFwd1`. + */ +pragma[nomagic] +private predicate flowOutCandFwd1(DataFlowCall call, boolean fromArg, Configuration config) { + exists(ArgumentNode arg | + nodeCandFwd1(arg, fromArg, config) and + viableParamArg(call, _, arg) + ) +} + bindingset[result, b] private boolean unbindBool(boolean b) { result != b.booleanNot() } /** * Holds if `node` is part of a path from a source to a sink in the given - * configuration ignoring call contexts. + * configuration taking simple call contexts into consideration. */ pragma[nomagic] -private predicate nodeCand1(Node node, Configuration config) { +private predicate nodeCand1(Node node, boolean toReturn, Configuration config) { + nodeCand1_0(node, toReturn, config) and + nodeCandFwd1(node, config) +} + +pragma[nomagic] +private predicate nodeCand1_0(Node node, boolean toReturn, Configuration config) { nodeCandFwd1(node, config) and - config.isSink(node) + config.isSink(node) and + toReturn = false or - nodeCandFwd1(node, unbind(config)) and - ( - exists(Node mid | - localFlowStep(node, mid, config) and - nodeCand1(mid, config) - ) + exists(Node mid | + localFlowStep(node, mid, config) and + nodeCand1(mid, toReturn, config) + ) + or + exists(Node mid | + additionalLocalFlowStep(node, mid, config) and + nodeCand1(mid, toReturn, config) + ) + or + exists(Node mid | + jumpStep(node, mid, config) and + nodeCand1(mid, _, config) and + toReturn = false + ) + or + exists(Node mid | + additionalJumpStep(node, mid, config) and + nodeCand1(mid, _, config) and + toReturn = false + ) + or + // store + exists(Content f | + nodeCand1Store(f, node, toReturn, config) and + readCand1(f, config) + ) + or + // read + exists(Node mid, Content f | + readDirect(node, f, mid) and + storeCandFwd1(f, unbind(config)) and + nodeCand1(mid, toReturn, config) + ) + or + // flow into a callable + exists(DataFlowCall call | + nodeCand1Arg(call, node, false, config) and + toReturn = false or - exists(Node mid | - additionalLocalFlowStep(node, mid, config) and - nodeCand1(mid, config) - ) - or - exists(Node mid | - jumpStep(node, mid, config) and - nodeCand1(mid, config) - ) - or - exists(Node mid | - additionalJumpStep(node, mid, config) and - nodeCand1(mid, config) - ) - or - // store - exists(Content f | - nodeCand1Store(f, node, config) and - readCand1(f, config) - ) - or - // read - exists(Node mid, Content f | - readDirect(node, f, mid) and - storeCandFwd1(f, unbind(config)) and - nodeCand1(mid, config) - ) - or - // flow into a callable - exists(Node param | - viableParamArg(_, param, node) and - nodeCand1(param, config) - ) - or - // flow out of a callable - exists(ReturnPosition pos | - nodeCand1ReturnPosition(pos, config) and - getReturnPosition(node) = pos - ) + nodeCand1ArgToReturn(call, node, config) and + flowInCand1(call, toReturn, config) + ) + or + // flow out of a callable + exists(ReturnPosition pos | + nodeCand1ReturnPosition(pos, config) and + getReturnPosition(node) = pos and + toReturn = true ) } -pragma[noinline] +pragma[nomagic] +private predicate nodeCand1(Node node, Configuration config) { nodeCand1(node, _, config) } + +pragma[nomagic] private predicate nodeCand1ReturnPosition(ReturnPosition pos, Configuration config) { exists(DataFlowCall call, ReturnKindExt kind, Node out | - nodeCand1(out, config) and + nodeCand1(out, _, config) and pos = viableReturnPos(call, kind) and out = kind.getAnOutNode(call) ) @@ -418,21 +477,21 @@ private predicate nodeCand1ReturnPosition(ReturnPosition pos, Configuration conf /** * Holds if `f` is the target of a read in the flow covered by `nodeCand1`. */ -pragma[noinline] +pragma[nomagic] private predicate readCand1(Content f, Configuration config) { exists(Node mid, Node node | useFieldFlow(config) and nodeCandFwd1(node, unbind(config)) and readDirect(node, f, mid) and storeCandFwd1(f, unbind(config)) and - nodeCand1(mid, config) + nodeCand1(mid, _, config) ) } pragma[nomagic] -private predicate nodeCand1Store(Content f, Node node, Configuration config) { +private predicate nodeCand1Store(Content f, Node node, boolean toReturn, Configuration config) { exists(Node mid | - nodeCand1(mid, config) and + nodeCand1(mid, toReturn, config) and storeCandFwd1(f, unbind(config)) and storeDirect(node, f, mid) ) @@ -444,11 +503,45 @@ private predicate nodeCand1Store(Content f, Node node, Configuration config) { */ private predicate readStoreCand1(Content f, Configuration conf) { readCand1(f, conf) and - nodeCand1Store(f, _, conf) + nodeCand1Store(f, _, _, conf) +} + +pragma[nomagic] +private predicate viableParamArgCandFwd1( + DataFlowCall call, ParameterNode p, ArgumentNode arg, Configuration config +) { + viableParamArg(call, p, arg) and + nodeCandFwd1(arg, config) +} + +pragma[nomagic] +private predicate nodeCand1Arg( + DataFlowCall call, ArgumentNode arg, boolean toReturn, Configuration config +) { + exists(ParameterNode p | + nodeCand1(p, toReturn, config) and + viableParamArgCandFwd1(call, p, arg, config) + ) +} + +pragma[nomagic] +private predicate nodeCand1ArgToReturn(DataFlowCall call, ArgumentNode arg, Configuration config) { + nodeCand1Arg(call, arg, true, config) +} + +/** + * Holds if an output from `call` is reached in the flow covered by `nodeCand1`. + */ +pragma[nomagic] +private predicate flowInCand1(DataFlowCall call, boolean toReturn, Configuration config) { + exists(Node out | + nodeCand1(out, toReturn, config) and + nodeCandFwd1OutFromArg(call, out, config) + ) } private predicate throughFlowNodeCand(Node node, Configuration config) { - nodeCand1(node, config) and + nodeCand1(node, true, config) and not fullBarrier(node, config) and not inBarrier(node, config) and not outBarrier(node, config) @@ -2257,13 +2350,12 @@ private class PathNodeSink extends PathNodeImpl, TPathNodeSink { * a callable is recorded by `cc`. */ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCtx sc, AccessPath ap) { - exists(LocalCallContext localCC, AccessPath ap0, Node midnode, Configuration conf | - midnode = mid.getNode() and - conf = mid.getConfiguration() and - cc = mid.getCallContext() and - sc = mid.getSummaryCtx() and - localCC = getLocalCallContext(cc, midnode.getEnclosingCallable()) and - ap0 = mid.getAp() + exists( + AccessPath ap0, Node midnode, Configuration conf, DataFlowCallable enclosing, + LocalCallContext localCC + | + pathIntoLocalStep(mid, midnode, cc, enclosing, sc, ap0, conf) and + localCC = getLocalCallContext(cc, enclosing) | localFlowBigStep(midnode, node, true, conf, localCC) and ap = ap0 @@ -2297,6 +2389,20 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt pathThroughCallable(mid, node, cc, ap) and sc = mid.getSummaryCtx() } +pragma[nomagic] +private predicate pathIntoLocalStep( + PathNodeMid mid, Node midnode, CallContext cc, DataFlowCallable enclosing, SummaryCtx sc, + AccessPath ap0, Configuration conf +) { + midnode = mid.getNode() and + cc = mid.getCallContext() and + conf = mid.getConfiguration() and + localFlowBigStep(midnode, _, _, conf, _) and + enclosing = midnode.getEnclosingCallable() and + sc = mid.getSummaryCtx() and + ap0 = mid.getAp() +} + pragma[nomagic] private predicate readCand(Node node1, Content f, Node node2, Configuration config) { readDirect(node1, f, node2) and diff --git a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl5.qll b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl5.qll index 86d44e4fcce..5eb98aefe4e 100644 --- a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl5.qll +++ b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl5.qll @@ -259,44 +259,47 @@ private ReturnPosition viableReturnPos(DataFlowCall call, ReturnKindExt kind) { /** * Holds if `node` is reachable from a source in the given configuration - * ignoring call contexts. + * taking simple call contexts into consideration. */ -private predicate nodeCandFwd1(Node node, Configuration config) { +private predicate nodeCandFwd1(Node node, boolean fromArg, Configuration config) { not fullBarrier(node, config) and ( - config.isSource(node) + config.isSource(node) and + fromArg = false or exists(Node mid | - nodeCandFwd1(mid, config) and + nodeCandFwd1(mid, fromArg, config) and localFlowStep(mid, node, config) ) or exists(Node mid | - nodeCandFwd1(mid, config) and + nodeCandFwd1(mid, fromArg, config) and additionalLocalFlowStep(mid, node, config) ) or exists(Node mid | nodeCandFwd1(mid, config) and - jumpStep(mid, node, config) + jumpStep(mid, node, config) and + fromArg = false ) or exists(Node mid | nodeCandFwd1(mid, config) and - additionalJumpStep(mid, node, config) + additionalJumpStep(mid, node, config) and + fromArg = false ) or // store exists(Node mid | useFieldFlow(config) and - nodeCandFwd1(mid, config) and + nodeCandFwd1(mid, fromArg, config) and storeDirect(mid, _, node) and not outBarrier(mid, config) ) or // read exists(Content f | - nodeCandFwd1Read(f, node, config) and + nodeCandFwd1Read(f, node, fromArg, config) and storeCandFwd1(f, config) and not inBarrier(node, config) ) @@ -304,30 +307,37 @@ private predicate nodeCandFwd1(Node node, Configuration config) { // flow into a callable exists(Node arg | nodeCandFwd1(arg, config) and - viableParamArg(_, node, arg) + viableParamArg(_, node, arg) and + fromArg = true ) or // flow out of a callable - exists(DataFlowCall call, ReturnPosition pos, ReturnKindExt kind | - nodeCandFwd1ReturnPosition(pos, config) and - pos = viableReturnPos(call, kind) and - node = kind.getAnOutNode(call) + exists(DataFlowCall call | + nodeCandFwd1Out(call, node, false, config) and + fromArg = false + or + nodeCandFwd1OutFromArg(call, node, config) and + flowOutCandFwd1(call, fromArg, config) ) ) } -pragma[noinline] -private predicate nodeCandFwd1ReturnPosition(ReturnPosition pos, Configuration config) { +private predicate nodeCandFwd1(Node node, Configuration config) { nodeCandFwd1(node, _, config) } + +pragma[nomagic] +private predicate nodeCandFwd1ReturnPosition( + ReturnPosition pos, boolean fromArg, Configuration config +) { exists(ReturnNodeExt ret | - nodeCandFwd1(ret, config) and + nodeCandFwd1(ret, fromArg, config) and getReturnPosition(ret) = pos ) } pragma[nomagic] -private predicate nodeCandFwd1Read(Content f, Node node, Configuration config) { +private predicate nodeCandFwd1Read(Content f, Node node, boolean fromArg, Configuration config) { exists(Node mid | - nodeCandFwd1(mid, config) and + nodeCandFwd1(mid, fromArg, config) and readDirect(mid, f, node) ) } @@ -335,7 +345,7 @@ private predicate nodeCandFwd1Read(Content f, Node node, Configuration config) { /** * Holds if `f` is the target of a store in the flow covered by `nodeCandFwd1`. */ -pragma[noinline] +pragma[nomagic] private predicate storeCandFwd1(Content f, Configuration config) { exists(Node mid, Node node | not fullBarrier(node, config) and @@ -345,71 +355,120 @@ private predicate storeCandFwd1(Content f, Configuration config) { ) } +pragma[nomagic] +private predicate nodeCandFwd1ReturnKind( + DataFlowCall call, ReturnKindExt kind, boolean fromArg, Configuration config +) { + exists(ReturnPosition pos | + nodeCandFwd1ReturnPosition(pos, fromArg, config) and + pos = viableReturnPos(call, kind) + ) +} + +pragma[nomagic] +private predicate nodeCandFwd1Out( + DataFlowCall call, Node node, boolean fromArg, Configuration config +) { + exists(ReturnKindExt kind | + nodeCandFwd1ReturnKind(call, kind, fromArg, config) and + node = kind.getAnOutNode(call) + ) +} + +pragma[nomagic] +private predicate nodeCandFwd1OutFromArg(DataFlowCall call, Node node, Configuration config) { + nodeCandFwd1Out(call, node, true, config) +} + +/** + * Holds if an argument to `call` is reached in the flow covered by `nodeCandFwd1`. + */ +pragma[nomagic] +private predicate flowOutCandFwd1(DataFlowCall call, boolean fromArg, Configuration config) { + exists(ArgumentNode arg | + nodeCandFwd1(arg, fromArg, config) and + viableParamArg(call, _, arg) + ) +} + bindingset[result, b] private boolean unbindBool(boolean b) { result != b.booleanNot() } /** * Holds if `node` is part of a path from a source to a sink in the given - * configuration ignoring call contexts. + * configuration taking simple call contexts into consideration. */ pragma[nomagic] -private predicate nodeCand1(Node node, Configuration config) { +private predicate nodeCand1(Node node, boolean toReturn, Configuration config) { + nodeCand1_0(node, toReturn, config) and + nodeCandFwd1(node, config) +} + +pragma[nomagic] +private predicate nodeCand1_0(Node node, boolean toReturn, Configuration config) { nodeCandFwd1(node, config) and - config.isSink(node) + config.isSink(node) and + toReturn = false or - nodeCandFwd1(node, unbind(config)) and - ( - exists(Node mid | - localFlowStep(node, mid, config) and - nodeCand1(mid, config) - ) + exists(Node mid | + localFlowStep(node, mid, config) and + nodeCand1(mid, toReturn, config) + ) + or + exists(Node mid | + additionalLocalFlowStep(node, mid, config) and + nodeCand1(mid, toReturn, config) + ) + or + exists(Node mid | + jumpStep(node, mid, config) and + nodeCand1(mid, _, config) and + toReturn = false + ) + or + exists(Node mid | + additionalJumpStep(node, mid, config) and + nodeCand1(mid, _, config) and + toReturn = false + ) + or + // store + exists(Content f | + nodeCand1Store(f, node, toReturn, config) and + readCand1(f, config) + ) + or + // read + exists(Node mid, Content f | + readDirect(node, f, mid) and + storeCandFwd1(f, unbind(config)) and + nodeCand1(mid, toReturn, config) + ) + or + // flow into a callable + exists(DataFlowCall call | + nodeCand1Arg(call, node, false, config) and + toReturn = false or - exists(Node mid | - additionalLocalFlowStep(node, mid, config) and - nodeCand1(mid, config) - ) - or - exists(Node mid | - jumpStep(node, mid, config) and - nodeCand1(mid, config) - ) - or - exists(Node mid | - additionalJumpStep(node, mid, config) and - nodeCand1(mid, config) - ) - or - // store - exists(Content f | - nodeCand1Store(f, node, config) and - readCand1(f, config) - ) - or - // read - exists(Node mid, Content f | - readDirect(node, f, mid) and - storeCandFwd1(f, unbind(config)) and - nodeCand1(mid, config) - ) - or - // flow into a callable - exists(Node param | - viableParamArg(_, param, node) and - nodeCand1(param, config) - ) - or - // flow out of a callable - exists(ReturnPosition pos | - nodeCand1ReturnPosition(pos, config) and - getReturnPosition(node) = pos - ) + nodeCand1ArgToReturn(call, node, config) and + flowInCand1(call, toReturn, config) + ) + or + // flow out of a callable + exists(ReturnPosition pos | + nodeCand1ReturnPosition(pos, config) and + getReturnPosition(node) = pos and + toReturn = true ) } -pragma[noinline] +pragma[nomagic] +private predicate nodeCand1(Node node, Configuration config) { nodeCand1(node, _, config) } + +pragma[nomagic] private predicate nodeCand1ReturnPosition(ReturnPosition pos, Configuration config) { exists(DataFlowCall call, ReturnKindExt kind, Node out | - nodeCand1(out, config) and + nodeCand1(out, _, config) and pos = viableReturnPos(call, kind) and out = kind.getAnOutNode(call) ) @@ -418,21 +477,21 @@ private predicate nodeCand1ReturnPosition(ReturnPosition pos, Configuration conf /** * Holds if `f` is the target of a read in the flow covered by `nodeCand1`. */ -pragma[noinline] +pragma[nomagic] private predicate readCand1(Content f, Configuration config) { exists(Node mid, Node node | useFieldFlow(config) and nodeCandFwd1(node, unbind(config)) and readDirect(node, f, mid) and storeCandFwd1(f, unbind(config)) and - nodeCand1(mid, config) + nodeCand1(mid, _, config) ) } pragma[nomagic] -private predicate nodeCand1Store(Content f, Node node, Configuration config) { +private predicate nodeCand1Store(Content f, Node node, boolean toReturn, Configuration config) { exists(Node mid | - nodeCand1(mid, config) and + nodeCand1(mid, toReturn, config) and storeCandFwd1(f, unbind(config)) and storeDirect(node, f, mid) ) @@ -444,11 +503,45 @@ private predicate nodeCand1Store(Content f, Node node, Configuration config) { */ private predicate readStoreCand1(Content f, Configuration conf) { readCand1(f, conf) and - nodeCand1Store(f, _, conf) + nodeCand1Store(f, _, _, conf) +} + +pragma[nomagic] +private predicate viableParamArgCandFwd1( + DataFlowCall call, ParameterNode p, ArgumentNode arg, Configuration config +) { + viableParamArg(call, p, arg) and + nodeCandFwd1(arg, config) +} + +pragma[nomagic] +private predicate nodeCand1Arg( + DataFlowCall call, ArgumentNode arg, boolean toReturn, Configuration config +) { + exists(ParameterNode p | + nodeCand1(p, toReturn, config) and + viableParamArgCandFwd1(call, p, arg, config) + ) +} + +pragma[nomagic] +private predicate nodeCand1ArgToReturn(DataFlowCall call, ArgumentNode arg, Configuration config) { + nodeCand1Arg(call, arg, true, config) +} + +/** + * Holds if an output from `call` is reached in the flow covered by `nodeCand1`. + */ +pragma[nomagic] +private predicate flowInCand1(DataFlowCall call, boolean toReturn, Configuration config) { + exists(Node out | + nodeCand1(out, toReturn, config) and + nodeCandFwd1OutFromArg(call, out, config) + ) } private predicate throughFlowNodeCand(Node node, Configuration config) { - nodeCand1(node, config) and + nodeCand1(node, true, config) and not fullBarrier(node, config) and not inBarrier(node, config) and not outBarrier(node, config) @@ -2257,13 +2350,12 @@ private class PathNodeSink extends PathNodeImpl, TPathNodeSink { * a callable is recorded by `cc`. */ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCtx sc, AccessPath ap) { - exists(LocalCallContext localCC, AccessPath ap0, Node midnode, Configuration conf | - midnode = mid.getNode() and - conf = mid.getConfiguration() and - cc = mid.getCallContext() and - sc = mid.getSummaryCtx() and - localCC = getLocalCallContext(cc, midnode.getEnclosingCallable()) and - ap0 = mid.getAp() + exists( + AccessPath ap0, Node midnode, Configuration conf, DataFlowCallable enclosing, + LocalCallContext localCC + | + pathIntoLocalStep(mid, midnode, cc, enclosing, sc, ap0, conf) and + localCC = getLocalCallContext(cc, enclosing) | localFlowBigStep(midnode, node, true, conf, localCC) and ap = ap0 @@ -2297,6 +2389,20 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt pathThroughCallable(mid, node, cc, ap) and sc = mid.getSummaryCtx() } +pragma[nomagic] +private predicate pathIntoLocalStep( + PathNodeMid mid, Node midnode, CallContext cc, DataFlowCallable enclosing, SummaryCtx sc, + AccessPath ap0, Configuration conf +) { + midnode = mid.getNode() and + cc = mid.getCallContext() and + conf = mid.getConfiguration() and + localFlowBigStep(midnode, _, _, conf, _) and + enclosing = midnode.getEnclosingCallable() and + sc = mid.getSummaryCtx() and + ap0 = mid.getAp() +} + pragma[nomagic] private predicate readCand(Node node1, Content f, Node node2, Configuration config) { readDirect(node1, f, node2) and 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 daf09497dc4..27d80d8df8d 100644 --- a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowPrivate.qll +++ b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowPrivate.qll @@ -120,7 +120,7 @@ predicate jumpStep(Node node1, Node node2) { * Holds if `fa` is an access to an instance field that occurs as the * destination of an assignment of the value `src`. */ -predicate instanceFieldAssign(Expr src, FieldAccess fa) { +private predicate instanceFieldAssign(Expr src, FieldAccess fa) { exists(AssignExpr a | a.getSource() = src and a.getDest() = fa and diff --git a/javascript/extractor/lib/typescript/package.json b/javascript/extractor/lib/typescript/package.json index fb6b9b987c9..55994647a94 100644 --- a/javascript/extractor/lib/typescript/package.json +++ b/javascript/extractor/lib/typescript/package.json @@ -2,7 +2,7 @@ "name": "typescript-parser-wrapper", "private": true, "dependencies": { - "typescript": "^3.7.2" + "typescript": "3.8.2" }, "scripts": { "build": "tsc --project tsconfig.json", diff --git a/javascript/extractor/lib/typescript/yarn.lock b/javascript/extractor/lib/typescript/yarn.lock index 5116c7eb5c3..5afdf469e36 100644 --- a/javascript/extractor/lib/typescript/yarn.lock +++ b/javascript/extractor/lib/typescript/yarn.lock @@ -225,9 +225,9 @@ tsutils@^2.12.1: dependencies: tslib "^1.8.1" -typescript@^3.7.2: - version "3.7.2" - resolved "typescript-3.7.2.tgz" +typescript@3.8.2: + version "3.8.2" + resolved typescript-3.8.2.tgz#91d6868aaead7da74f493c553aeff76c0c0b1d5a wrappy@1: version "1.0.2" diff --git a/javascript/extractor/src/com/semmle/js/ast/ExportNamedDeclaration.java b/javascript/extractor/src/com/semmle/js/ast/ExportNamedDeclaration.java index c70ed56b30a..d4d19b8d1ab 100644 --- a/javascript/extractor/src/com/semmle/js/ast/ExportNamedDeclaration.java +++ b/javascript/extractor/src/com/semmle/js/ast/ExportNamedDeclaration.java @@ -15,13 +15,21 @@ public class ExportNamedDeclaration extends ExportDeclaration { private final Statement declaration; private final List specifiers; private final Literal source; + private final boolean hasTypeKeyword; public ExportNamedDeclaration( SourceLocation loc, Statement declaration, List specifiers, Literal source) { + this(loc, declaration, specifiers, source, false); + } + + public ExportNamedDeclaration( + SourceLocation loc, Statement declaration, List specifiers, Literal source, + boolean hasTypeKeyword) { super("ExportNamedDeclaration", loc); this.declaration = declaration; this.specifiers = specifiers; this.source = source; + this.hasTypeKeyword = hasTypeKeyword; } public Statement getDeclaration() { @@ -48,4 +56,9 @@ public class ExportNamedDeclaration extends ExportDeclaration { public R accept(Visitor v, C c) { return v.visit(this, c); } + + /** Returns true if this is an export type declaration. */ + public boolean hasTypeKeyword() { + return hasTypeKeyword; + } } diff --git a/javascript/extractor/src/com/semmle/js/ast/ImportDeclaration.java b/javascript/extractor/src/com/semmle/js/ast/ImportDeclaration.java index 1e538c1bb74..8a291000f2f 100644 --- a/javascript/extractor/src/com/semmle/js/ast/ImportDeclaration.java +++ b/javascript/extractor/src/com/semmle/js/ast/ImportDeclaration.java @@ -1,8 +1,9 @@ package com.semmle.js.ast; -import com.semmle.ts.ast.INodeWithSymbol; import java.util.List; +import com.semmle.ts.ast.INodeWithSymbol; + /** * An import declaration, which can be of one of the following forms: * @@ -24,10 +25,17 @@ public class ImportDeclaration extends Statement implements INodeWithSymbol { private int symbol = -1; + private boolean hasTypeKeyword; + public ImportDeclaration(SourceLocation loc, List specifiers, Literal source) { + this(loc, specifiers, source, false); + } + + public ImportDeclaration(SourceLocation loc, List specifiers, Literal source, boolean hasTypeKeyword) { super("ImportDeclaration", loc); this.specifiers = specifiers; this.source = source; + this.hasTypeKeyword = hasTypeKeyword; } public Literal getSource() { @@ -52,4 +60,9 @@ public class ImportDeclaration extends Statement implements INodeWithSymbol { public void setSymbol(int symbol) { this.symbol = symbol; } + + /** Returns true if this is an import type declaration. */ + public boolean hasTypeKeyword() { + return hasTypeKeyword; + } } diff --git a/javascript/extractor/src/com/semmle/js/extractor/ASTExtractor.java b/javascript/extractor/src/com/semmle/js/extractor/ASTExtractor.java index 6af90d562d8..8dcc6d3a5c5 100644 --- a/javascript/extractor/src/com/semmle/js/extractor/ASTExtractor.java +++ b/javascript/extractor/src/com/semmle/js/extractor/ASTExtractor.java @@ -250,6 +250,22 @@ public class ASTExtractor { /** An identifier that declares a variable and a namespace. */ varAndNamespaceDecl, + /** + * An identifier that occurs in a type-only import. + * + * These may declare a type and/or a namespace, but for compatibility with our AST, + * must be emitted as a VarDecl (with no variable binding). + */ + typeOnlyImport, + + /** + * An identifier that occurs in a type-only export. + * + * These may refer to a type and/or a namespace, but for compatibility with our AST, + * must be emitted as an ExportVarAccess (with no variable binding). + */ + typeOnlyExport, + /** An identifier that declares a variable, type, and namepsace. */ varAndTypeAndNamespaceDecl, @@ -278,7 +294,8 @@ public class ASTExtractor { * True if this occurs as part of a type annotation, i.e. it is {@link #typeBind} or {@link * #typeDecl}, {@link #typeLabel}, {@link #varInTypeBind}, or {@link #namespaceBind}. * - *

Does not hold for {@link #varAndTypeDecl}. + *

Does not hold for {@link #varAndTypeDecl}, {@link #typeOnlyImport}, or @{link {@link #typeOnlyExport} + * as these do not occur in type annotations. */ public boolean isInsideType() { return this == typeBind @@ -488,6 +505,14 @@ public class ASTExtractor { addVariableBinding("decl", key, name); addNamespaceBinding("namespacedecl", key, name); break; + case typeOnlyImport: + addTypeBinding("typedecl", key, name); + addNamespaceBinding("namespacedecl", key, name); + break; + case typeOnlyExport: + addTypeBinding("typebind", key, name); + addNamespaceBinding("namespacebind", key, name); + break; case varAndTypeAndNamespaceDecl: addVariableBinding("decl", key, name); addTypeBinding("typedecl", key, name); @@ -1538,7 +1563,14 @@ public class ASTExtractor { Label lbl = super.visit(nd, c); visit(nd.getDeclaration(), lbl, -1); visit(nd.getSource(), lbl, -2); - visitAll(nd.getSpecifiers(), lbl, nd.hasSource() ? IdContext.label : IdContext.export, 0); + IdContext childContext = + nd.hasSource() ? IdContext.label : + nd.hasTypeKeyword() ? IdContext.typeOnlyExport : + IdContext.export; + visitAll(nd.getSpecifiers(), lbl, childContext, 0); + if (nd.hasTypeKeyword()) { + trapwriter.addTuple("hasTypeKeyword", lbl); + } return lbl; } @@ -1554,8 +1586,12 @@ public class ASTExtractor { public Label visit(ImportDeclaration nd, Context c) { Label lbl = super.visit(nd, c); visit(nd.getSource(), lbl, -1); - visitAll(nd.getSpecifiers(), lbl); + IdContext childContext = nd.hasTypeKeyword() ? IdContext.typeOnlyImport : IdContext.varAndTypeAndNamespaceDecl; + visitAll(nd.getSpecifiers(), lbl, childContext, 0); emitNodeSymbol(nd, lbl); + if (nd.hasTypeKeyword()) { + trapwriter.addTuple("hasTypeKeyword", lbl); + } return lbl; } @@ -1563,7 +1599,7 @@ public class ASTExtractor { public Label visit(ImportSpecifier nd, Context c) { Label lbl = super.visit(nd, c); visit(nd.getImported(), lbl, 0, IdContext.label); - visit(nd.getLocal(), lbl, 1, IdContext.varAndTypeAndNamespaceDecl); + visit(nd.getLocal(), lbl, 1, c.idcontext); return lbl; } diff --git a/javascript/extractor/src/com/semmle/js/extractor/ExprKinds.java b/javascript/extractor/src/com/semmle/js/extractor/ExprKinds.java index 122ccda028e..d88f341bc0e 100644 --- a/javascript/extractor/src/com/semmle/js/extractor/ExprKinds.java +++ b/javascript/extractor/src/com/semmle/js/extractor/ExprKinds.java @@ -1,5 +1,9 @@ package com.semmle.js.extractor; +import java.util.EnumMap; +import java.util.LinkedHashMap; +import java.util.Map; + import com.semmle.jcorn.TokenType; import com.semmle.jcorn.jsx.JSXParser; import com.semmle.js.ast.AssignmentExpression; @@ -29,9 +33,6 @@ import com.semmle.ts.ast.DecoratorList; import com.semmle.ts.ast.ExpressionWithTypeArguments; import com.semmle.ts.ast.TypeAssertion; import com.semmle.util.exception.CatastrophicError; -import java.util.EnumMap; -import java.util.LinkedHashMap; -import java.util.Map; /** Map from SpiderMonkey expression types to the numeric kinds used in the DB scheme. */ public class ExprKinds { @@ -154,6 +155,8 @@ public class ExprKinds { idKinds.put(IdContext.namespaceDecl, 78); idKinds.put(IdContext.varAndNamespaceDecl, 78); idKinds.put(IdContext.varAndTypeAndNamespaceDecl, 78); + idKinds.put(IdContext.typeOnlyImport, 78); + idKinds.put(IdContext.typeOnlyExport, 103); idKinds.put(IdContext.varBind, 79); idKinds.put(IdContext.export, 103); idKinds.put(IdContext.exportBase, 103); diff --git a/javascript/extractor/src/com/semmle/js/extractor/Main.java b/javascript/extractor/src/com/semmle/js/extractor/Main.java index 00e9dee9f9e..81e25eb23e0 100644 --- a/javascript/extractor/src/com/semmle/js/extractor/Main.java +++ b/javascript/extractor/src/com/semmle/js/extractor/Main.java @@ -37,7 +37,7 @@ public class Main { * A version identifier that should be updated every time the extractor changes in such a way that * it may produce different tuples for the same file under the same {@link ExtractorConfig}. */ - public static final String EXTRACTOR_VERSION = "2020-02-05"; + public static final String EXTRACTOR_VERSION = "2020-02-14"; public static final Pattern NEWLINE = Pattern.compile("\n"); diff --git a/javascript/extractor/src/com/semmle/js/parser/RegExpParser.java b/javascript/extractor/src/com/semmle/js/parser/RegExpParser.java index 0f6ef74972c..c211120ae73 100644 --- a/javascript/extractor/src/com/semmle/js/parser/RegExpParser.java +++ b/javascript/extractor/src/com/semmle/js/parser/RegExpParser.java @@ -281,8 +281,19 @@ public class RegExpParser { if (this.match("+")) return this.finishTerm(new Plus(loc, atom, !this.match("?"))); if (this.match("?")) return this.finishTerm(new Opt(loc, atom, !this.match("?"))); if (this.match("{")) { - Double lo = toNumber(this.readDigits(false)), hi = null; - if (this.match(",") && !this.lookahead("}")) hi = toNumber(this.readDigits(false)); + Double lo = toNumber(this.readDigits(false)), hi; + if (this.match(",")) { + if (!this.lookahead("}")) { + // atom{lo, hi} + hi = toNumber(this.readDigits(false)); + } else { + // atom{lo,} + hi = null; + } + } else { + // atom{lo} + hi = lo; + } this.expectRBrace(); return this.finishTerm(new Range(loc, atom, !this.match("?"), lo, hi)); } diff --git a/javascript/extractor/src/com/semmle/js/parser/TypeScriptASTConverter.java b/javascript/extractor/src/com/semmle/js/parser/TypeScriptASTConverter.java index 1b0e5421497..0643cc15ef5 100644 --- a/javascript/extractor/src/com/semmle/js/parser/TypeScriptASTConverter.java +++ b/javascript/extractor/src/com/semmle/js/parser/TypeScriptASTConverter.java @@ -1,5 +1,13 @@ package com.semmle.js.parser; +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.gson.JsonNull; @@ -34,6 +42,7 @@ import com.semmle.js.ast.ExportAllDeclaration; import com.semmle.js.ast.ExportDeclaration; import com.semmle.js.ast.ExportDefaultDeclaration; import com.semmle.js.ast.ExportNamedDeclaration; +import com.semmle.js.ast.ExportNamespaceSpecifier; import com.semmle.js.ast.ExportSpecifier; import com.semmle.js.ast.Expression; import com.semmle.js.ast.ExpressionStatement; @@ -144,13 +153,6 @@ import com.semmle.ts.ast.UnaryTypeExpr; import com.semmle.ts.ast.UnionTypeExpr; import com.semmle.util.collections.CollectionUtil; import com.semmle.util.data.IntList; -import java.util.ArrayList; -import java.util.Collections; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.regex.Matcher; -import java.util.regex.Pattern; /** * Utility class for converting a specifiers = + hasKind(node.get("exportClause"), "NamespaceExport") + ? Collections.singletonList(convertChild(node, "exportClause")) + : convertChildren(node.get("exportClause").getAsJsonObject(), "elements"); + return new ExportNamedDeclaration(loc, null, specifiers, source, hasTypeKeyword); } else { return new ExportAllDeclaration(loc, source); } @@ -1187,6 +1197,11 @@ public class TypeScriptASTConverter { convertChild(node, "name")); } + private Node convertNamespaceExport(JsonObject node, SourceLocation loc) throws ParseError { + // Convert the "* as ns" from an export declaration. + return new ExportNamespaceSpecifier(loc, convertChild(node, "name")); + } + private Node convertExpressionStatement(JsonObject node, SourceLocation loc) throws ParseError { Expression expression = convertChild(node, "expression"); return new ExpressionStatement(loc, expression); @@ -1354,6 +1369,7 @@ public class TypeScriptASTConverter { private Node convertImportDeclaration(JsonObject node, SourceLocation loc) throws ParseError { Literal src = tryConvertChild(node, "moduleSpecifier", Literal.class); List specifiers = new ArrayList<>(); + boolean hasTypeKeyword = false; if (hasChild(node, "importClause")) { JsonObject importClause = node.get("importClause").getAsJsonObject(); if (hasChild(importClause, "name")) { @@ -1367,8 +1383,9 @@ public class TypeScriptASTConverter { specifiers.addAll(convertChildren(namedBindings, "elements")); } } + hasTypeKeyword = importClause.get("isTypeOnly").getAsBoolean(); } - ImportDeclaration importDecl = new ImportDeclaration(loc, specifiers, src); + ImportDeclaration importDecl = new ImportDeclaration(loc, specifiers, src, hasTypeKeyword); attachSymbolInformation(importDecl, node); return importDecl; } diff --git a/javascript/extractor/tests/exprs/output/trap/regexp.js.trap b/javascript/extractor/tests/exprs/output/trap/regexp.js.trap index b8958389d6b..84dd57d3a17 100644 --- a/javascript/extractor/tests/exprs/output/trap/regexp.js.trap +++ b/javascript/extractor/tests/exprs/output/trap/regexp.js.trap @@ -1088,6 +1088,7 @@ locations_default(#20376,#10000,15,2,15,5) hasLocation(#20375,#20376) isGreedy(#20375) rangeQuantifierLowerBound(#20375,1) +rangeQuantifierUpperBound(#20375,1) #20377=* regexpterm(#20377,14,#20375,0,"a") #20378=@"loc,{#10000},15,2,15,2" @@ -1157,6 +1158,7 @@ regexpterm(#20393,11,#20392,0,"a{1}?") locations_default(#20394,#10000,18,2,18,6) hasLocation(#20393,#20394) rangeQuantifierLowerBound(#20393,1) +rangeQuantifierUpperBound(#20393,1) #20395=* regexpterm(#20395,14,#20393,0,"a") #20396=@"loc,{#10000},18,2,18,2" @@ -1670,6 +1672,7 @@ locations_default(#20543,#10000,37,2,37,3) hasLocation(#20542,#20543) isGreedy(#20542) rangeQuantifierLowerBound(#20542,0) +rangeQuantifierUpperBound(#20542,0) #20544=* regexpterm(#20544,14,#20542,0,"a") #20545=@"loc,{#10000},37,2,37,2" @@ -1708,6 +1711,7 @@ locations_default(#20555,#10000,38,2,38,3) hasLocation(#20554,#20555) isGreedy(#20554) rangeQuantifierLowerBound(#20554,0) +rangeQuantifierUpperBound(#20554,0) #20556=* regexpterm(#20556,14,#20554,0,"a") #20557=@"loc,{#10000},38,2,38,2" @@ -1754,6 +1758,7 @@ locations_default(#20569,#10000,39,2,39,4) hasLocation(#20568,#20569) isGreedy(#20568) rangeQuantifierLowerBound(#20568,2) +rangeQuantifierUpperBound(#20568,2) #20570=* regexpterm(#20570,14,#20568,0,"a") #20571=@"loc,{#10000},39,2,39,2" diff --git a/javascript/extractor/tests/ts/input/privateField.ts b/javascript/extractor/tests/ts/input/privateField.ts new file mode 100644 index 00000000000..a6cd745260c --- /dev/null +++ b/javascript/extractor/tests/ts/input/privateField.ts @@ -0,0 +1,6 @@ +class C { + #foo; + constructor() { + this.#foo = 5; + } +} diff --git a/javascript/extractor/tests/ts/output/trap/privateField.ts.trap b/javascript/extractor/tests/ts/output/trap/privateField.ts.trap new file mode 100644 index 00000000000..97567d63457 --- /dev/null +++ b/javascript/extractor/tests/ts/output/trap/privateField.ts.trap @@ -0,0 +1,273 @@ +#10000=@"/privateField.ts;sourcefile" +files(#10000,"/privateField.ts","privateField","ts",0) +#10001=@"/;folder" +folders(#10001,"/","") +containerparent(#10001,#10000) +#10002=@"loc,{#10000},0,0,0,0" +locations_default(#10002,#10000,0,0,0,0) +hasLocation(#10000,#10002) +#20000=@"global_scope" +scopes(#20000,0) +#20001=@"script;{#10000},1,1" +#20002=* +lines(#20002,#20001,"class C {"," +") +#20003=@"loc,{#10000},1,1,1,9" +locations_default(#20003,#10000,1,1,1,9) +hasLocation(#20002,#20003) +#20004=* +lines(#20004,#20001," #foo;"," +") +#20005=@"loc,{#10000},2,1,2,6" +locations_default(#20005,#10000,2,1,2,6) +hasLocation(#20004,#20005) +indentation(#10000,2," ",1) +#20006=* +lines(#20006,#20001," constructor() {"," +") +#20007=@"loc,{#10000},3,1,3,16" +locations_default(#20007,#10000,3,1,3,16) +hasLocation(#20006,#20007) +indentation(#10000,3," ",1) +#20008=* +lines(#20008,#20001," this.#foo = 5;"," +") +#20009=@"loc,{#10000},4,1,4,17" +locations_default(#20009,#10000,4,1,4,17) +hasLocation(#20008,#20009) +indentation(#10000,4," ",3) +#20010=* +lines(#20010,#20001," }"," +") +#20011=@"loc,{#10000},5,1,5,2" +locations_default(#20011,#10000,5,1,5,2) +hasLocation(#20010,#20011) +indentation(#10000,5," ",1) +#20012=* +lines(#20012,#20001,"}"," +") +#20013=@"loc,{#10000},6,1,6,1" +locations_default(#20013,#10000,6,1,6,1) +hasLocation(#20012,#20013) +numlines(#20001,6,6,0) +#20014=* +tokeninfo(#20014,7,#20001,0,"class") +#20015=@"loc,{#10000},1,1,1,5" +locations_default(#20015,#10000,1,1,1,5) +hasLocation(#20014,#20015) +#20016=* +tokeninfo(#20016,6,#20001,1,"C") +#20017=@"loc,{#10000},1,7,1,7" +locations_default(#20017,#10000,1,7,1,7) +hasLocation(#20016,#20017) +#20018=* +tokeninfo(#20018,8,#20001,2,"{") +#20019=@"loc,{#10000},1,9,1,9" +locations_default(#20019,#10000,1,9,1,9) +hasLocation(#20018,#20019) +#20020=* +tokeninfo(#20020,8,#20001,3,";") +#20021=@"loc,{#10000},2,6,2,6" +locations_default(#20021,#10000,2,6,2,6) +hasLocation(#20020,#20021) +#20022=* +tokeninfo(#20022,7,#20001,4,"constructor") +#20023=@"loc,{#10000},3,2,3,12" +locations_default(#20023,#10000,3,2,3,12) +hasLocation(#20022,#20023) +#20024=* +tokeninfo(#20024,8,#20001,5,"(") +#20025=@"loc,{#10000},3,13,3,13" +locations_default(#20025,#10000,3,13,3,13) +hasLocation(#20024,#20025) +#20026=* +tokeninfo(#20026,8,#20001,6,")") +#20027=@"loc,{#10000},3,14,3,14" +locations_default(#20027,#10000,3,14,3,14) +hasLocation(#20026,#20027) +#20028=* +tokeninfo(#20028,8,#20001,7,"{") +#20029=@"loc,{#10000},3,16,3,16" +locations_default(#20029,#10000,3,16,3,16) +hasLocation(#20028,#20029) +#20030=* +tokeninfo(#20030,7,#20001,8,"this") +#20031=@"loc,{#10000},4,4,4,7" +locations_default(#20031,#10000,4,4,4,7) +hasLocation(#20030,#20031) +#20032=* +tokeninfo(#20032,8,#20001,9,".") +#20033=@"loc,{#10000},4,8,4,8" +locations_default(#20033,#10000,4,8,4,8) +hasLocation(#20032,#20033) +#20034=* +tokeninfo(#20034,8,#20001,10,"=") +#20035=@"loc,{#10000},4,14,4,14" +locations_default(#20035,#10000,4,14,4,14) +hasLocation(#20034,#20035) +#20036=* +tokeninfo(#20036,3,#20001,11,"5") +#20037=@"loc,{#10000},4,16,4,16" +locations_default(#20037,#10000,4,16,4,16) +hasLocation(#20036,#20037) +#20038=* +tokeninfo(#20038,8,#20001,12,";") +#20039=@"loc,{#10000},4,17,4,17" +locations_default(#20039,#10000,4,17,4,17) +hasLocation(#20038,#20039) +#20040=* +tokeninfo(#20040,8,#20001,13,"}") +#20041=@"loc,{#10000},5,2,5,2" +locations_default(#20041,#10000,5,2,5,2) +hasLocation(#20040,#20041) +#20042=* +tokeninfo(#20042,8,#20001,14,"}") +hasLocation(#20042,#20013) +#20043=* +tokeninfo(#20043,0,#20001,15,"") +#20044=@"loc,{#10000},7,1,7,0" +locations_default(#20044,#10000,7,1,7,0) +hasLocation(#20043,#20044) +toplevels(#20001,0) +#20045=@"loc,{#10000},1,1,7,0" +locations_default(#20045,#10000,1,1,7,0) +hasLocation(#20001,#20045) +#20046=@"var;{C};{#20000}" +variables(#20046,"C",#20000) +#20047=@"local_type_name;{C};{#20000}" +local_type_names(#20047,"C",#20000) +#20048=* +stmts(#20048,26,#20001,0,"class C ... 5;\n }\n}") +#20049=@"loc,{#10000},1,1,6,1" +locations_default(#20049,#10000,1,1,6,1) +hasLocation(#20048,#20049) +stmtContainers(#20048,#20001) +#20050=* +exprs(#20050,78,#20048,0,"C") +hasLocation(#20050,#20017) +enclosingStmt(#20050,#20048) +exprContainers(#20050,#20001) +literals("C","C",#20050) +decl(#20050,#20046) +typedecl(#20050,#20047) +#20051=* +scopes(#20051,10) +scopenodes(#20048,#20051) +scopenesting(#20051,#20000) +#20052=* +properties(#20052,#20048,2,8,"#foo;") +#20053=@"loc,{#10000},2,2,2,6" +locations_default(#20053,#10000,2,2,2,6) +hasLocation(#20052,#20053) +#20054=* +#20055=* +exprs(#20055,0,#20052,0,"#foo") +#20056=@"loc,{#10000},2,2,2,5" +locations_default(#20056,#10000,2,2,2,5) +hasLocation(#20055,#20056) +exprContainers(#20055,#20054) +literals("#foo","#foo",#20055) +#20057=* +properties(#20057,#20048,3,0,"constru ... = 5;\n }") +#20058=@"loc,{#10000},3,2,5,2" +locations_default(#20058,#10000,3,2,5,2) +hasLocation(#20057,#20058) +#20059=* +exprs(#20059,0,#20057,0,"constru ... = 5;\n }") +hasLocation(#20059,#20058) +enclosingStmt(#20059,#20048) +exprContainers(#20059,#20001) +literals("constructor","constructor",#20059) +exprs(#20054,9,#20057,1,"constru ... = 5;\n }") +hasLocation(#20054,#20058) +enclosingStmt(#20054,#20048) +exprContainers(#20054,#20001) +#20060=* +scopes(#20060,1) +scopenodes(#20054,#20060) +scopenesting(#20060,#20051) +#20061=@"var;{arguments};{#20060}" +variables(#20061,"arguments",#20060) +isArgumentsObject(#20061) +#20062=* +stmts(#20062,1,#20054,-2,"{\n th ... = 5;\n }") +#20063=@"loc,{#10000},3,16,5,2" +locations_default(#20063,#10000,3,16,5,2) +hasLocation(#20062,#20063) +stmtContainers(#20062,#20054) +#20064=* +stmts(#20064,2,#20062,0,"this.#foo = 5;") +#20065=@"loc,{#10000},4,4,4,17" +locations_default(#20065,#10000,4,4,4,17) +hasLocation(#20064,#20065) +stmtContainers(#20064,#20054) +#20066=* +exprs(#20066,47,#20064,0,"this.#foo = 5") +#20067=@"loc,{#10000},4,4,4,16" +locations_default(#20067,#10000,4,4,4,16) +hasLocation(#20066,#20067) +enclosingStmt(#20066,#20064) +exprContainers(#20066,#20054) +#20068=* +exprs(#20068,14,#20066,0,"this.#foo") +#20069=@"loc,{#10000},4,4,4,12" +locations_default(#20069,#10000,4,4,4,12) +hasLocation(#20068,#20069) +enclosingStmt(#20068,#20064) +exprContainers(#20068,#20054) +#20070=* +exprs(#20070,6,#20068,0,"this") +hasLocation(#20070,#20031) +enclosingStmt(#20070,#20064) +exprContainers(#20070,#20054) +#20071=* +exprs(#20071,0,#20068,1,"#foo") +#20072=@"loc,{#10000},4,9,4,12" +locations_default(#20072,#10000,4,9,4,12) +hasLocation(#20071,#20072) +enclosingStmt(#20071,#20064) +exprContainers(#20071,#20054) +literals("#foo","#foo",#20071) +#20073=* +exprs(#20073,3,#20066,1,"5") +hasLocation(#20073,#20037) +enclosingStmt(#20073,#20064) +exprContainers(#20073,#20054) +literals("5","5",#20073) +isMethod(#20057) +#20074=* +entry_cfg_node(#20074,#20001) +#20075=@"loc,{#10000},1,1,1,0" +locations_default(#20075,#10000,1,1,1,0) +hasLocation(#20074,#20075) +#20076=* +exit_cfg_node(#20076,#20001) +hasLocation(#20076,#20044) +successor(#20055,#20052) +successor(#20054,#20057) +#20077=* +entry_cfg_node(#20077,#20054) +#20078=@"loc,{#10000},3,2,3,1" +locations_default(#20078,#10000,3,2,3,1) +hasLocation(#20077,#20078) +successor(#20052,#20062) +#20079=* +exit_cfg_node(#20079,#20054) +#20080=@"loc,{#10000},5,3,5,2" +locations_default(#20080,#10000,5,3,5,2) +hasLocation(#20079,#20080) +successor(#20062,#20064) +successor(#20064,#20070) +successor(#20073,#20066) +successor(#20071,#20068) +successor(#20070,#20071) +successor(#20068,#20073) +successor(#20066,#20079) +successor(#20077,#20055) +successor(#20059,#20054) +successor(#20057,#20048) +successor(#20050,#20059) +successor(#20048,#20076) +successor(#20074,#20050) +numlines(#10000,6,6,0) +filetype(#10000,"typescript") diff --git a/javascript/ql/src/Expressions/MissingAwait.ql b/javascript/ql/src/Expressions/MissingAwait.ql index f6a446e43c2..be40eef0d4b 100644 --- a/javascript/ql/src/Expressions/MissingAwait.ql +++ b/javascript/ql/src/Expressions/MissingAwait.ql @@ -43,7 +43,10 @@ predicate isBadPromiseContext(Expr expr) { or expr = any(LogicalBinaryExpr e).getLeftOperand() or - expr = any(UnaryExpr e).getOperand() + exists(UnaryExpr e | + expr = e.getOperand() and + not e instanceof VoidExpr + ) or expr = any(UpdateExpr e).getOperand() or diff --git a/javascript/ql/src/Security/CWE-400/PrototypePollutionUtility.ql b/javascript/ql/src/Security/CWE-400/PrototypePollutionUtility.ql index 418031eeee1..59f637e95ef 100644 --- a/javascript/ql/src/Security/CWE-400/PrototypePollutionUtility.ql +++ b/javascript/ql/src/Security/CWE-400/PrototypePollutionUtility.ql @@ -188,12 +188,7 @@ class PropNameTracking extends DataFlow::Configuration { override predicate isBarrier(DataFlow::Node node) { super.isBarrier(node) or - exists(ConditionGuardNode guard, SsaRefinementNode refinement | - node = DataFlow::ssaDefinitionNode(refinement) and - refinement.getGuard() = guard and - guard.getTest() instanceof VarAccess and - guard.getOutcome() = false - ) + node instanceof DataFlow::VarAccessBarrier } override predicate isBarrierGuard(DataFlow::BarrierGuardNode node) { @@ -204,7 +199,8 @@ class PropNameTracking extends DataFlow::Configuration { node instanceof InstanceOfGuard or node instanceof TypeofGuard or node instanceof BlacklistInclusionGuard or - node instanceof WhitelistInclusionGuard + node instanceof WhitelistInclusionGuard or + node instanceof IsPlainObjectGuard } } @@ -379,6 +375,25 @@ class WhitelistInclusionGuard extends DataFlow::LabeledBarrierGuardNode { } } +/** + * A check of form `isPlainObject(e)` or similar, which sanitizes the `constructor` + * payload in the true case, since it rejects objects with a non-standard `constructor` + * property. + */ +class IsPlainObjectGuard extends DataFlow::LabeledBarrierGuardNode, DataFlow::CallNode { + IsPlainObjectGuard() { + exists(string name | name = "is-plain-object" or name = "is-extendable" | + this = moduleImport(name).getACall() + ) + } + + override predicate blocks(boolean outcome, Expr e, DataFlow::FlowLabel lbl) { + e = getArgument(0).asExpr() and + outcome = true and + lbl = "constructor" + } +} + /** * Gets a meaningful name for `node` if possible. */ diff --git a/javascript/ql/src/semmle/javascript/AST.qll b/javascript/ql/src/semmle/javascript/AST.qll index 66a5155458c..b1cbe224764 100644 --- a/javascript/ql/src/semmle/javascript/AST.qll +++ b/javascript/ql/src/semmle/javascript/AST.qll @@ -125,7 +125,7 @@ class ASTNode extends @ast_node, Locatable { * Holds if this is part of an ambient declaration or type annotation in a TypeScript file. * * A declaration is ambient if it occurs under a `declare` modifier or is - * an interface declaration, type alias, or type annotation. + * an interface declaration, type alias, type annotation, or type-only import/export declaration. * * The TypeScript compiler emits no code for ambient declarations, but they * can affect name resolution and type checking at compile-time. diff --git a/javascript/ql/src/semmle/javascript/Closure.qll b/javascript/ql/src/semmle/javascript/Closure.qll index 83b8049d48f..c8adaad42d1 100644 --- a/javascript/ql/src/semmle/javascript/Closure.qll +++ b/javascript/ql/src/semmle/javascript/Closure.qll @@ -267,6 +267,9 @@ module Closure { result = this } - override DataFlow::Node getBoundReceiver() { result = getArgument(1) } + override DataFlow::Node getBoundReceiver(DataFlow::Node callback) { + callback = getArgument(0) and + result = getArgument(1) + } } } diff --git a/javascript/ql/src/semmle/javascript/ES2015Modules.qll b/javascript/ql/src/semmle/javascript/ES2015Modules.qll index f75f582d851..ca50b3cdcc6 100644 --- a/javascript/ql/src/semmle/javascript/ES2015Modules.qll +++ b/javascript/ql/src/semmle/javascript/ES2015Modules.qll @@ -76,6 +76,16 @@ class ImportDeclaration extends Stmt, Import, @importdeclaration { // `import { createServer } from 'http'` result = DataFlow::destructuredModuleImportNode(this) } + + /** Holds if this is declared with the `type` keyword, so it only imports types. */ + predicate isTypeOnly() { + hasTypeKeyword(this) + } + + override predicate isAmbient() { + Stmt.super.isAmbient() or + isTypeOnly() + } } /** A literal path expression appearing in an `import` declaration. */ @@ -256,6 +266,16 @@ abstract class ExportDeclaration extends Stmt, @exportdeclaration { * to module `a` or possibly to some other module from which `a` re-exports. */ abstract DataFlow::Node getSourceNode(string name); + + /** Holds if is declared with the `type` keyword, so only types are exported. */ + predicate isTypeOnly() { + hasTypeKeyword(this) + } + + override predicate isAmbient() { + Stmt.super.isAmbient() or + isTypeOnly() + } } /** @@ -413,6 +433,18 @@ class ExportNamedDeclaration extends ExportDeclaration, @exportnameddeclaration } } +/** + * An export declaration with the `type` modifier. + */ +private class TypeOnlyExportDeclaration extends ExportNamedDeclaration { + TypeOnlyExportDeclaration() { isTypeOnly() } + + override predicate exportsAs(LexicalName v, string name) { + super.exportsAs(v, name) and + not v instanceof Variable + } +} + /** * An export specifier in an export declaration. * diff --git a/javascript/ql/src/semmle/javascript/Regexp.qll b/javascript/ql/src/semmle/javascript/Regexp.qll index 29e5a1bae3e..e3c45261537 100644 --- a/javascript/ql/src/semmle/javascript/Regexp.qll +++ b/javascript/ql/src/semmle/javascript/Regexp.qll @@ -75,25 +75,22 @@ class RegExpTerm extends Locatable, @regexpterm { /** Gets the regular expression term that is matched (textually) before this one, if any. */ RegExpTerm getPredecessor() { - exists(RegExpSequence seq, int i | - seq.getChild(i) = this and - seq.getChild(i - 1) = result + exists(RegExpTerm parent | parent = getParent() | + result = parent.(RegExpSequence).previousElement(this) + or + not exists(parent.(RegExpSequence).previousElement(this)) and + not parent instanceof RegExpSubPattern and + result = parent.getPredecessor() ) - or - result = getParent().(RegExpTerm).getPredecessor() } /** Gets the regular expression term that is matched (textually) after this one, if any. */ RegExpTerm getSuccessor() { - exists(RegExpSequence seq, int i | - seq.getChild(i) = this and - seq.getChild(i + 1) = result - ) - or - exists(RegExpTerm parent | - parent = getParent() and - not parent instanceof RegExpSubPattern - | + exists(RegExpTerm parent | parent = getParent() | + result = parent.(RegExpSequence).nextElement(this) + or + not exists(parent.(RegExpSequence).nextElement(this)) and + not parent instanceof RegExpSubPattern and result = parent.getSuccessor() ) } @@ -313,6 +310,19 @@ class RegExpSequence extends RegExpTerm, @regexp_seq { or result = getChild(i).getConstantValue() + getConstantValue(i+1) } + + /** Gets the element preceding `element` in this sequence. */ + RegExpTerm previousElement(RegExpTerm element) { + element = nextElement(result) + } + + /** Gets the element following `element` in this sequence. */ + RegExpTerm nextElement(RegExpTerm element) { + exists(int i | + element = this.getChild(i) and + result = this.getChild(i + 1) + ) + } } /** @@ -506,17 +516,25 @@ class RegExpOpt extends RegExpQuantifier, @regexp_opt { /** * A range-quantified term * - * Example: + * Examples: * * ``` * \w{2,4} + * \w{2,} + * \w{2} * ``` */ class RegExpRange extends RegExpQuantifier, @regexp_range { - /** Gets the lower bound of the range, if any. */ + /** Gets the lower bound of the range. */ int getLowerBound() { rangeQuantifierLowerBound(this, result) } - /** Gets the upper bound of the range, if any. */ + /** + * Gets the upper bound of the range, if any. + * + * If there is no upper bound, any number of repetitions is allowed. + * For a term of the form `r{lo}`, both the lower and the upper bound + * are `lo`. + */ int getUpperBound() { rangeQuantifierUpperBound(this, result) } override predicate isNullable() { diff --git a/javascript/ql/src/semmle/javascript/StandardLibrary.qll b/javascript/ql/src/semmle/javascript/StandardLibrary.qll index 0de4cce8e05..aae98553faf 100644 --- a/javascript/ql/src/semmle/javascript/StandardLibrary.qll +++ b/javascript/ql/src/semmle/javascript/StandardLibrary.qll @@ -46,33 +46,26 @@ class DirectEval extends CallExpr { } /** - * Flow analysis for `this` expressions inside a function that is called with - * `Array.prototype.map` or a similar Array function that binds `this`. - * - * However, since the function could be invoked in another way, we additionally - * still infer the ordinary abstract value. + * Models `Array.prototype.map` and friends as partial invocations that pass their second + * argument as the receiver to the callback. */ -private class AnalyzedThisInArrayIterationFunction extends AnalyzedNode, DataFlow::ThisNode { - AnalyzedNode thisSource; - - AnalyzedThisInArrayIterationFunction() { - exists(DataFlow::MethodCallNode bindingCall, string name | +private class ArrayIterationCallbackAsPartialInvoke extends DataFlow::PartialInvokeNode::Range, DataFlow::MethodCallNode { + ArrayIterationCallbackAsPartialInvoke() { + getNumArgument() = 2 and + // Filter out library methods named 'forEach' etc + not DataFlow::moduleImport(_).flowsTo(getReceiver()) and + exists(string name | name = getMethodName() | name = "filter" or name = "forEach" or name = "map" or name = "some" or name = "every" - | - name = bindingCall.getMethodName() and - 2 = bindingCall.getNumArgument() and - getBinder() = bindingCall.getCallback(0) and - thisSource = bindingCall.getArgument(1) ) } - override AbstractValue getALocalValue() { - result = thisSource.getALocalValue() or - result = AnalyzedNode.super.getALocalValue() + override DataFlow::Node getBoundReceiver(DataFlow::Node callback) { + callback = getArgument(0) and + result = getArgument(1) } } diff --git a/javascript/ql/src/semmle/javascript/Variables.qll b/javascript/ql/src/semmle/javascript/Variables.qll index 2c6e7b32647..df4dccaf987 100644 --- a/javascript/ql/src/semmle/javascript/Variables.qll +++ b/javascript/ql/src/semmle/javascript/Variables.qll @@ -680,7 +680,7 @@ class Parameterized extends @parameterized, Documentable { } /** - * A parameter declaration. + * A parameter declaration in a function or catch clause. * * Examples: * @@ -688,6 +688,9 @@ class Parameterized extends @parameterized, Documentable { * function f(x, { y: z }, ...rest) { // `x`, `{ y: z }` and `rest` are parameter declarations * var [ a, b ] = rest; * var c; + * try { + * x.m(); + * } catch(e) {} // `e` is a parameter declaration * } * ``` */ diff --git a/javascript/ql/src/semmle/javascript/dataflow/Configuration.qll b/javascript/ql/src/semmle/javascript/dataflow/Configuration.qll index f281a8aa23e..f71b6982f1e 100644 --- a/javascript/ql/src/semmle/javascript/dataflow/Configuration.qll +++ b/javascript/ql/src/semmle/javascript/dataflow/Configuration.qll @@ -71,6 +71,7 @@ private import javascript private import internal.FlowSteps private import internal.AccessPaths +private import internal.CallGraphs /** * A data flow tracking configuration for finding inter-procedural paths from @@ -620,10 +621,11 @@ private predicate exploratoryFlowStep( 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 + // the following three disjuncts taken together over-approximate flow through // higher-order calls callback(pred, succ) or - succ = pred.(DataFlow::FunctionNode).getAParameter() + succ = pred.(DataFlow::FunctionNode).getAParameter() or + exploratoryBoundInvokeStep(pred, succ) } /** @@ -751,7 +753,7 @@ private predicate flowThroughCall( ) { exists(Function f, DataFlow::ValueNode ret | ret.asExpr() = f.getAReturnedExpr() and - calls(output, f) and // Do not consider partial calls + (calls(output, f) or callsBound(output, f, _)) and // Do not consider partial calls reachableFromInput(f, output, input, ret, cfg, summary) and not isBarrierEdge(cfg, ret, output) and not isLabeledBarrierEdge(cfg, ret, output, summary.getEndLabel()) and @@ -761,7 +763,7 @@ private predicate flowThroughCall( exists(Function f, DataFlow::Node invk, DataFlow::Node ret | DataFlow::exceptionalFunctionReturnNode(ret, f) and DataFlow::exceptionalInvocationReturnNode(output, invk.asExpr()) and - calls(invk, f) and + (calls(invk, f) or callsBound(invk, f, _)) and reachableFromInput(f, invk, input, ret, cfg, summary) and not isBarrierEdge(cfg, ret, output) and not isLabeledBarrierEdge(cfg, ret, output, summary.getEndLabel()) and @@ -1032,6 +1034,13 @@ private predicate flowIntoHigherOrderCall( succ = cb.getParameter(i) and summary = oldSummary.append(PathSummary::call()) ) + or + exists(DataFlow::SourceNode cb, DataFlow::FunctionNode f, int i, int boundArgs, PathSummary oldSummary | + higherOrderCall(pred, cb, i, cfg, oldSummary) and + cb = CallGraph::getABoundFunctionReference(f, boundArgs, false) and + succ = f.getParameter(boundArgs + i) and + summary = oldSummary.append(PathSummary::call()) + ) } /** @@ -1311,6 +1320,9 @@ class MidPathNode extends PathNode, MkMidNode { or // Skip the exceptional return on functions, as this highlights the entire function. nd = any(DataFlow::FunctionNode f).getExceptionalReturn() + or + // Skip the synthetic 'this' node, as a ThisExpr will be the next node anyway + nd = DataFlow::thisNode(_) } } @@ -1480,3 +1492,18 @@ private class AdditionalBarrierGuardCall extends AdditionalBarrierGuardNode, Dat override predicate appliesTo(Configuration cfg) { f.appliesTo(cfg) } } + +/** + * A guard node for a variable in a negative condition, such as `x` in `if(!x)`. + * Can be added to a `isBarrier` in a data-flow configuration to block flow through such checks. + */ +class VarAccessBarrier extends DataFlow::Node { + VarAccessBarrier() { + exists(ConditionGuardNode guard, SsaRefinementNode refinement | + this = DataFlow::ssaDefinitionNode(refinement) and + refinement.getGuard() = guard and + guard.getTest() instanceof VarAccess and + guard.getOutcome() = false + ) + } +} diff --git a/javascript/ql/src/semmle/javascript/dataflow/DataFlow.qll b/javascript/ql/src/semmle/javascript/dataflow/DataFlow.qll index 5f97f0845bb..e4c1097b877 100644 --- a/javascript/ql/src/semmle/javascript/dataflow/DataFlow.qll +++ b/javascript/ql/src/semmle/javascript/dataflow/DataFlow.qll @@ -147,7 +147,7 @@ module DataFlow { final FunctionNode getABoundFunctionValue(int boundArgs) { result = getAFunctionValue() and boundArgs = 0 or - CallGraph::getABoundFunctionReference(result, boundArgs).flowsTo(this) + CallGraph::getABoundFunctionReference(result, boundArgs, _).flowsTo(this) } /** diff --git a/javascript/ql/src/semmle/javascript/dataflow/Nodes.qll b/javascript/ql/src/semmle/javascript/dataflow/Nodes.qll index 98574813b92..937255e60a9 100644 --- a/javascript/ql/src/semmle/javascript/dataflow/Nodes.qll +++ b/javascript/ql/src/semmle/javascript/dataflow/Nodes.qll @@ -1036,6 +1036,9 @@ module ClassNode { kind = MemberKind::of(method) and result = method.getBody().flow() ) + or + kind = MemberKind::method() and + result = getConstructor().getReceiver().getAPropertySource(name) } override FunctionNode getAnInstanceMember(MemberKind kind) { @@ -1045,6 +1048,9 @@ module ClassNode { kind = MemberKind::of(method) and result = method.getBody().flow() ) + or + kind = MemberKind::method() and + result = getConstructor().getReceiver().getAPropertySource() } override FunctionNode getStaticMethod(string name) { @@ -1063,6 +1069,8 @@ module ClassNode { method.isStatic() and result = method.getBody().flow() ) + or + result = getAPropertySource() } override DataFlow::Node getASuperClassNode() { result = astNode.getSuperClass().flow() } @@ -1199,6 +1207,13 @@ class PartialInvokeNode extends DataFlow::Node { PartialInvokeNode() { this = range } + /** Gets a node holding a callback invoked by this partial invocation node. */ + DataFlow::Node getACallbackNode() { + isPartialArgument(result, _, _) + or + exists(getBoundReceiver(result)) + } + /** * Holds if `argument` is passed as argument `index` to the function in `callback`. */ @@ -1216,7 +1231,12 @@ class PartialInvokeNode extends DataFlow::Node { /** * Gets the node holding the receiver to be passed to the bound function, if specified. */ - DataFlow::Node getBoundReceiver() { result = range.getBoundReceiver() } + DataFlow::Node getBoundReceiver() { result = range.getBoundReceiver(_) } + + /** + * Gets the node holding the receiver to be passed to the bound function, if specified. + */ + DataFlow::Node getBoundReceiver(DataFlow::Node callback) { result = range.getBoundReceiver(callback) } } module PartialInvokeNode { @@ -1235,9 +1255,17 @@ module PartialInvokeNode { DataFlow::SourceNode getBoundFunction(DataFlow::Node callback, int boundArgs) { none() } /** + * DEPRECATED. Use the one-argument version of `getBoundReceiver` instead. + * * Gets the node holding the receiver to be passed to the bound function, if specified. */ + deprecated DataFlow::Node getBoundReceiver() { none() } + + /** + * Gets the node holding the receiver to be passed to `callback`. + */ + DataFlow::Node getBoundReceiver(DataFlow::Node callback) { none() } } /** @@ -1264,7 +1292,8 @@ module PartialInvokeNode { result = this } - override DataFlow::Node getBoundReceiver() { + override DataFlow::Node getBoundReceiver(DataFlow::Node callback) { + callback = getReceiver() and result = getArgument(0) } } @@ -1309,6 +1338,22 @@ module PartialInvokeNode { result = this } } + + /** + * A call to `for-in` or `for-own`, passing the context parameter to the target function. + */ + class ForOwnInPartialCall extends PartialInvokeNode::Range, DataFlow::CallNode { + ForOwnInPartialCall() { + exists(string name | name = "for-in" or name = "for-own" | + this = moduleImport(name).getACall() + ) + } + + override DataFlow::Node getBoundReceiver(DataFlow::Node callback) { + callback = getArgument(1) and + result = getArgument(2) + } + } } /** diff --git a/javascript/ql/src/semmle/javascript/dataflow/TaintTracking.qll b/javascript/ql/src/semmle/javascript/dataflow/TaintTracking.qll index 938870e483b..e5acc6ee5b5 100644 --- a/javascript/ql/src/semmle/javascript/dataflow/TaintTracking.qll +++ b/javascript/ql/src/semmle/javascript/dataflow/TaintTracking.qll @@ -89,7 +89,8 @@ module TaintTracking { final override predicate isBarrier(DataFlow::Node node) { super.isBarrier(node) or - isSanitizer(node) + isSanitizer(node) or + node instanceof DataFlow::VarAccessBarrier } final override predicate isBarrierEdge(DataFlow::Node source, DataFlow::Node sink) { diff --git a/javascript/ql/src/semmle/javascript/dataflow/internal/CallGraphs.qll b/javascript/ql/src/semmle/javascript/dataflow/internal/CallGraphs.qll index d58a83fa0bd..51bd466215e 100644 --- a/javascript/ql/src/semmle/javascript/dataflow/internal/CallGraphs.qll +++ b/javascript/ql/src/semmle/javascript/dataflow/internal/CallGraphs.qll @@ -74,7 +74,7 @@ module CallGraph { */ pragma[nomagic] private - DataFlow::SourceNode getABoundFunctionReference(DataFlow::FunctionNode function, int boundArgs, DataFlow::TypeTracker t) { + DataFlow::SourceNode getABoundFunctionReferenceAux(DataFlow::FunctionNode function, int boundArgs, DataFlow::TypeTracker t) { exists(DataFlow::PartialInvokeNode partial, DataFlow::Node callback | result = partial.getBoundFunction(callback, boundArgs) and getAFunctionReference(function, 0, t.continue()).flowsTo(callback) @@ -90,7 +90,7 @@ module CallGraph { private DataFlow::SourceNode getABoundFunctionReferenceAux(DataFlow::FunctionNode function, int boundArgs, DataFlow::TypeTracker t, DataFlow::StepSummary summary) { exists(DataFlow::SourceNode prev | - prev = getABoundFunctionReference(function, boundArgs, t) and + prev = getABoundFunctionReferenceAux(function, boundArgs, t) and DataFlow::StepSummary::step(prev, result, summary) ) } @@ -100,8 +100,12 @@ module CallGraph { * with `function` as the underlying function. */ cached - DataFlow::SourceNode getABoundFunctionReference(DataFlow::FunctionNode function, int boundArgs) { - result = getABoundFunctionReference(function, boundArgs, DataFlow::TypeTracker::end()) + DataFlow::SourceNode getABoundFunctionReference(DataFlow::FunctionNode function, int boundArgs, boolean contextDependent) { + exists(DataFlow::TypeTracker t | + result = getABoundFunctionReferenceAux(function, boundArgs, t) and + t.end() and + contextDependent = t.hasCall() + ) } /** diff --git a/javascript/ql/src/semmle/javascript/dataflow/internal/FlowSteps.qll b/javascript/ql/src/semmle/javascript/dataflow/internal/FlowSteps.qll index 2ca25905764..d01c8ca9e8a 100644 --- a/javascript/ql/src/semmle/javascript/dataflow/internal/FlowSteps.qll +++ b/javascript/ql/src/semmle/javascript/dataflow/internal/FlowSteps.qll @@ -6,6 +6,7 @@ import javascript import semmle.javascript.dataflow.Configuration +import semmle.javascript.dataflow.internal.CallGraphs /** * Holds if flow should be tracked through properties of `obj`. @@ -91,6 +92,32 @@ private module CachedSteps { cached predicate calls(DataFlow::InvokeNode invk, Function f) { f = invk.getACallee(0) } + /** + * Holds if `invk` may invoke a bound version of `f` with `boundArgs` already bound. + * + * The receiver is assumed to be bound as well, and should not propagate into `f`. + * + * Does not hold for context-dependent call sites, such as callback invocations. + */ + cached + predicate callsBound(DataFlow::InvokeNode invk, Function f, int boundArgs) { + CallGraph::getABoundFunctionReference(f.flow(), boundArgs, false).flowsTo(invk.getCalleeNode()) + } + + /** + * Holds if `pred` may flow to `succ` through an invocation of a bound function. + * + * Should only be used for graph pruning, as the edge may lead to spurious flow. + */ + cached + predicate exploratoryBoundInvokeStep(DataFlow::Node pred, DataFlow::Node succ) { + exists(DataFlow::InvokeNode invk, DataFlow::FunctionNode f, int i, int boundArgs | + CallGraph::getABoundFunctionReference(f, boundArgs, _).flowsTo(invk.getCalleeNode()) and + pred = invk.getArgument(i) and + succ = f.getParameter(i + boundArgs) + ) + } + /** * Holds if `invk` may invoke `f` indirectly through the given `callback` argument. * @@ -99,7 +126,7 @@ private module CachedSteps { private predicate partiallyCalls( DataFlow::PartialInvokeNode invk, DataFlow::AnalyzedNode callback, Function f ) { - invk.isPartialArgument(callback, _, _) and + callback = invk.getACallbackNode() and exists(AbstractFunction callee | callee = callback.getAValue() | if callback.getAValue().isIndefinite("global") then f = callee.getFunction() and f.getFile() = invk.getFile() @@ -135,6 +162,20 @@ private module CachedSteps { not p.isRestParameter() and parm = DataFlow::parameterNode(p) ) + or + exists(DataFlow::Node callback | + arg = invk.(DataFlow::PartialInvokeNode).getBoundReceiver(callback) and + partiallyCalls(invk, callback, f) and + parm = DataFlow::thisNode(f) + ) + or + exists(int boundArgs, int i, Parameter p | + callsBound(invk, f, boundArgs) and + f.getParameter(boundArgs + i) = p and + not p.isRestParameter() and + arg = invk.getArgument(i) and + parm = DataFlow::parameterNode(p) + ) } /** @@ -152,7 +193,7 @@ private module CachedSteps { */ cached predicate returnStep(DataFlow::Node pred, DataFlow::Node succ) { - exists(Function f | calls(succ, f) | + exists(Function f | calls(succ, f) or callsBound(succ, f, _) | returnExpr(f, pred, _) or succ instanceof DataFlow::NewNode and @@ -161,8 +202,11 @@ private module CachedSteps { or exists(InvokeExpr invoke, Function fun | DataFlow::exceptionalFunctionReturnNode(pred, fun) and - DataFlow::exceptionalInvocationReturnNode(succ, invoke) and + DataFlow::exceptionalInvocationReturnNode(succ, invoke) + | calls(invoke.flow(), fun) + or + callsBound(invoke.flow(), fun, _) ) } @@ -458,4 +502,3 @@ module PathSummary { */ PathSummary return() { exists(FlowLabel lbl | result = MkPathSummary(true, false, lbl, lbl)) } } - diff --git a/javascript/ql/src/semmle/javascript/dataflow/internal/InterModuleTypeInference.qll b/javascript/ql/src/semmle/javascript/dataflow/internal/InterModuleTypeInference.qll index 6bc0eac95bc..4d60fdebcfd 100644 --- a/javascript/ql/src/semmle/javascript/dataflow/internal/InterModuleTypeInference.qll +++ b/javascript/ql/src/semmle/javascript/dataflow/internal/InterModuleTypeInference.qll @@ -405,10 +405,16 @@ private class AnalyzedClosureGlobalAccessPath extends AnalyzedNode, AnalyzedProp */ private class AnalyzedExportNamespaceSpecifier extends AnalyzedPropertyWrite, DataFlow::ValueNode { override ExportNamespaceSpecifier astNode; + ReExportDeclaration decl; + + AnalyzedExportNamespaceSpecifier() { + decl = astNode.getExportDeclaration() and + not decl.isTypeOnly() + } override predicate writesValue(AbstractValue baseVal, string propName, AbstractValue value) { baseVal = TAbstractExportsObject(getTopLevel()) and propName = astNode.getExportedName() and - value = TAbstractExportsObject(astNode.getExportDeclaration().(ReExportDeclaration).getReExportedModule()) + value = TAbstractExportsObject(decl.getReExportedModule()) } } diff --git a/javascript/ql/src/semmle/javascript/dataflow/internal/InterProceduralTypeInference.qll b/javascript/ql/src/semmle/javascript/dataflow/internal/InterProceduralTypeInference.qll index d2243aeff7a..ea46bf36668 100644 --- a/javascript/ql/src/semmle/javascript/dataflow/internal/InterProceduralTypeInference.qll +++ b/javascript/ql/src/semmle/javascript/dataflow/internal/InterProceduralTypeInference.qll @@ -274,3 +274,24 @@ private class TypeInferredMethodWithAnalyzedReturnFlow extends CallWithNonLocalA override AnalyzedFunction getACallee() { result = fun } } + +/** + * Propagates receivers into locally defined callbacks of partial invocations. + */ +private class AnalyzedThisInPartialInvokeCallback extends AnalyzedNode, DataFlow::ThisNode { + DataFlow::PartialInvokeNode call; + DataFlow::Node receiver; + + AnalyzedThisInPartialInvokeCallback() { + exists(DataFlow::Node callbackArg | + receiver = call.getBoundReceiver(callbackArg) and + getBinder().flowsTo(callbackArg) + ) + } + + override AbstractValue getALocalValue() { + result = receiver.analyze().getALocalValue() + or + result = AnalyzedNode.super.getALocalValue() + } +} diff --git a/javascript/ql/src/semmle/javascript/frameworks/AngularJS/AngularJSCore.qll b/javascript/ql/src/semmle/javascript/frameworks/AngularJS/AngularJSCore.qll index 45ad2066f2a..6d1aba6bf8e 100644 --- a/javascript/ql/src/semmle/javascript/frameworks/AngularJS/AngularJSCore.qll +++ b/javascript/ql/src/semmle/javascript/frameworks/AngularJS/AngularJSCore.qll @@ -1097,5 +1097,8 @@ private class BindCall extends DataFlow::PartialInvokeNode::Range, DataFlow::Cal result = this } - override DataFlow::Node getBoundReceiver() { result = getArgument(0) } + override DataFlow::Node getBoundReceiver(DataFlow::Node callback) { + callback = getArgument(1) and + result = getArgument(0) + } } diff --git a/javascript/ql/src/semmle/javascript/frameworks/LodashUnderscore.qll b/javascript/ql/src/semmle/javascript/frameworks/LodashUnderscore.qll index b900af99d1f..16c8fd851bf 100644 --- a/javascript/ql/src/semmle/javascript/frameworks/LodashUnderscore.qll +++ b/javascript/ql/src/semmle/javascript/frameworks/LodashUnderscore.qll @@ -407,103 +407,100 @@ module LodashUnderscore { * However, since the function could be invoked in another way, we additionally * still infer the ordinary abstract value. */ -private class AnalyzedThisInBoundCallback extends AnalyzedNode, DataFlow::ThisNode { - AnalyzedNode thisSource; +private class LodashCallbackAsPartialInvoke extends DataFlow::PartialInvokeNode::Range, + DataFlow::CallNode { + int callbackIndex; + int contextIndex; - AnalyzedThisInBoundCallback() { - exists( - DataFlow::CallNode bindingCall, string binderName, int callbackIndex, int contextIndex, - int argumentCount - | - bindingCall = LodashUnderscore::member(binderName).getACall() and - bindingCall.getNumArgument() = argumentCount and - getBinder() = bindingCall.getCallback(callbackIndex) and - thisSource = bindingCall.getArgument(contextIndex) + LodashCallbackAsPartialInvoke() { + exists(string name, int argumentCount | + this = LodashUnderscore::member(name).getACall() and + getNumArgument() = argumentCount | ( - binderName = "bind" or - binderName = "callback" or - binderName = "iteratee" + name = "bind" or + name = "callback" or + name = "iteratee" ) and callbackIndex = 0 and contextIndex = 1 and argumentCount = 2 or ( - binderName = "all" or - binderName = "any" or - binderName = "collect" or - binderName = "countBy" or - binderName = "detect" or - binderName = "dropRightWhile" or - binderName = "dropWhile" or - binderName = "each" or - binderName = "eachRight" or - binderName = "every" or - binderName = "filter" or - binderName = "find" or - binderName = "findIndex" or - binderName = "findKey" or - binderName = "findLast" or - binderName = "findLastIndex" or - binderName = "findLastKey" or - binderName = "forEach" or - binderName = "forEachRight" or - binderName = "forIn" or - binderName = "forInRight" or - binderName = "groupBy" or - binderName = "indexBy" or - binderName = "map" or - binderName = "mapKeys" or - binderName = "mapValues" or - binderName = "max" or - binderName = "min" or - binderName = "omit" or - binderName = "partition" or - binderName = "pick" or - binderName = "reject" or - binderName = "remove" or - binderName = "select" or - binderName = "some" or - binderName = "sortBy" or - binderName = "sum" or - binderName = "takeRightWhile" or - binderName = "takeWhile" or - binderName = "tap" or - binderName = "thru" or - binderName = "times" or - binderName = "unzipWith" or - binderName = "zipWith" + name = "all" or + name = "any" or + name = "collect" or + name = "countBy" or + name = "detect" or + name = "dropRightWhile" or + name = "dropWhile" or + name = "each" or + name = "eachRight" or + name = "every" or + name = "filter" or + name = "find" or + name = "findIndex" or + name = "findKey" or + name = "findLast" or + name = "findLastIndex" or + name = "findLastKey" or + name = "forEach" or + name = "forEachRight" or + name = "forIn" or + name = "forInRight" or + name = "groupBy" or + name = "indexBy" or + name = "map" or + name = "mapKeys" or + name = "mapValues" or + name = "max" or + name = "min" or + name = "omit" or + name = "partition" or + name = "pick" or + name = "reject" or + name = "remove" or + name = "select" or + name = "some" or + name = "sortBy" or + name = "sum" or + name = "takeRightWhile" or + name = "takeWhile" or + name = "tap" or + name = "thru" or + name = "times" or + name = "unzipWith" or + name = "zipWith" ) and callbackIndex = 1 and contextIndex = 2 and argumentCount = 3 or ( - binderName = "foldl" or - binderName = "foldr" or - binderName = "inject" or - binderName = "reduce" or - binderName = "reduceRight" or - binderName = "transform" + name = "foldl" or + name = "foldr" or + name = "inject" or + name = "reduce" or + name = "reduceRight" or + name = "transform" ) and callbackIndex = 1 and contextIndex = 3 and argumentCount = 4 or ( - binderName = "sortedlastIndex" + name = "sortedlastIndex" or - binderName = "assign" + name = "assign" or - binderName = "eq" + name = "eq" or - binderName = "extend" + name = "extend" or - binderName = "merge" + name = "merge" or - binderName = "sortedIndex" and - binderName = "uniq" + name = "sortedIndex" and + name = "uniq" ) and callbackIndex = 2 and contextIndex = 3 and @@ -511,8 +508,8 @@ private class AnalyzedThisInBoundCallback extends AnalyzedNode, DataFlow::ThisNo ) } - override AbstractValue getALocalValue() { - result = thisSource.getALocalValue() or - result = AnalyzedNode.super.getALocalValue() + override DataFlow::Node getBoundReceiver(DataFlow::Node callback) { + callback = getArgument(callbackIndex) and + result = getArgument(contextIndex) } } diff --git a/javascript/ql/src/semmle/javascript/frameworks/React.qll b/javascript/ql/src/semmle/javascript/frameworks/React.qll index a8c1793a5cc..3b593e66478 100644 --- a/javascript/ql/src/semmle/javascript/frameworks/React.qll +++ b/javascript/ql/src/semmle/javascript/frameworks/React.qll @@ -517,32 +517,24 @@ private class FactoryDefinition extends ReactElementDefinition { } /** - * Flow analysis for `this` expressions inside a function that is called with - * `React.Children.map` or a similar library function that binds `this` of a - * callback. - * - * However, since the function could be invoked in another way, we additionally - * still infer the ordinary abstract value. + * Partial invocation for calls to `React.Children.map` or a similar library function + * that binds `this` of a callback. */ -private class AnalyzedThisInBoundCallback extends AnalyzedNode, DataFlow::ThisNode { - AnalyzedNode thisSource; - - AnalyzedThisInBoundCallback() { - exists(DataFlow::CallNode bindingCall, string binderName | +private class ReactCallbackPartialInvoke extends DataFlow::PartialInvokeNode::Range, DataFlow::CallNode { + ReactCallbackPartialInvoke() { + exists(string name | // React.Children.map or React.Children.forEach - binderName = "map" or - binderName = "forEach" + name = "map" or + name = "forEach" | - bindingCall = react().getAPropertyRead("Children").getAMemberCall(binderName) and - 3 = bindingCall.getNumArgument() and - getBinder() = bindingCall.getCallback(1) and - thisSource = bindingCall.getArgument(2) + this = react().getAPropertyRead("Children").getAMemberCall(name) and + 3 = getNumArgument() ) } - override AbstractValue getALocalValue() { - result = thisSource.getALocalValue() or - result = AnalyzedNode.super.getALocalValue() + override DataFlow::Node getBoundReceiver(DataFlow::Node callback) { + callback = getArgument(1) and + result = getArgument(2) } } diff --git a/javascript/ql/src/semmle/javascript/security/dataflow/TaintedPath.qll b/javascript/ql/src/semmle/javascript/security/dataflow/TaintedPath.qll index b46f7d508f7..8dca676af46 100644 --- a/javascript/ql/src/semmle/javascript/security/dataflow/TaintedPath.qll +++ b/javascript/ql/src/semmle/javascript/security/dataflow/TaintedPath.qll @@ -35,7 +35,9 @@ module TaintedPath { guard instanceof StartsWithDotDotSanitizer or guard instanceof StartsWithDirSanitizer or guard instanceof IsAbsoluteSanitizer or - guard instanceof ContainsDotDotSanitizer + guard instanceof ContainsDotDotSanitizer or + guard instanceof RelativePathStartsWithDotDotSanitizer or + guard instanceof IsInsideCheckSanitizer } override predicate isAdditionalFlowStep( @@ -93,13 +95,68 @@ module TaintedPath { | name = argumentlessMethodName ) - or + ) + or + // array method calls of interest + exists(DataFlow::MethodCallNode mcn, string name | dst = mcn and mcn.calls(src, name) | + // A `str.split()` call can either split into path elements (`str.split("/")`) or split by some other string. name = "split" and - not exists(DataFlow::Node splitBy | splitBy = mcn.getArgument(0) | - splitBy.mayHaveStringValue("/") or - any(DataFlow::RegExpLiteralNode reg | reg.getRoot().getAMatchedString() = "/") - .flowsTo(splitBy) + ( + if + exists(DataFlow::Node splitBy | splitBy = mcn.getArgument(0) | + splitBy.mayHaveStringValue("/") or + any(DataFlow::RegExpCreationNode reg | reg.getRoot().getAMatchedString() = "/") + .flowsTo(splitBy) + ) + then + srclabel.(Label::PosixPath).canContainDotDotSlash() and + dstlabel instanceof Label::SplitPath + else srclabel = dstlabel ) + or + ( + name = "pop" or + name = "shift" + ) and + srclabel instanceof Label::SplitPath and + dstlabel.(Label::PosixPath).canContainDotDotSlash() + or + ( + name = "slice" or + name = "splice" or + name = "concat" + ) and + dstlabel instanceof Label::SplitPath and + srclabel instanceof Label::SplitPath + or + name = "join" and + mcn.getArgument(0).mayHaveStringValue("/") and + srclabel instanceof Label::SplitPath and + dstlabel.(Label::PosixPath).canContainDotDotSlash() + ) + or + // prefix.concat(path) + exists(DataFlow::MethodCallNode mcn | + mcn.getMethodName() = "concat" and mcn.getAnArgument() = src + | + dst = mcn and + dstlabel instanceof Label::SplitPath and + srclabel instanceof Label::SplitPath + ) + or + // reading unknown property of split path + exists(DataFlow::PropRead read | read = dst | + src = read.getBase() and + not read.getPropertyName() = "length" and + not exists(read.getPropertyNameExpr().getIntValue()) and + // split[split.length - 1] + not exists(BinaryExpr binop | + read.getPropertyNameExpr() = binop and + binop.getAnOperand().getIntValue() = 1 and + binop.getAnOperand().(PropAccess).getPropertyName() = "length" + ) and + srclabel instanceof Label::SplitPath and + dstlabel.(Label::PosixPath).canContainDotDotSlash() ) } diff --git a/javascript/ql/src/semmle/javascript/security/dataflow/TaintedPathCustomizations.qll b/javascript/ql/src/semmle/javascript/security/dataflow/TaintedPathCustomizations.qll index 25bb232f8fe..a3060a74eb8 100644 --- a/javascript/ql/src/semmle/javascript/security/dataflow/TaintedPathCustomizations.qll +++ b/javascript/ql/src/semmle/javascript/security/dataflow/TaintedPathCustomizations.qll @@ -12,9 +12,7 @@ module TaintedPath { */ abstract class Source extends DataFlow::Node { /** Gets a flow label denoting the type of value for which this is a source. */ - DataFlow::FlowLabel getAFlowLabel() { - result instanceof Label::PosixPath - } + DataFlow::FlowLabel getAFlowLabel() { result instanceof Label::PosixPath } } /** @@ -22,9 +20,7 @@ module TaintedPath { */ abstract class Sink extends DataFlow::Node { /** Gets a flow label denoting the type of value for which this is a sink. */ - DataFlow::FlowLabel getAFlowLabel() { - result instanceof Label::PosixPath - } + DataFlow::FlowLabel getAFlowLabel() { result instanceof Label::PosixPath } } /** @@ -108,6 +104,15 @@ module TaintedPath { not (isNormalized() and isAbsolute()) } } + + /** + * A flow label representing an array of path elements that may include "..". + */ + class SplitPath extends DataFlow::FlowLabel { + SplitPath() { + this = "splitPath" + } + } } /** @@ -355,6 +360,80 @@ module TaintedPath { } } + /** + * A sanitizer that recognizes the following pattern: + * ``` + * var relative = path.relative(webroot, pathname); + * if(relative.startsWith(".." + path.sep) || relative == "..") { + * // pathname is unsafe + * } else { + * // pathname is safe + * } + * ``` + */ + class RelativePathStartsWithDotDotSanitizer extends DataFlow::BarrierGuardNode { + StringOps::StartsWith startsWith; + DataFlow::CallNode relativeCall; + + RelativePathStartsWithDotDotSanitizer() { + this = startsWith and + relativeCall = NodeJSLib::Path::moduleMember("relative").getACall() and + ( + startsWith.getBaseString().getALocalSource() = relativeCall + or + startsWith + .getBaseString() + .getALocalSource() + .(NormalizingPathCall) + .getInput() + .getALocalSource() = relativeCall + ) and + isDotDotSlashPrefix(startsWith.getSubstring()) + } + + override predicate blocks(boolean outcome, Expr e) { + e = relativeCall.getArgument(1).asExpr() and outcome = startsWith.getPolarity().booleanNot() + } + } + + /** + * A guard node for a variable in a negative condition, such as `x` in `if(!x)`. + */ + private class VarAccessBarrier extends Sanitizer, DataFlow::VarAccessBarrier { } + + /** + * An expression of form `isInside(x, y)` or similar, where `isInside` is + * a library check for the relation between `x` and `y`. + */ + class IsInsideCheckSanitizer extends DataFlow::LabeledBarrierGuardNode { + DataFlow::Node checked; + boolean onlyNormalizedAbsolutePaths; + + IsInsideCheckSanitizer() { + exists(string name, DataFlow::CallNode check | + name = "path-is-inside" and onlyNormalizedAbsolutePaths = true + or + name = "is-path-inside" and onlyNormalizedAbsolutePaths = false + | + check = DataFlow::moduleImport(name).getACall() and + checked = check.getArgument(0) and + check = this + ) + } + + override predicate blocks(boolean outcome, Expr e, DataFlow::FlowLabel label) { + ( + onlyNormalizedAbsolutePaths = true and + label.(Label::PosixPath).isNormalized() and + label.(Label::PosixPath).isAbsolute() + or + onlyNormalizedAbsolutePaths = false + ) and + e = checked.asExpr() and + outcome = true + } + } + /** * A source of remote user input, considered as a flow source for * tainted-path vulnerabilities. @@ -396,9 +475,7 @@ module TaintedPath { * A path argument to a file system access, which disallows upward navigation. */ private class FsPathSinkWithoutUpwardNavigation extends FsPathSink { - FsPathSinkWithoutUpwardNavigation() { - fileSystemAccess.isUpwardNavigationRejected(this) - } + FsPathSinkWithoutUpwardNavigation() { fileSystemAccess.isUpwardNavigationRejected(this) } override DataFlow::FlowLabel getAFlowLabel() { // The protection is ineffective if the ../ segments have already diff --git a/javascript/ql/src/semmlecode.javascript.dbscheme b/javascript/ql/src/semmlecode.javascript.dbscheme index dad09eeeff5..03c078a7e6e 100644 --- a/javascript/ql/src/semmlecode.javascript.dbscheme +++ b/javascript/ql/src/semmlecode.javascript.dbscheme @@ -385,6 +385,8 @@ case @expr.kind of @exportspecifier = @namedexportspecifier | @exportdefaultspecifier | @exportnamespacespecifier; +@import_or_export_declaration = @importdeclaration | @exportdeclaration; + @typeassertion = @astypeassertion | @prefixtypeassertion; @classdefinition = @classdeclstmt | @classexpr; @@ -518,6 +520,7 @@ hasPublicKeyword (int id: @property ref); hasPrivateKeyword (int id: @property ref); hasProtectedKeyword (int id: @property ref); hasReadonlyKeyword (int id: @property ref); +hasTypeKeyword (int id: @import_or_export_declaration ref); isOptionalMember (int id: @property ref); hasDefiniteAssignmentAssertion (int id: @field_or_vardeclarator ref); isOptionalParameterDeclaration (unique int parameter: @pattern ref); diff --git a/javascript/ql/src/semmlecode.javascript.dbscheme.stats b/javascript/ql/src/semmlecode.javascript.dbscheme.stats index 1bc81f76f9b..7e352454e59 100644 --- a/javascript/ql/src/semmlecode.javascript.dbscheme.stats +++ b/javascript/ql/src/semmlecode.javascript.dbscheme.stats @@ -7984,6 +7984,17 @@ +hasTypeKeyword +1000 + + +id +1000 + + + + + isOptionalMember 3668 diff --git a/javascript/ql/test/library-tests/CallGraphs/AnnotatedTest/Test.expected b/javascript/ql/test/library-tests/CallGraphs/AnnotatedTest/Test.expected index 7257e488e92..b8e143dc5a6 100644 --- a/javascript/ql/test/library-tests/CallGraphs/AnnotatedTest/Test.expected +++ b/javascript/ql/test/library-tests/CallGraphs/AnnotatedTest/Test.expected @@ -1,5 +1,5 @@ spuriousCallee missingCallee -| constructor-field.ts:40:5:40:14 | f3.build() | constructor-field.ts:13:3:13:12 | build() {} | -| constructor-field.ts:71:1:71:11 | bf3.build() | constructor-field.ts:13:3:13:12 | build() {} | +| constructor-field.ts:40:5:40:14 | f3.build() | constructor-field.ts:13:3:13:12 | build() {} | -1 | +| constructor-field.ts:71:1:71:11 | bf3.build() | constructor-field.ts:13:3:13:12 | build() {} | -1 | badAnnotation diff --git a/javascript/ql/test/library-tests/CallGraphs/AnnotatedTest/Test.ql b/javascript/ql/test/library-tests/CallGraphs/AnnotatedTest/Test.ql index 705ad7eb661..dd41c794213 100644 --- a/javascript/ql/test/library-tests/CallGraphs/AnnotatedTest/Test.ql +++ b/javascript/ql/test/library-tests/CallGraphs/AnnotatedTest/Test.ql @@ -33,16 +33,37 @@ class AnnotatedCall extends InvokeExpr { string getCallTargetName() { result = calls } AnnotatedFunction getAnExpectedCallee() { result.getCalleeName() = getCallTargetName() } + + int getBoundArgs() { + result = getAnnotation(this, "boundArgs").toInt() + } + + int getBoundArgsOrMinusOne() { + result = getBoundArgs() + or + not exists(getBoundArgs()) and + result = -1 + } } -query predicate spuriousCallee(AnnotatedCall call, AnnotatedFunction target) { - FlowSteps::calls(call.flow(), target) and - not target = call.getAnExpectedCallee() +predicate callEdge(AnnotatedCall call, AnnotatedFunction target, int boundArgs) { + FlowSteps::calls(call.flow(), target) and boundArgs = -1 + or + FlowSteps::callsBound(call.flow(), target, boundArgs) } -query predicate missingCallee(AnnotatedCall call, AnnotatedFunction target) { - not FlowSteps::calls(call.flow(), target) and - target = call.getAnExpectedCallee() +query predicate spuriousCallee(AnnotatedCall call, AnnotatedFunction target, int boundArgs) { + callEdge(call, target, boundArgs) and + not ( + target = call.getAnExpectedCallee() and + boundArgs = call.getBoundArgsOrMinusOne() + ) +} + +query predicate missingCallee(AnnotatedCall call, AnnotatedFunction target, int boundArgs) { + not callEdge(call, target, boundArgs) and + target = call.getAnExpectedCallee() and + boundArgs = call.getBoundArgsOrMinusOne() } query predicate badAnnotation(string name) { diff --git a/javascript/ql/test/library-tests/CallGraphs/AnnotatedTest/bound-function.js b/javascript/ql/test/library-tests/CallGraphs/AnnotatedTest/bound-function.js new file mode 100644 index 00000000000..ebce2493639 --- /dev/null +++ b/javascript/ql/test/library-tests/CallGraphs/AnnotatedTest/bound-function.js @@ -0,0 +1,31 @@ +var url = require('url') +var http = require('http') + +function Mount () {} + +/** name:mount.serve */ +Mount.prototype.serve = function (x) { +} + +function makeMount() { + var m = new Mount() + return m.serve.bind(m); +} + +function makeMount2(x) { + var m = new Mount() + return m.serve.bind(m, x); +} + +var mount = makeMount() +var mount2 = makeMount2(null); + +http.createServer(function (req, res) { + /** calls:mount.serve */ + /** boundArgs:0 */ + mount(req, res) + + /** calls:mount.serve */ + /** boundArgs:1 */ + mount2(res) +}); diff --git a/javascript/ql/test/library-tests/ClassNode/InstanceMember.expected b/javascript/ql/test/library-tests/ClassNode/InstanceMember.expected index 1566c4ff9c2..a6faded04a4 100644 --- a/javascript/ql/test/library-tests/ClassNode/InstanceMember.expected +++ b/javascript/ql/test/library-tests/ClassNode/InstanceMember.expected @@ -1,6 +1,8 @@ +| fields.ts:2:16:2:32 | (x: string) => {} | Foo.m | method | | namespace.js:5:32:5:44 | function() {} | Baz.method | method | | tst2.js:6:9:9:3 | () {\\n ... .x;\\n } | C.method | method | | tst2.js:11:13:13:3 | () {\\n ... .x;\\n } | C.getter | getter | +| tst2.js:18:14:18:22 | (x) => {} | D.f | method | | tst.js:4:17:4:21 | () {} | A.instanceMethod | method | | tst.js:7:6:7:10 | () {} | A.bar | method | | tst.js:9:10:9:14 | () {} | A.baz | getter | diff --git a/javascript/ql/test/library-tests/ClassNode/InstanceMethod.expected b/javascript/ql/test/library-tests/ClassNode/InstanceMethod.expected index d0406361e6b..c5b94a35a3c 100644 --- a/javascript/ql/test/library-tests/ClassNode/InstanceMethod.expected +++ b/javascript/ql/test/library-tests/ClassNode/InstanceMethod.expected @@ -1,5 +1,7 @@ +| fields.ts:2:16:2:32 | (x: string) => {} | Foo.m | | namespace.js:5:32:5:44 | function() {} | Baz.method | | tst2.js:6:9:9:3 | () {\\n ... .x;\\n } | C.method | +| tst2.js:18:14:18:22 | (x) => {} | D.f | | tst.js:4:17:4:21 | () {} | A.instanceMethod | | tst.js:7:6:7:10 | () {} | A.bar | | tst.js:17:19:17:31 | function() {} | B.foo | diff --git a/javascript/ql/test/library-tests/ClassNode/fields.ts b/javascript/ql/test/library-tests/ClassNode/fields.ts new file mode 100644 index 00000000000..f288a47bb4d --- /dev/null +++ b/javascript/ql/test/library-tests/ClassNode/fields.ts @@ -0,0 +1,3 @@ +class Foo { + public m = (x: string) => {}; +} diff --git a/javascript/ql/test/library-tests/ClassNode/getAReceiverNode.expected b/javascript/ql/test/library-tests/ClassNode/getAReceiverNode.expected index c17b1db03a4..f8f057d646f 100644 --- a/javascript/ql/test/library-tests/ClassNode/getAReceiverNode.expected +++ b/javascript/ql/test/library-tests/ClassNode/getAReceiverNode.expected @@ -1,8 +1,10 @@ +| fields.ts:1:1:3:1 | class F ... > {};\\n} | fields.ts:1:11:1:10 | this | | namespace.js:3:15:3:31 | function Baz() {} | namespace.js:3:15:3:14 | this | | namespace.js:3:15:3:31 | function Baz() {} | namespace.js:5:32:5:31 | this | | tst2.js:1:1:14:1 | class C ... ;\\n }\\n} | tst2.js:2:14:2:13 | this | | tst2.js:1:1:14:1 | class C ... ;\\n }\\n} | tst2.js:6:9:6:8 | this | | tst2.js:1:1:14:1 | class C ... ;\\n }\\n} | tst2.js:11:13:11:12 | this | +| tst2.js:16:1:20:1 | class D ... ;\\n }\\n} | tst2.js:17:14:17:13 | this | | tst.js:3:1:10:1 | class A ... () {}\\n} | tst.js:3:9:3:8 | this | | tst.js:3:1:10:1 | class A ... () {}\\n} | tst.js:4:17:4:16 | this | | tst.js:3:1:10:1 | class A ... () {}\\n} | tst.js:7:6:7:5 | this | diff --git a/javascript/ql/test/library-tests/ClassNode/tst2.js b/javascript/ql/test/library-tests/ClassNode/tst2.js index 88746d8fcd4..493fe027949 100644 --- a/javascript/ql/test/library-tests/ClassNode/tst2.js +++ b/javascript/ql/test/library-tests/ClassNode/tst2.js @@ -12,3 +12,9 @@ class C { return this.x; } } + +class D { + constructor() { + this.f = (x) => {}; + } +} diff --git a/javascript/ql/test/library-tests/InterProceduralFlow/DataFlow.expected b/javascript/ql/test/library-tests/InterProceduralFlow/DataFlow.expected index 24e5f967f9f..a7fa3a0de3b 100644 --- a/javascript/ql/test/library-tests/InterProceduralFlow/DataFlow.expected +++ b/javascript/ql/test/library-tests/InterProceduralFlow/DataFlow.expected @@ -22,6 +22,10 @@ | partial.js:5:15:5:24 | "tainted1" | partial.js:15:15:15:15 | x | | 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 | +| partial.js:6:15:6:24 | "tainted2" | partial.js:10:15:10:15 | y | +| partial.js:6:15:6:24 | "tainted2" | partial.js:16:15:16:15 | y | +| partial.js:6:15:6:24 | "tainted2" | partial.js:22:15:22:15 | y | +| partial.js:6:15:6:24 | "tainted2" | partial.js:28:15:28:15 | y | | 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 | diff --git a/javascript/ql/test/library-tests/InterProceduralFlow/GermanFlow.expected b/javascript/ql/test/library-tests/InterProceduralFlow/GermanFlow.expected index 9c3980797ea..377eec8c75a 100644 --- a/javascript/ql/test/library-tests/InterProceduralFlow/GermanFlow.expected +++ b/javascript/ql/test/library-tests/InterProceduralFlow/GermanFlow.expected @@ -23,6 +23,10 @@ | partial.js:5:15:5:24 | "tainted1" | partial.js:15:15:15:15 | x | | 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 | +| partial.js:6:15:6:24 | "tainted2" | partial.js:10:15:10:15 | y | +| partial.js:6:15:6:24 | "tainted2" | partial.js:16:15:16:15 | y | +| partial.js:6:15:6:24 | "tainted2" | partial.js:22:15:22:15 | y | +| partial.js:6:15:6:24 | "tainted2" | partial.js:28:15:28:15 | y | | 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 | diff --git a/javascript/ql/test/library-tests/InterProceduralFlow/TaintTracking.expected b/javascript/ql/test/library-tests/InterProceduralFlow/TaintTracking.expected index 5c252b4aeea..b6122f6e2a4 100644 --- a/javascript/ql/test/library-tests/InterProceduralFlow/TaintTracking.expected +++ b/javascript/ql/test/library-tests/InterProceduralFlow/TaintTracking.expected @@ -27,6 +27,10 @@ | partial.js:5:15:5:24 | "tainted1" | partial.js:15:15:15:15 | x | | 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 | +| partial.js:6:15:6:24 | "tainted2" | partial.js:10:15:10:15 | y | +| partial.js:6:15:6:24 | "tainted2" | partial.js:16:15:16:15 | y | +| partial.js:6:15:6:24 | "tainted2" | partial.js:22:15:22:15 | y | +| partial.js:6:15:6:24 | "tainted2" | partial.js:28:15:28:15 | y | | 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 | diff --git a/javascript/ql/test/library-tests/RegExp/Bounds/Bounds.expected b/javascript/ql/test/library-tests/RegExp/Bounds/Bounds.expected new file mode 100644 index 00000000000..44a0433869a --- /dev/null +++ b/javascript/ql/test/library-tests/RegExp/Bounds/Bounds.expected @@ -0,0 +1,4 @@ +| tst.js:1:2:1:5 | a{1} | 1 | 1 | +| tst.js:2:2:2:6 | a{1,} | 1 | | +| tst.js:3:2:3:7 | a{1,5} | 1 | 5 | +| tst.js:4:2:4:6 | a{,5} | 0 | 5 | diff --git a/javascript/ql/test/library-tests/RegExp/Bounds/Bounds.ql b/javascript/ql/test/library-tests/RegExp/Bounds/Bounds.ql new file mode 100644 index 00000000000..2e31a8ab3ce --- /dev/null +++ b/javascript/ql/test/library-tests/RegExp/Bounds/Bounds.ql @@ -0,0 +1,7 @@ +import javascript + +from RegExpRange rr, string lb, string ub +where + lb = rr.getLowerBound().toString() and + if exists(rr.getUpperBound()) then ub = rr.getUpperBound().toString() else ub = "" +select rr, lb, ub diff --git a/javascript/ql/test/library-tests/RegExp/Bounds/tst.js b/javascript/ql/test/library-tests/RegExp/Bounds/tst.js new file mode 100644 index 00000000000..4a92bc55edb --- /dev/null +++ b/javascript/ql/test/library-tests/RegExp/Bounds/tst.js @@ -0,0 +1,4 @@ +/a{1}/; +/a{1,}/; +/a{1,5}/; +/a{,5}/; diff --git a/javascript/ql/test/library-tests/RegExp/predecessors_and_successors/getPredecessor.expected b/javascript/ql/test/library-tests/RegExp/predecessors_and_successors/getPredecessor.expected new file mode 100644 index 00000000000..d2414fd2500 --- /dev/null +++ b/javascript/ql/test/library-tests/RegExp/predecessors_and_successors/getPredecessor.expected @@ -0,0 +1,38 @@ +| tst.js:2:2:2:2 | a | tst.js:2:3:2:5 | (b) | +| tst.js:2:2:2:2 | a | tst.js:2:4:2:4 | b | +| tst.js:3:2:3:3 | ab | tst.js:3:4:3:6 | (c) | +| tst.js:3:2:3:3 | ab | tst.js:3:5:3:5 | c | +| tst.js:4:2:4:2 | a | tst.js:4:3:4:6 | (bc) | +| tst.js:4:2:4:2 | a | tst.js:4:4:4:5 | bc | +| tst.js:5:2:5:2 | a | tst.js:5:3:5:8 | (b[c]) | +| tst.js:5:2:5:2 | a | tst.js:5:4:5:4 | b | +| tst.js:5:2:5:2 | a | tst.js:5:4:5:7 | b[c] | +| tst.js:5:4:5:4 | b | tst.js:5:5:5:7 | [c] | +| tst.js:5:4:5:4 | b | tst.js:5:6:5:6 | c | +| tst.js:6:2:6:2 | a | tst.js:6:3:6:9 | (b\|[b]) | +| tst.js:6:2:6:2 | a | tst.js:6:4:6:4 | b | +| tst.js:6:2:6:2 | a | tst.js:6:4:6:8 | b\|[b] | +| tst.js:6:2:6:2 | a | tst.js:6:6:6:8 | [b] | +| tst.js:6:2:6:2 | a | tst.js:6:7:6:7 | b | +| tst.js:7:2:7:2 | a | tst.js:7:3:7:10 | ([b][c]) | +| tst.js:7:2:7:2 | a | tst.js:7:4:7:6 | [b] | +| tst.js:7:2:7:2 | a | tst.js:7:4:7:9 | [b][c] | +| tst.js:7:2:7:2 | a | tst.js:7:5:7:5 | b | +| tst.js:7:4:7:6 | [b] | tst.js:7:7:7:9 | [c] | +| tst.js:7:4:7:6 | [b] | tst.js:7:8:7:8 | c | +| tst.js:8:2:8:2 | a | tst.js:8:3:8:13 | (b\|[b]\|[b]) | +| tst.js:8:2:8:2 | a | tst.js:8:4:8:4 | b | +| tst.js:8:2:8:2 | a | tst.js:8:4:8:12 | b\|[b]\|[b] | +| tst.js:8:2:8:2 | a | tst.js:8:6:8:8 | [b] | +| tst.js:8:2:8:2 | a | tst.js:8:7:8:7 | b | +| tst.js:8:2:8:2 | a | tst.js:8:10:8:12 | [b] | +| tst.js:8:2:8:2 | a | tst.js:8:11:8:11 | b | +| tst.js:11:2:11:4 | (a) | tst.js:11:5:11:5 | b | +| tst.js:12:2:12:4 | (a) | tst.js:12:5:12:6 | bc | +| tst.js:13:2:13:5 | (ab) | tst.js:13:6:13:6 | c | +| tst.js:14:2:14:7 | ([a]b) | tst.js:14:8:14:8 | c | +| tst.js:14:3:14:5 | [a] | tst.js:14:6:14:6 | b | +| tst.js:16:2:16:9 | ([a][b]) | tst.js:16:10:16:10 | c | +| tst.js:16:3:16:5 | [a] | tst.js:16:6:16:8 | [b] | +| tst.js:16:3:16:5 | [a] | tst.js:16:7:16:7 | b | +| tst.js:17:2:17:12 | ([a]\|[a]\|a) | tst.js:17:13:17:13 | b | diff --git a/javascript/ql/test/library-tests/RegExp/predecessors_and_successors/getPredecessor.ql b/javascript/ql/test/library-tests/RegExp/predecessors_and_successors/getPredecessor.ql new file mode 100644 index 00000000000..c356564dc7f --- /dev/null +++ b/javascript/ql/test/library-tests/RegExp/predecessors_and_successors/getPredecessor.ql @@ -0,0 +1,5 @@ +import javascript + +from RegExpTerm pred, RegExpTerm succ +where pred = succ.getPredecessor() +select pred, succ \ No newline at end of file diff --git a/javascript/ql/test/library-tests/RegExp/predecessors_and_successors/getSuccessor.expected b/javascript/ql/test/library-tests/RegExp/predecessors_and_successors/getSuccessor.expected new file mode 100644 index 00000000000..e8550156fb3 --- /dev/null +++ b/javascript/ql/test/library-tests/RegExp/predecessors_and_successors/getSuccessor.expected @@ -0,0 +1,34 @@ +| tst.js:2:2:2:2 | a | tst.js:2:3:2:5 | (b) | +| tst.js:3:2:3:3 | ab | tst.js:3:4:3:6 | (c) | +| tst.js:4:2:4:2 | a | tst.js:4:3:4:6 | (bc) | +| tst.js:5:2:5:2 | a | tst.js:5:3:5:8 | (b[c]) | +| tst.js:5:4:5:4 | b | tst.js:5:5:5:7 | [c] | +| tst.js:6:2:6:2 | a | tst.js:6:3:6:9 | (b\|[b]) | +| tst.js:7:2:7:2 | a | tst.js:7:3:7:10 | ([b][c]) | +| tst.js:7:4:7:6 | [b] | tst.js:7:7:7:9 | [c] | +| tst.js:7:5:7:5 | b | tst.js:7:7:7:9 | [c] | +| tst.js:8:2:8:2 | a | tst.js:8:3:8:13 | (b\|[b]\|[b]) | +| tst.js:11:2:11:4 | (a) | tst.js:11:5:11:5 | b | +| tst.js:11:3:11:3 | a | tst.js:11:5:11:5 | b | +| tst.js:12:2:12:4 | (a) | tst.js:12:5:12:6 | bc | +| tst.js:12:3:12:3 | a | tst.js:12:5:12:6 | bc | +| tst.js:13:2:13:5 | (ab) | tst.js:13:6:13:6 | c | +| tst.js:13:3:13:4 | ab | tst.js:13:6:13:6 | c | +| tst.js:14:2:14:7 | ([a]b) | tst.js:14:8:14:8 | c | +| tst.js:14:3:14:5 | [a] | tst.js:14:6:14:6 | b | +| tst.js:14:3:14:6 | [a]b | tst.js:14:8:14:8 | c | +| tst.js:14:4:14:4 | a | tst.js:14:6:14:6 | b | +| tst.js:14:6:14:6 | b | tst.js:14:8:14:8 | c | +| tst.js:16:2:16:9 | ([a][b]) | tst.js:16:10:16:10 | c | +| tst.js:16:3:16:5 | [a] | tst.js:16:6:16:8 | [b] | +| tst.js:16:3:16:8 | [a][b] | tst.js:16:10:16:10 | c | +| tst.js:16:4:16:4 | a | tst.js:16:6:16:8 | [b] | +| tst.js:16:6:16:8 | [b] | tst.js:16:10:16:10 | c | +| tst.js:16:7:16:7 | b | tst.js:16:10:16:10 | c | +| tst.js:17:2:17:12 | ([a]\|[a]\|a) | tst.js:17:13:17:13 | b | +| tst.js:17:3:17:5 | [a] | tst.js:17:13:17:13 | b | +| tst.js:17:3:17:11 | [a]\|[a]\|a | tst.js:17:13:17:13 | b | +| tst.js:17:4:17:4 | a | tst.js:17:13:17:13 | b | +| tst.js:17:7:17:9 | [a] | tst.js:17:13:17:13 | b | +| tst.js:17:8:17:8 | a | tst.js:17:13:17:13 | b | +| tst.js:17:11:17:11 | a | tst.js:17:13:17:13 | b | diff --git a/javascript/ql/test/library-tests/RegExp/predecessors_and_successors/getSuccessor.ql b/javascript/ql/test/library-tests/RegExp/predecessors_and_successors/getSuccessor.ql new file mode 100644 index 00000000000..49916f8c426 --- /dev/null +++ b/javascript/ql/test/library-tests/RegExp/predecessors_and_successors/getSuccessor.ql @@ -0,0 +1,5 @@ +import javascript + +from RegExpTerm pred, RegExpTerm succ +where succ = pred.getSuccessor() +select pred, succ \ No newline at end of file diff --git a/javascript/ql/test/library-tests/RegExp/predecessors_and_successors/tst.js b/javascript/ql/test/library-tests/RegExp/predecessors_and_successors/tst.js new file mode 100644 index 00000000000..25924a257d3 --- /dev/null +++ b/javascript/ql/test/library-tests/RegExp/predecessors_and_successors/tst.js @@ -0,0 +1,17 @@ +/ab/; +/a(b)/; +/ab(c)/; +/a(bc)/; +/a(b[c])/; +/a(b|[b])/; +/a([b][c])/; +/a(b|[b]|[b])/; + +/ab/; +/(a)b/; +/(a)bc/; +/(ab)c/; +/([a]b)c/; +/([a]|a)|b/; +/([a][b])c/; +/([a]|[a]|a)b/; diff --git a/javascript/ql/test/library-tests/TaintTracking/BasicTaintTracking.expected b/javascript/ql/test/library-tests/TaintTracking/BasicTaintTracking.expected index 22c9a6c4576..53dbe8d1b53 100644 --- a/javascript/ql/test/library-tests/TaintTracking/BasicTaintTracking.expected +++ b/javascript/ql/test/library-tests/TaintTracking/BasicTaintTracking.expected @@ -15,6 +15,12 @@ typeInferenceMismatch | booleanOps.js:2:11:2:18 | source() | booleanOps.js:13:10:13:10 | x | | booleanOps.js:2:11:2:18 | source() | booleanOps.js:19:10:19:10 | x | | booleanOps.js:2:11:2:18 | source() | booleanOps.js:22:10:22:10 | x | +| bound-function.js:12:12:12:19 | source() | bound-function.js:4:10:4:10 | y | +| bound-function.js:14:6:14:13 | source() | bound-function.js:4:10:4:10 | y | +| bound-function.js:22:8:22:15 | source() | bound-function.js:25:10:25:10 | y | +| bound-function.js:45:10:45:17 | source() | bound-function.js:45:6:45:18 | id3(source()) | +| bound-function.js:49:12:49:19 | source() | bound-function.js:54:6:54:14 | source0() | +| bound-function.js:49:12:49:19 | source() | bound-function.js:55:6:55:14 | source1() | | callbacks.js:4:6:4:13 | source() | callbacks.js:34:27:34:27 | x | | callbacks.js:4:6:4:13 | source() | callbacks.js:35:27:35:27 | x | | callbacks.js:5:6:5:13 | source() | callbacks.js:34:27:34:27 | x | @@ -79,6 +85,8 @@ typeInferenceMismatch | sanitizer-guards.js:43:11:43:18 | source() | sanitizer-guards.js:45:8:45:8 | x | | sanitizer-guards.js:43:11:43:18 | source() | sanitizer-guards.js:48:10:48:10 | x | | sanitizer-guards.js:68:11:68:18 | source() | sanitizer-guards.js:75:8:75:8 | x | +| sanitizer-guards.js:79:11:79:18 | source() | sanitizer-guards.js:81:8:81:8 | x | +| sanitizer-guards.js:79:11:79:18 | source() | sanitizer-guards.js:84:10:84:10 | x | | spread.js:2:15:2:22 | source() | spread.js:4:8:4:19 | { ...taint } | | spread.js:2:15:2:22 | source() | spread.js:5:8:5:43 | { f: 'h ... orld' } | | spread.js:2:15:2:22 | source() | spread.js:7:8:7:19 | [ ...taint ] | diff --git a/javascript/ql/test/library-tests/TaintTracking/DataFlowTracking.expected b/javascript/ql/test/library-tests/TaintTracking/DataFlowTracking.expected index 9dd83d53ad1..127d58056ae 100644 --- a/javascript/ql/test/library-tests/TaintTracking/DataFlowTracking.expected +++ b/javascript/ql/test/library-tests/TaintTracking/DataFlowTracking.expected @@ -6,6 +6,12 @@ | booleanOps.js:2:11:2:18 | source() | booleanOps.js:13:10:13:10 | x | | booleanOps.js:2:11:2:18 | source() | booleanOps.js:19:10:19:10 | x | | booleanOps.js:2:11:2:18 | source() | booleanOps.js:22:10:22:10 | x | +| bound-function.js:12:12:12:19 | source() | bound-function.js:4:10:4:10 | y | +| bound-function.js:14:6:14:13 | source() | bound-function.js:4:10:4:10 | y | +| bound-function.js:22:8:22:15 | source() | bound-function.js:25:10:25:10 | y | +| bound-function.js:45:10:45:17 | source() | bound-function.js:45:6:45:18 | id3(source()) | +| bound-function.js:49:12:49:19 | source() | bound-function.js:54:6:54:14 | source0() | +| bound-function.js:49:12:49:19 | source() | bound-function.js:55:6:55:14 | source1() | | callbacks.js:4:6:4:13 | source() | callbacks.js:34:27:34:27 | x | | callbacks.js:4:6:4:13 | source() | callbacks.js:35:27:35:27 | x | | callbacks.js:5:6:5:13 | source() | callbacks.js:34:27:34:27 | x | @@ -54,6 +60,9 @@ | sanitizer-guards.js:43:11:43:18 | source() | sanitizer-guards.js:48:10:48:10 | x | | sanitizer-guards.js:43:11:43:18 | source() | sanitizer-guards.js:52:10:52:10 | x | | sanitizer-guards.js:68:11:68:18 | source() | sanitizer-guards.js:75:8:75:8 | x | +| sanitizer-guards.js:79:11:79:18 | source() | sanitizer-guards.js:81:8:81:8 | x | +| sanitizer-guards.js:79:11:79:18 | source() | sanitizer-guards.js:84:10:84:10 | x | +| sanitizer-guards.js:79:11:79:18 | source() | sanitizer-guards.js:86:7:86:7 | x | | thisAssignments.js:4:17:4:24 | source() | thisAssignments.js:5:10:5:18 | obj.field | | thisAssignments.js:7:19:7:26 | source() | thisAssignments.js:8:10:8:20 | this.field2 | | tst.js:2:13:2:20 | source() | tst.js:4:10:4:10 | x | diff --git a/javascript/ql/test/library-tests/TaintTracking/bound-function.js b/javascript/ql/test/library-tests/TaintTracking/bound-function.js new file mode 100644 index 00000000000..b38dee1c922 --- /dev/null +++ b/javascript/ql/test/library-tests/TaintTracking/bound-function.js @@ -0,0 +1,55 @@ +import * as dummy from 'dummy'; + +function foo(x, y) { + sink(y); +} + +let foo0 = foo.bind(null); +let foo1 = foo.bind(null, null); +let foo2 = foo.bind(null, null, null); + +foo0(source(), null); // OK +foo0(null, source()); // NOT OK + +foo1(source()); // NOT OK +foo1(null, source()); // OK + +foo2(source()); // OK +foo2(null, source()); // OK + + +function takesCallback(cb) { + cb(source()); // NOT OK +} +function callback(x, y) { + sink(y); +} +takesCallback(callback.bind(null, null)); + +function id(x) { + return x; +} + +let sourceGetter = id.bind(null, source()); +let constGetter = id.bind(null, 'safe'); + +sink(sourceGetter()); // NOT OK - but not flagged +sink(constGetter()); // OK + +function id2(x, y) { + return y; +} + +let id3 = id2.bind(null, null); + +sink(id3(source())); // NOT OK +sink(id3('safe')); // OK + +function getSource() { + return source(); +} +let source0 = getSource.bind(null); +let source1 = getSource.bind(null, null); + +sink(source0()); // NOT OK +sink(source1()); // NOT OK diff --git a/javascript/ql/test/library-tests/TaintTracking/sanitizer-guards.js b/javascript/ql/test/library-tests/TaintTracking/sanitizer-guards.js index 8549776d5dc..497271d989e 100644 --- a/javascript/ql/test/library-tests/TaintTracking/sanitizer-guards.js +++ b/javascript/ql/test/library-tests/TaintTracking/sanitizer-guards.js @@ -74,3 +74,15 @@ function phi2() { } sink(x); // NOT OK } + +function falsy() { + let x = source(); + + sink(x); // NOT OK + + if (x) { + sink(x); // OK (for taint-tracking) + } else { + sink(x); // NOT OK + } +} diff --git a/javascript/ql/test/library-tests/TypeScript/ExportNamespaceSpecifier/client.ts b/javascript/ql/test/library-tests/TypeScript/ExportNamespaceSpecifier/client.ts new file mode 100644 index 00000000000..b4004f7005b --- /dev/null +++ b/javascript/ql/test/library-tests/TypeScript/ExportNamespaceSpecifier/client.ts @@ -0,0 +1,3 @@ +import { ns } from "./reexport"; + +ns.foo(); diff --git a/javascript/ql/test/library-tests/TypeScript/ExportNamespaceSpecifier/lib.ts b/javascript/ql/test/library-tests/TypeScript/ExportNamespaceSpecifier/lib.ts new file mode 100644 index 00000000000..f99d4277774 --- /dev/null +++ b/javascript/ql/test/library-tests/TypeScript/ExportNamespaceSpecifier/lib.ts @@ -0,0 +1 @@ +export function foo() {} diff --git a/javascript/ql/test/library-tests/TypeScript/ExportNamespaceSpecifier/reexport.ts b/javascript/ql/test/library-tests/TypeScript/ExportNamespaceSpecifier/reexport.ts new file mode 100644 index 00000000000..19c4b621498 --- /dev/null +++ b/javascript/ql/test/library-tests/TypeScript/ExportNamespaceSpecifier/reexport.ts @@ -0,0 +1 @@ +export * as ns from "./lib"; diff --git a/javascript/ql/test/library-tests/TypeScript/ExportNamespaceSpecifier/test.expected b/javascript/ql/test/library-tests/TypeScript/ExportNamespaceSpecifier/test.expected new file mode 100644 index 00000000000..ca8914da44f --- /dev/null +++ b/javascript/ql/test/library-tests/TypeScript/ExportNamespaceSpecifier/test.expected @@ -0,0 +1 @@ +| reexport.ts:1:8:1:14 | * as ns | diff --git a/javascript/ql/test/library-tests/TypeScript/ExportNamespaceSpecifier/test.ql b/javascript/ql/test/library-tests/TypeScript/ExportNamespaceSpecifier/test.ql new file mode 100644 index 00000000000..44f3f9d7fd4 --- /dev/null +++ b/javascript/ql/test/library-tests/TypeScript/ExportNamespaceSpecifier/test.ql @@ -0,0 +1,3 @@ +import javascript + +query ExportNamespaceSpecifier test_ExportNamespaceSpecifier() { any() } diff --git a/javascript/ql/test/library-tests/TypeScript/PrivateFields/test.expected b/javascript/ql/test/library-tests/TypeScript/PrivateFields/test.expected new file mode 100644 index 00000000000..fc28ada4ff9 --- /dev/null +++ b/javascript/ql/test/library-tests/TypeScript/PrivateFields/test.expected @@ -0,0 +1,5 @@ +propAccess +| #privateField | tst.ts:5:9:5:26 | this.#privateField | +| #privateField | tst.ts:6:9:6:26 | this.#privateField | +fieldDecl +| #privateField | tst.ts:2:5:2:23 | #privateField: any; | diff --git a/javascript/ql/test/library-tests/TypeScript/PrivateFields/test.ql b/javascript/ql/test/library-tests/TypeScript/PrivateFields/test.ql new file mode 100644 index 00000000000..27147fcb461 --- /dev/null +++ b/javascript/ql/test/library-tests/TypeScript/PrivateFields/test.ql @@ -0,0 +1,9 @@ +import javascript + +query PropAccess propAccess(string name) { + result.getPropertyName() = name +} + +query FieldDeclaration fieldDecl(string name) { + result.getName() = name +} diff --git a/javascript/ql/test/library-tests/TypeScript/PrivateFields/tst.ts b/javascript/ql/test/library-tests/TypeScript/PrivateFields/tst.ts new file mode 100644 index 00000000000..9e2dbdca644 --- /dev/null +++ b/javascript/ql/test/library-tests/TypeScript/PrivateFields/tst.ts @@ -0,0 +1,8 @@ +class C { + #privateField: any; + + constructor(x: any) { + this.#privateField = x; + this.#privateField(y); + } +} diff --git a/javascript/ql/test/library-tests/TypeScript/TypeOnlyImportExport/exportClass.ts b/javascript/ql/test/library-tests/TypeScript/TypeOnlyImportExport/exportClass.ts new file mode 100644 index 00000000000..1ec0ebf40c5 --- /dev/null +++ b/javascript/ql/test/library-tests/TypeScript/TypeOnlyImportExport/exportClass.ts @@ -0,0 +1 @@ +export class C {} diff --git a/javascript/ql/test/library-tests/TypeScript/TypeOnlyImportExport/exportFunction.ts b/javascript/ql/test/library-tests/TypeScript/TypeOnlyImportExport/exportFunction.ts new file mode 100644 index 00000000000..8b38a11158b --- /dev/null +++ b/javascript/ql/test/library-tests/TypeScript/TypeOnlyImportExport/exportFunction.ts @@ -0,0 +1 @@ +export function f() {} diff --git a/javascript/ql/test/library-tests/TypeScript/TypeOnlyImportExport/importFunction.ts b/javascript/ql/test/library-tests/TypeScript/TypeOnlyImportExport/importFunction.ts new file mode 100644 index 00000000000..1034558749c --- /dev/null +++ b/javascript/ql/test/library-tests/TypeScript/TypeOnlyImportExport/importFunction.ts @@ -0,0 +1,3 @@ +import { ns } from "./importType"; + +ns.f(); // Calls local method in 'importType' diff --git a/javascript/ql/test/library-tests/TypeScript/TypeOnlyImportExport/importRexportedClass.ts b/javascript/ql/test/library-tests/TypeScript/TypeOnlyImportExport/importRexportedClass.ts new file mode 100644 index 00000000000..92b60b44ccf --- /dev/null +++ b/javascript/ql/test/library-tests/TypeScript/TypeOnlyImportExport/importRexportedClass.ts @@ -0,0 +1,3 @@ +import type { C } from "./namedReexport"; + +let c: C; diff --git a/javascript/ql/test/library-tests/TypeScript/TypeOnlyImportExport/importType.ts b/javascript/ql/test/library-tests/TypeScript/TypeOnlyImportExport/importType.ts new file mode 100644 index 00000000000..090e94bc597 --- /dev/null +++ b/javascript/ql/test/library-tests/TypeScript/TypeOnlyImportExport/importType.ts @@ -0,0 +1,3 @@ +export type * as ns from "./exportFunction"; + +export var ns = { f() {} }; diff --git a/javascript/ql/test/library-tests/TypeScript/TypeOnlyImportExport/namedReexport.ts b/javascript/ql/test/library-tests/TypeScript/TypeOnlyImportExport/namedReexport.ts new file mode 100644 index 00000000000..6fd9a22c067 --- /dev/null +++ b/javascript/ql/test/library-tests/TypeScript/TypeOnlyImportExport/namedReexport.ts @@ -0,0 +1 @@ +export type { C } from "./exportClass"; diff --git a/javascript/ql/test/library-tests/TypeScript/TypeOnlyImportExport/test.expected b/javascript/ql/test/library-tests/TypeScript/TypeOnlyImportExport/test.expected new file mode 100644 index 00000000000..083b5a90be6 --- /dev/null +++ b/javascript/ql/test/library-tests/TypeScript/TypeOnlyImportExport/test.expected @@ -0,0 +1,26 @@ +getAVarReference +| C | exportClass.ts:1:14:1:14 | C | +| Foo | tst.ts:5:5:5:7 | Foo | +| c | importRexportedClass.ts:3:5:3:5 | c | +| f | exportFunction.ts:1:17:1:17 | f | +| ns | importFunction.ts:1:10:1:11 | ns | +| ns | importFunction.ts:3:1:3:2 | ns | +| ns | importType.ts:3:12:3:13 | ns | +getAnExportAccess +| Foo | tst.ts:3:15:3:17 | Foo | +getATypeDecl +| C | exportClass.ts:1:14:1:14 | C | +| C | importRexportedClass.ts:1:15:1:15 | C | +| Foo | tst.ts:1:15:1:17 | Foo | +| ns | importFunction.ts:1:10:1:11 | ns | +| types | tst.ts:7:18:7:22 | types | +calls +| importFunction.ts:3:1:3:6 | ns.f() | importType.ts:3:19:3:24 | f() {} | +exportsAs +| exportClass.ts:1:1:1:17 | export class C {} | C | C | type | +| exportClass.ts:1:1:1:17 | export class C {} | C | C | variable | +| exportFunction.ts:1:1:1:22 | export ... f() {} | f | f | variable | +| importType.ts:3:1:3:27 | export ... ) {} }; | ns | ns | variable | +| namedReexport.ts:1:1:1:39 | export ... Class"; | C | C | type | +| tst.ts:3:1:3:20 | export type { Foo }; | Foo | Foo | namespace | +| tst.ts:3:1:3:20 | export type { Foo }; | Foo | Foo | type | diff --git a/javascript/ql/test/library-tests/TypeScript/TypeOnlyImportExport/test.ql b/javascript/ql/test/library-tests/TypeScript/TypeOnlyImportExport/test.ql new file mode 100644 index 00000000000..17939b3b380 --- /dev/null +++ b/javascript/ql/test/library-tests/TypeScript/TypeOnlyImportExport/test.ql @@ -0,0 +1,22 @@ +import javascript + +query VarRef getAVarReference(Variable v) { + result = v.getAReference() +} + +query VarRef getAnExportAccess(LocalTypeName t) { + result = t.getAnExportAccess() +} + +query TypeDecl getATypeDecl(LocalTypeName t) { + result = t.getADeclaration() +} + +query Function calls(DataFlow::InvokeNode invoke) { + result = invoke.getACallee() +} + +query predicate exportsAs(ExportDeclaration exprt, LexicalName v, string name, string kind) { + exprt.exportsAs(v, name) and + kind = v.getDeclarationSpace() +} diff --git a/javascript/ql/test/library-tests/TypeScript/TypeOnlyImportExport/tst.ts b/javascript/ql/test/library-tests/TypeScript/TypeOnlyImportExport/tst.ts new file mode 100644 index 00000000000..440e77b9dbc --- /dev/null +++ b/javascript/ql/test/library-tests/TypeScript/TypeOnlyImportExport/tst.ts @@ -0,0 +1,9 @@ +import type { Foo } from "foo"; + +export type { Foo }; + +var Foo = 45; + +import type * as types from "types"; + +export type * as blah from "blah"; diff --git a/javascript/ql/test/query-tests/Declarations/DeclBeforeUse/typescript.ts b/javascript/ql/test/query-tests/Declarations/DeclBeforeUse/typescript.ts index 18862ea5f5e..0de18d48a48 100644 --- a/javascript/ql/test/query-tests/Declarations/DeclBeforeUse/typescript.ts +++ b/javascript/ql/test/query-tests/Declarations/DeclBeforeUse/typescript.ts @@ -3,3 +3,9 @@ class Foo {} declare class Bar extends Baz {} // OK declare class Baz {} + +export type { I }; // OK - does not refer to the constant 'I' + +const I = 45; + +interface I {} diff --git a/javascript/ql/test/query-tests/Expressions/MissingAwait/tst.js b/javascript/ql/test/query-tests/Expressions/MissingAwait/tst.js index 7675f196b08..23909124b41 100644 --- a/javascript/ql/test/query-tests/Expressions/MissingAwait/tst.js +++ b/javascript/ql/test/query-tests/Expressions/MissingAwait/tst.js @@ -61,3 +61,7 @@ function useThingPossiblySync(b) { return thing + "bar"; // NOT OK - but we don't flag it } + +function useThingInVoid() { + void getThing(); // OK +} 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 396d9811ad8..68ed692f741 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,3 +1,4 @@ | normalizedPaths.js:208:38:208:63 | // OK - ... anyway | Spurious 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 | +| tainted-string-steps.js:28:39:28:70 | // NOT ... flagged | Missing 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 7e2aa30fe1f..061f3522e34 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 @@ -801,92 +801,328 @@ nodes | TaintedPath.js:112:45:112:52 | realpath | | TaintedPath.js:112:45:112:52 | realpath | | TaintedPath.js:112:45:112:52 | realpath | -| TaintedPath.js:119:6:119:47 | path | -| TaintedPath.js:119:6:119:47 | path | -| TaintedPath.js:119:6:119:47 | path | -| TaintedPath.js:119:6:119:47 | path | -| TaintedPath.js:119:6:119:47 | path | -| TaintedPath.js:119:6:119:47 | path | -| TaintedPath.js:119:6:119:47 | path | -| TaintedPath.js:119:6:119:47 | path | -| TaintedPath.js:119:6:119:47 | path | -| TaintedPath.js:119:6:119:47 | path | -| TaintedPath.js:119:6:119:47 | path | -| TaintedPath.js:119:6:119:47 | path | -| TaintedPath.js:119:6:119:47 | path | -| TaintedPath.js:119:6:119:47 | path | -| TaintedPath.js:119:6:119:47 | path | -| TaintedPath.js:119:6:119:47 | path | -| TaintedPath.js:119:13:119:36 | url.par ... , true) | -| TaintedPath.js:119:13:119:36 | url.par ... , true) | -| TaintedPath.js:119:13:119:36 | url.par ... , true) | -| TaintedPath.js:119:13:119:36 | url.par ... , true) | -| TaintedPath.js:119:13:119:36 | url.par ... , true) | -| TaintedPath.js:119:13:119:36 | url.par ... , true) | -| TaintedPath.js:119:13:119:36 | url.par ... , true) | -| TaintedPath.js:119:13:119:36 | url.par ... , true) | -| TaintedPath.js:119:13:119:36 | url.par ... , true) | -| TaintedPath.js:119:13:119:36 | url.par ... , true) | -| TaintedPath.js:119:13:119:36 | url.par ... , true) | -| TaintedPath.js:119:13:119:36 | url.par ... , true) | -| TaintedPath.js:119:13:119:36 | url.par ... , true) | -| TaintedPath.js:119:13:119:36 | url.par ... , true) | -| TaintedPath.js:119:13:119:36 | url.par ... , true) | -| TaintedPath.js:119:13:119:36 | url.par ... , true) | -| TaintedPath.js:119:13:119:42 | url.par ... ).query | -| TaintedPath.js:119:13:119:42 | url.par ... ).query | -| TaintedPath.js:119:13:119:42 | url.par ... ).query | -| TaintedPath.js:119:13:119:42 | url.par ... ).query | -| TaintedPath.js:119:13:119:42 | url.par ... ).query | -| TaintedPath.js:119:13:119:42 | url.par ... ).query | -| TaintedPath.js:119:13:119:42 | url.par ... ).query | -| TaintedPath.js:119:13:119:42 | url.par ... ).query | -| TaintedPath.js:119:13:119:42 | url.par ... ).query | -| TaintedPath.js:119:13:119:42 | url.par ... ).query | -| TaintedPath.js:119:13:119:42 | url.par ... ).query | -| TaintedPath.js:119:13:119:42 | url.par ... ).query | -| TaintedPath.js:119:13:119:42 | url.par ... ).query | -| TaintedPath.js:119:13:119:42 | url.par ... ).query | -| TaintedPath.js:119:13:119:42 | url.par ... ).query | -| TaintedPath.js:119:13:119:42 | url.par ... ).query | -| TaintedPath.js:119:13:119:47 | url.par ... ry.path | -| TaintedPath.js:119:13:119:47 | url.par ... ry.path | -| TaintedPath.js:119:13:119:47 | url.par ... ry.path | -| TaintedPath.js:119:13:119:47 | url.par ... ry.path | -| TaintedPath.js:119:13:119:47 | url.par ... ry.path | -| TaintedPath.js:119:13:119:47 | url.par ... ry.path | -| TaintedPath.js:119:13:119:47 | url.par ... ry.path | -| TaintedPath.js:119:13:119:47 | url.par ... ry.path | -| TaintedPath.js:119:13:119:47 | url.par ... ry.path | -| TaintedPath.js:119:13:119:47 | url.par ... ry.path | -| TaintedPath.js:119:13:119:47 | url.par ... ry.path | -| TaintedPath.js:119:13:119:47 | url.par ... ry.path | -| TaintedPath.js:119:13:119:47 | url.par ... ry.path | -| TaintedPath.js:119:13:119:47 | url.par ... ry.path | -| TaintedPath.js:119:13:119:47 | url.par ... ry.path | -| TaintedPath.js:119:13:119:47 | url.par ... ry.path | -| TaintedPath.js:119:23:119:29 | req.url | -| TaintedPath.js:119:23:119:29 | req.url | -| TaintedPath.js:119:23:119:29 | req.url | -| TaintedPath.js:119:23:119:29 | req.url | -| TaintedPath.js:119:23:119:29 | req.url | -| TaintedPath.js:121:23:121:26 | path | -| TaintedPath.js:121:23:121:26 | path | -| TaintedPath.js:121:23:121:26 | path | -| TaintedPath.js:121:23:121:26 | path | -| TaintedPath.js:121:23:121:26 | path | -| TaintedPath.js:121:23:121:26 | path | -| TaintedPath.js:121:23:121:26 | path | -| TaintedPath.js:121:23:121:26 | path | -| TaintedPath.js:121:23:121:26 | path | -| TaintedPath.js:121:23:121:26 | path | -| TaintedPath.js:121:23:121:26 | path | -| TaintedPath.js:121:23:121:26 | path | -| TaintedPath.js:121:23:121:26 | path | -| TaintedPath.js:121:23:121:26 | path | -| TaintedPath.js:121:23:121:26 | path | -| TaintedPath.js:121:23:121:26 | path | -| TaintedPath.js:121:23:121:26 | path | +| TaintedPath.js:143:6:143:47 | path | +| TaintedPath.js:143:6:143:47 | path | +| TaintedPath.js:143:6:143:47 | path | +| TaintedPath.js:143:6:143:47 | path | +| TaintedPath.js:143:6:143:47 | path | +| TaintedPath.js:143:6:143:47 | path | +| TaintedPath.js:143:6:143:47 | path | +| TaintedPath.js:143:6:143:47 | path | +| TaintedPath.js:143:6:143:47 | path | +| TaintedPath.js:143:6:143:47 | path | +| TaintedPath.js:143:6:143:47 | path | +| TaintedPath.js:143:6:143:47 | path | +| TaintedPath.js:143:6:143:47 | path | +| TaintedPath.js:143:6:143:47 | path | +| TaintedPath.js:143:6:143:47 | path | +| TaintedPath.js:143:6:143:47 | path | +| TaintedPath.js:143:13:143:36 | url.par ... , true) | +| TaintedPath.js:143:13:143:36 | url.par ... , true) | +| TaintedPath.js:143:13:143:36 | url.par ... , true) | +| TaintedPath.js:143:13:143:36 | url.par ... , true) | +| TaintedPath.js:143:13:143:36 | url.par ... , true) | +| TaintedPath.js:143:13:143:36 | url.par ... , true) | +| TaintedPath.js:143:13:143:36 | url.par ... , true) | +| TaintedPath.js:143:13:143:36 | url.par ... , true) | +| TaintedPath.js:143:13:143:36 | url.par ... , true) | +| TaintedPath.js:143:13:143:36 | url.par ... , true) | +| TaintedPath.js:143:13:143:36 | url.par ... , true) | +| TaintedPath.js:143:13:143:36 | url.par ... , true) | +| TaintedPath.js:143:13:143:36 | url.par ... , true) | +| TaintedPath.js:143:13:143:36 | url.par ... , true) | +| TaintedPath.js:143:13:143:36 | url.par ... , true) | +| TaintedPath.js:143:13:143:36 | url.par ... , true) | +| TaintedPath.js:143:13:143:42 | url.par ... ).query | +| TaintedPath.js:143:13:143:42 | url.par ... ).query | +| TaintedPath.js:143:13:143:42 | url.par ... ).query | +| TaintedPath.js:143:13:143:42 | url.par ... ).query | +| TaintedPath.js:143:13:143:42 | url.par ... ).query | +| TaintedPath.js:143:13:143:42 | url.par ... ).query | +| TaintedPath.js:143:13:143:42 | url.par ... ).query | +| TaintedPath.js:143:13:143:42 | url.par ... ).query | +| TaintedPath.js:143:13:143:42 | url.par ... ).query | +| TaintedPath.js:143:13:143:42 | url.par ... ).query | +| TaintedPath.js:143:13:143:42 | url.par ... ).query | +| TaintedPath.js:143:13:143:42 | url.par ... ).query | +| TaintedPath.js:143:13:143:42 | url.par ... ).query | +| TaintedPath.js:143:13:143:42 | url.par ... ).query | +| TaintedPath.js:143:13:143:42 | url.par ... ).query | +| TaintedPath.js:143:13:143:42 | url.par ... ).query | +| TaintedPath.js:143:13:143:47 | url.par ... ry.path | +| TaintedPath.js:143:13:143:47 | url.par ... ry.path | +| TaintedPath.js:143:13:143:47 | url.par ... ry.path | +| TaintedPath.js:143:13:143:47 | url.par ... ry.path | +| TaintedPath.js:143:13:143:47 | url.par ... ry.path | +| TaintedPath.js:143:13:143:47 | url.par ... ry.path | +| TaintedPath.js:143:13:143:47 | url.par ... ry.path | +| TaintedPath.js:143:13:143:47 | url.par ... ry.path | +| TaintedPath.js:143:13:143:47 | url.par ... ry.path | +| TaintedPath.js:143:13:143:47 | url.par ... ry.path | +| TaintedPath.js:143:13:143:47 | url.par ... ry.path | +| TaintedPath.js:143:13:143:47 | url.par ... ry.path | +| TaintedPath.js:143:13:143:47 | url.par ... ry.path | +| TaintedPath.js:143:13:143:47 | url.par ... ry.path | +| TaintedPath.js:143:13:143:47 | url.par ... ry.path | +| TaintedPath.js:143:13:143:47 | url.par ... ry.path | +| TaintedPath.js:143:23:143:29 | req.url | +| TaintedPath.js:143:23:143:29 | req.url | +| TaintedPath.js:143:23:143:29 | req.url | +| TaintedPath.js:143:23:143:29 | req.url | +| TaintedPath.js:143:23:143:29 | req.url | +| TaintedPath.js:145:23:145:26 | path | +| TaintedPath.js:145:23:145:26 | path | +| TaintedPath.js:145:23:145:26 | path | +| TaintedPath.js:145:23:145:26 | path | +| TaintedPath.js:145:23:145:26 | path | +| TaintedPath.js:145:23:145:26 | path | +| TaintedPath.js:145:23:145:26 | path | +| TaintedPath.js:145:23:145:26 | path | +| TaintedPath.js:145:23:145:26 | path | +| TaintedPath.js:145:23:145:26 | path | +| TaintedPath.js:145:23:145:26 | path | +| TaintedPath.js:145:23:145:26 | path | +| TaintedPath.js:145:23:145:26 | path | +| TaintedPath.js:145:23:145:26 | path | +| TaintedPath.js:145:23:145:26 | path | +| TaintedPath.js:145:23:145:26 | path | +| TaintedPath.js:145:23:145:26 | path | +| TaintedPath.js:149:7:149:48 | path | +| TaintedPath.js:149:7:149:48 | path | +| TaintedPath.js:149:7:149:48 | path | +| TaintedPath.js:149:7:149:48 | path | +| TaintedPath.js:149:7:149:48 | path | +| TaintedPath.js:149:7:149:48 | path | +| TaintedPath.js:149:7:149:48 | path | +| TaintedPath.js:149:7:149:48 | path | +| TaintedPath.js:149:7:149:48 | path | +| TaintedPath.js:149:7:149:48 | path | +| TaintedPath.js:149:7:149:48 | path | +| TaintedPath.js:149:7:149:48 | path | +| TaintedPath.js:149:7:149:48 | path | +| TaintedPath.js:149:7:149:48 | path | +| TaintedPath.js:149:7:149:48 | path | +| TaintedPath.js:149:7:149:48 | path | +| TaintedPath.js:149:14:149:37 | url.par ... , true) | +| TaintedPath.js:149:14:149:37 | url.par ... , true) | +| TaintedPath.js:149:14:149:37 | url.par ... , true) | +| TaintedPath.js:149:14:149:37 | url.par ... , true) | +| TaintedPath.js:149:14:149:37 | url.par ... , true) | +| TaintedPath.js:149:14:149:37 | url.par ... , true) | +| TaintedPath.js:149:14:149:37 | url.par ... , true) | +| TaintedPath.js:149:14:149:37 | url.par ... , true) | +| TaintedPath.js:149:14:149:37 | url.par ... , true) | +| TaintedPath.js:149:14:149:37 | url.par ... , true) | +| TaintedPath.js:149:14:149:37 | url.par ... , true) | +| TaintedPath.js:149:14:149:37 | url.par ... , true) | +| TaintedPath.js:149:14:149:37 | url.par ... , true) | +| TaintedPath.js:149:14:149:37 | url.par ... , true) | +| TaintedPath.js:149:14:149:37 | url.par ... , true) | +| TaintedPath.js:149:14:149:37 | url.par ... , true) | +| TaintedPath.js:149:14:149:43 | url.par ... ).query | +| TaintedPath.js:149:14:149:43 | url.par ... ).query | +| TaintedPath.js:149:14:149:43 | url.par ... ).query | +| TaintedPath.js:149:14:149:43 | url.par ... ).query | +| TaintedPath.js:149:14:149:43 | url.par ... ).query | +| TaintedPath.js:149:14:149:43 | url.par ... ).query | +| TaintedPath.js:149:14:149:43 | url.par ... ).query | +| TaintedPath.js:149:14:149:43 | url.par ... ).query | +| TaintedPath.js:149:14:149:43 | url.par ... ).query | +| TaintedPath.js:149:14:149:43 | url.par ... ).query | +| TaintedPath.js:149:14:149:43 | url.par ... ).query | +| TaintedPath.js:149:14:149:43 | url.par ... ).query | +| TaintedPath.js:149:14:149:43 | url.par ... ).query | +| TaintedPath.js:149:14:149:43 | url.par ... ).query | +| TaintedPath.js:149:14:149:43 | url.par ... ).query | +| TaintedPath.js:149:14:149:43 | url.par ... ).query | +| TaintedPath.js:149:14:149:48 | url.par ... ry.path | +| TaintedPath.js:149:14:149:48 | url.par ... ry.path | +| TaintedPath.js:149:14:149:48 | url.par ... ry.path | +| TaintedPath.js:149:14:149:48 | url.par ... ry.path | +| TaintedPath.js:149:14:149:48 | url.par ... ry.path | +| TaintedPath.js:149:14:149:48 | url.par ... ry.path | +| TaintedPath.js:149:14:149:48 | url.par ... ry.path | +| TaintedPath.js:149:14:149:48 | url.par ... ry.path | +| TaintedPath.js:149:14:149:48 | url.par ... ry.path | +| TaintedPath.js:149:14:149:48 | url.par ... ry.path | +| TaintedPath.js:149:14:149:48 | url.par ... ry.path | +| TaintedPath.js:149:14:149:48 | url.par ... ry.path | +| TaintedPath.js:149:14:149:48 | url.par ... ry.path | +| TaintedPath.js:149:14:149:48 | url.par ... ry.path | +| TaintedPath.js:149:14:149:48 | url.par ... ry.path | +| TaintedPath.js:149:14:149:48 | url.par ... ry.path | +| TaintedPath.js:149:24:149:30 | req.url | +| TaintedPath.js:149:24:149:30 | req.url | +| TaintedPath.js:149:24:149:30 | req.url | +| TaintedPath.js:149:24:149:30 | req.url | +| TaintedPath.js:149:24:149:30 | req.url | +| TaintedPath.js:151:19:151:22 | path | +| TaintedPath.js:151:19:151:22 | path | +| TaintedPath.js:151:19:151:22 | path | +| TaintedPath.js:151:19:151:22 | path | +| TaintedPath.js:151:19:151:22 | path | +| TaintedPath.js:151:19:151:22 | path | +| TaintedPath.js:151:19:151:22 | path | +| TaintedPath.js:151:19:151:22 | path | +| TaintedPath.js:151:19:151:22 | path | +| TaintedPath.js:151:19:151:22 | path | +| TaintedPath.js:151:19:151:22 | path | +| TaintedPath.js:151:19:151:22 | path | +| TaintedPath.js:151:19:151:22 | path | +| TaintedPath.js:151:19:151:22 | path | +| TaintedPath.js:151:19:151:22 | path | +| TaintedPath.js:151:19:151:22 | path | +| TaintedPath.js:151:19:151:22 | path | +| TaintedPath.js:153:7:153:29 | split | +| TaintedPath.js:153:7:153:29 | split | +| TaintedPath.js:153:7:153:29 | split | +| TaintedPath.js:153:7:153:29 | split | +| TaintedPath.js:153:15:153:18 | path | +| TaintedPath.js:153:15:153:18 | path | +| TaintedPath.js:153:15:153:18 | path | +| TaintedPath.js:153:15:153:18 | path | +| TaintedPath.js:153:15:153:18 | path | +| TaintedPath.js:153:15:153:18 | path | +| TaintedPath.js:153:15:153:18 | path | +| TaintedPath.js:153:15:153:18 | path | +| TaintedPath.js:153:15:153:18 | path | +| TaintedPath.js:153:15:153:18 | path | +| TaintedPath.js:153:15:153:18 | path | +| TaintedPath.js:153:15:153:18 | path | +| TaintedPath.js:153:15:153:29 | path.split("/") | +| TaintedPath.js:153:15:153:29 | path.split("/") | +| TaintedPath.js:153:15:153:29 | path.split("/") | +| TaintedPath.js:153:15:153:29 | path.split("/") | +| TaintedPath.js:155:19:155:23 | split | +| TaintedPath.js:155:19:155:23 | split | +| TaintedPath.js:155:19:155:23 | split | +| TaintedPath.js:155:19:155:23 | split | +| TaintedPath.js:155:19:155:33 | split.join("/") | +| TaintedPath.js:155:19:155:33 | split.join("/") | +| TaintedPath.js:155:19:155:33 | split.join("/") | +| TaintedPath.js:155:19:155:33 | split.join("/") | +| TaintedPath.js:155:19:155:33 | split.join("/") | +| TaintedPath.js:155:19:155:33 | split.join("/") | +| TaintedPath.js:155:19:155:33 | split.join("/") | +| TaintedPath.js:155:19:155:33 | split.join("/") | +| TaintedPath.js:155:19:155:33 | split.join("/") | +| TaintedPath.js:155:19:155:33 | split.join("/") | +| TaintedPath.js:155:19:155:33 | split.join("/") | +| TaintedPath.js:155:19:155:33 | split.join("/") | +| TaintedPath.js:155:19:155:33 | split.join("/") | +| TaintedPath.js:159:19:159:23 | split | +| TaintedPath.js:159:19:159:23 | split | +| TaintedPath.js:159:19:159:23 | split | +| TaintedPath.js:159:19:159:23 | split | +| TaintedPath.js:159:19:159:26 | split[x] | +| TaintedPath.js:159:19:159:26 | split[x] | +| TaintedPath.js:159:19:159:26 | split[x] | +| TaintedPath.js:159:19:159:26 | split[x] | +| TaintedPath.js:159:19:159:26 | split[x] | +| TaintedPath.js:159:19:159:26 | split[x] | +| TaintedPath.js:159:19:159:26 | split[x] | +| TaintedPath.js:159:19:159:26 | split[x] | +| TaintedPath.js:159:19:159:26 | split[x] | +| TaintedPath.js:159:19:159:26 | split[x] | +| TaintedPath.js:159:19:159:26 | split[x] | +| TaintedPath.js:159:19:159:26 | split[x] | +| TaintedPath.js:159:19:159:26 | split[x] | +| TaintedPath.js:160:19:160:35 | prefix + split[x] | +| TaintedPath.js:160:19:160:35 | prefix + split[x] | +| TaintedPath.js:160:19:160:35 | prefix + split[x] | +| TaintedPath.js:160:19:160:35 | prefix + split[x] | +| TaintedPath.js:160:19:160:35 | prefix + split[x] | +| TaintedPath.js:160:28:160:32 | split | +| TaintedPath.js:160:28:160:32 | split | +| TaintedPath.js:160:28:160:32 | split | +| TaintedPath.js:160:28:160:32 | split | +| TaintedPath.js:160:28:160:35 | split[x] | +| TaintedPath.js:160:28:160:35 | split[x] | +| TaintedPath.js:160:28:160:35 | split[x] | +| TaintedPath.js:160:28:160:35 | split[x] | +| TaintedPath.js:160:28:160:35 | split[x] | +| TaintedPath.js:160:28:160:35 | split[x] | +| TaintedPath.js:160:28:160:35 | split[x] | +| TaintedPath.js:160:28:160:35 | split[x] | +| TaintedPath.js:160:28:160:35 | split[x] | +| TaintedPath.js:160:28:160:35 | split[x] | +| TaintedPath.js:160:28:160:35 | split[x] | +| TaintedPath.js:160:28:160:35 | split[x] | +| TaintedPath.js:162:7:162:38 | concatted | +| TaintedPath.js:162:7:162:38 | concatted | +| TaintedPath.js:162:7:162:38 | concatted | +| TaintedPath.js:162:7:162:38 | concatted | +| TaintedPath.js:162:19:162:38 | prefix.concat(split) | +| TaintedPath.js:162:19:162:38 | prefix.concat(split) | +| TaintedPath.js:162:19:162:38 | prefix.concat(split) | +| TaintedPath.js:162:19:162:38 | prefix.concat(split) | +| TaintedPath.js:162:33:162:37 | split | +| TaintedPath.js:162:33:162:37 | split | +| TaintedPath.js:162:33:162:37 | split | +| TaintedPath.js:162:33:162:37 | split | +| TaintedPath.js:163:19:163:27 | concatted | +| TaintedPath.js:163:19:163:27 | concatted | +| TaintedPath.js:163:19:163:27 | concatted | +| TaintedPath.js:163:19:163:27 | concatted | +| TaintedPath.js:163:19:163:37 | concatted.join("/") | +| TaintedPath.js:163:19:163:37 | concatted.join("/") | +| TaintedPath.js:163:19:163:37 | concatted.join("/") | +| TaintedPath.js:163:19:163:37 | concatted.join("/") | +| TaintedPath.js:163:19:163:37 | concatted.join("/") | +| TaintedPath.js:163:19:163:37 | concatted.join("/") | +| TaintedPath.js:163:19:163:37 | concatted.join("/") | +| TaintedPath.js:163:19:163:37 | concatted.join("/") | +| TaintedPath.js:163:19:163:37 | concatted.join("/") | +| TaintedPath.js:163:19:163:37 | concatted.join("/") | +| TaintedPath.js:163:19:163:37 | concatted.join("/") | +| TaintedPath.js:163:19:163:37 | concatted.join("/") | +| TaintedPath.js:163:19:163:37 | concatted.join("/") | +| TaintedPath.js:165:7:165:39 | concatted2 | +| TaintedPath.js:165:7:165:39 | concatted2 | +| TaintedPath.js:165:7:165:39 | concatted2 | +| TaintedPath.js:165:7:165:39 | concatted2 | +| TaintedPath.js:165:20:165:24 | split | +| TaintedPath.js:165:20:165:24 | split | +| TaintedPath.js:165:20:165:24 | split | +| TaintedPath.js:165:20:165:24 | split | +| TaintedPath.js:165:20:165:39 | split.concat(prefix) | +| TaintedPath.js:165:20:165:39 | split.concat(prefix) | +| TaintedPath.js:165:20:165:39 | split.concat(prefix) | +| TaintedPath.js:165:20:165:39 | split.concat(prefix) | +| TaintedPath.js:166:19:166:28 | concatted2 | +| TaintedPath.js:166:19:166:28 | concatted2 | +| TaintedPath.js:166:19:166:28 | concatted2 | +| TaintedPath.js:166:19:166:28 | concatted2 | +| TaintedPath.js:166:19:166:38 | concatted2.join("/") | +| TaintedPath.js:166:19:166:38 | concatted2.join("/") | +| TaintedPath.js:166:19:166:38 | concatted2.join("/") | +| TaintedPath.js:166:19:166:38 | concatted2.join("/") | +| TaintedPath.js:166:19:166:38 | concatted2.join("/") | +| TaintedPath.js:166:19:166:38 | concatted2.join("/") | +| TaintedPath.js:166:19:166:38 | concatted2.join("/") | +| TaintedPath.js:166:19:166:38 | concatted2.join("/") | +| TaintedPath.js:166:19:166:38 | concatted2.join("/") | +| TaintedPath.js:166:19:166:38 | concatted2.join("/") | +| TaintedPath.js:166:19:166:38 | concatted2.join("/") | +| TaintedPath.js:166:19:166:38 | concatted2.join("/") | +| TaintedPath.js:166:19:166:38 | concatted2.join("/") | +| TaintedPath.js:168:19:168:23 | split | +| TaintedPath.js:168:19:168:23 | split | +| TaintedPath.js:168:19:168:23 | split | +| TaintedPath.js:168:19:168:23 | split | +| TaintedPath.js:168:19:168:29 | split.pop() | +| TaintedPath.js:168:19:168:29 | split.pop() | +| TaintedPath.js:168:19:168:29 | split.pop() | +| TaintedPath.js:168:19:168:29 | split.pop() | +| TaintedPath.js:168:19:168:29 | split.pop() | +| TaintedPath.js:168:19:168:29 | split.pop() | +| TaintedPath.js:168:19:168:29 | split.pop() | +| TaintedPath.js:168:19:168:29 | split.pop() | +| TaintedPath.js:168:19:168:29 | split.pop() | +| TaintedPath.js:168:19:168:29 | split.pop() | +| TaintedPath.js:168:19:168:29 | split.pop() | +| TaintedPath.js:168:19:168:29 | split.pop() | +| TaintedPath.js:168:19:168:29 | split.pop() | | normalizedPaths.js:11:7:11:27 | path | | normalizedPaths.js:11:7:11:27 | path | | normalizedPaths.js:11:7:11:27 | path | @@ -1259,6 +1495,142 @@ nodes | normalizedPaths.js:250:21:250:24 | path | | normalizedPaths.js:250:21:250:24 | path | | normalizedPaths.js:250:21:250:24 | path | +| normalizedPaths.js:254:7:254:47 | path | +| normalizedPaths.js:254:7:254:47 | path | +| normalizedPaths.js:254:7:254:47 | path | +| normalizedPaths.js:254:7:254:47 | path | +| normalizedPaths.js:254:14:254:47 | pathMod ... y.path) | +| normalizedPaths.js:254:14:254:47 | pathMod ... y.path) | +| normalizedPaths.js:254:14:254:47 | pathMod ... y.path) | +| normalizedPaths.js:254:14:254:47 | pathMod ... y.path) | +| normalizedPaths.js:254:33:254:46 | req.query.path | +| normalizedPaths.js:254:33:254:46 | req.query.path | +| normalizedPaths.js:254:33:254:46 | req.query.path | +| normalizedPaths.js:254:33:254:46 | req.query.path | +| normalizedPaths.js:254:33:254:46 | req.query.path | +| normalizedPaths.js:256:19:256:22 | path | +| normalizedPaths.js:256:19:256:22 | path | +| normalizedPaths.js:256:19:256:22 | path | +| normalizedPaths.js:256:19:256:22 | path | +| normalizedPaths.js:256:19:256:22 | path | +| normalizedPaths.js:262:21:262:24 | path | +| normalizedPaths.js:262:21:262:24 | path | +| normalizedPaths.js:262:21:262:24 | path | +| normalizedPaths.js:262:21:262:24 | path | +| normalizedPaths.js:262:21:262:24 | path | +| normalizedPaths.js:267:7:267:42 | newpath | +| normalizedPaths.js:267:7:267:42 | newpath | +| normalizedPaths.js:267:7:267:42 | newpath | +| normalizedPaths.js:267:7:267:42 | newpath | +| normalizedPaths.js:267:17:267:42 | pathMod ... e(path) | +| normalizedPaths.js:267:17:267:42 | pathMod ... e(path) | +| normalizedPaths.js:267:17:267:42 | pathMod ... e(path) | +| normalizedPaths.js:267:17:267:42 | pathMod ... e(path) | +| normalizedPaths.js:267:38:267:41 | path | +| normalizedPaths.js:267:38:267:41 | path | +| normalizedPaths.js:267:38:267:41 | path | +| normalizedPaths.js:267:38:267:41 | path | +| normalizedPaths.js:270:21:270:27 | newpath | +| normalizedPaths.js:270:21:270:27 | newpath | +| normalizedPaths.js:270:21:270:27 | newpath | +| normalizedPaths.js:270:21:270:27 | newpath | +| normalizedPaths.js:270:21:270:27 | newpath | +| normalizedPaths.js:275:7:275:42 | newpath | +| normalizedPaths.js:275:7:275:42 | newpath | +| normalizedPaths.js:275:7:275:42 | newpath | +| normalizedPaths.js:275:7:275:42 | newpath | +| normalizedPaths.js:275:17:275:42 | pathMod ... e(path) | +| normalizedPaths.js:275:17:275:42 | pathMod ... e(path) | +| normalizedPaths.js:275:17:275:42 | pathMod ... e(path) | +| normalizedPaths.js:275:17:275:42 | pathMod ... e(path) | +| normalizedPaths.js:275:38:275:41 | path | +| normalizedPaths.js:275:38:275:41 | path | +| normalizedPaths.js:275:38:275:41 | path | +| normalizedPaths.js:275:38:275:41 | path | +| normalizedPaths.js:278:21:278:27 | newpath | +| normalizedPaths.js:278:21:278:27 | newpath | +| normalizedPaths.js:278:21:278:27 | newpath | +| normalizedPaths.js:278:21:278:27 | newpath | +| normalizedPaths.js:278:21:278:27 | newpath | +| normalizedPaths.js:283:7:283:42 | newpath | +| normalizedPaths.js:283:7:283:42 | newpath | +| normalizedPaths.js:283:7:283:42 | newpath | +| normalizedPaths.js:283:7:283:42 | newpath | +| normalizedPaths.js:283:17:283:42 | pathMod ... e(path) | +| normalizedPaths.js:283:17:283:42 | pathMod ... e(path) | +| normalizedPaths.js:283:17:283:42 | pathMod ... e(path) | +| normalizedPaths.js:283:17:283:42 | pathMod ... e(path) | +| normalizedPaths.js:283:38:283:41 | path | +| normalizedPaths.js:283:38:283:41 | path | +| normalizedPaths.js:283:38:283:41 | path | +| normalizedPaths.js:283:38:283:41 | path | +| normalizedPaths.js:286:21:286:27 | newpath | +| normalizedPaths.js:286:21:286:27 | newpath | +| normalizedPaths.js:286:21:286:27 | newpath | +| normalizedPaths.js:286:21:286:27 | newpath | +| normalizedPaths.js:286:21:286:27 | newpath | +| normalizedPaths.js:291:7:291:42 | newpath | +| normalizedPaths.js:291:7:291:42 | newpath | +| normalizedPaths.js:291:7:291:42 | newpath | +| normalizedPaths.js:291:7:291:42 | newpath | +| normalizedPaths.js:291:17:291:42 | pathMod ... e(path) | +| normalizedPaths.js:291:17:291:42 | pathMod ... e(path) | +| normalizedPaths.js:291:17:291:42 | pathMod ... e(path) | +| normalizedPaths.js:291:17:291:42 | pathMod ... e(path) | +| normalizedPaths.js:291:38:291:41 | path | +| normalizedPaths.js:291:38:291:41 | path | +| normalizedPaths.js:291:38:291:41 | path | +| normalizedPaths.js:291:38:291:41 | path | +| normalizedPaths.js:296:21:296:27 | newpath | +| normalizedPaths.js:296:21:296:27 | newpath | +| normalizedPaths.js:296:21:296:27 | newpath | +| normalizedPaths.js:296:21:296:27 | newpath | +| normalizedPaths.js:296:21:296:27 | newpath | +| normalizedPaths.js:303:6:303:26 | path | +| normalizedPaths.js:303:6:303:26 | path | +| normalizedPaths.js:303:6:303:26 | path | +| normalizedPaths.js:303:6:303:26 | path | +| normalizedPaths.js:303:13:303:26 | req.query.path | +| normalizedPaths.js:303:13:303:26 | req.query.path | +| normalizedPaths.js:303:13:303:26 | req.query.path | +| normalizedPaths.js:303:13:303:26 | req.query.path | +| normalizedPaths.js:303:13:303:26 | req.query.path | +| normalizedPaths.js:304:18:304:21 | path | +| normalizedPaths.js:304:18:304:21 | path | +| normalizedPaths.js:304:18:304:21 | path | +| normalizedPaths.js:304:18:304:21 | path | +| normalizedPaths.js:304:18:304:21 | path | +| normalizedPaths.js:309:19:309:22 | path | +| normalizedPaths.js:309:19:309:22 | path | +| normalizedPaths.js:309:19:309:22 | path | +| normalizedPaths.js:309:19:309:22 | path | +| normalizedPaths.js:309:19:309:22 | path | +| normalizedPaths.js:313:19:313:22 | path | +| normalizedPaths.js:313:19:313:22 | path | +| normalizedPaths.js:313:19:313:22 | path | +| normalizedPaths.js:313:19:313:22 | path | +| normalizedPaths.js:316:19:316:22 | path | +| normalizedPaths.js:316:19:316:22 | path | +| normalizedPaths.js:316:19:316:22 | path | +| normalizedPaths.js:316:19:316:22 | path | +| normalizedPaths.js:316:19:316:22 | path | +| normalizedPaths.js:320:6:320:49 | normalizedPath | +| normalizedPaths.js:320:6:320:49 | normalizedPath | +| normalizedPaths.js:320:6:320:49 | normalizedPath | +| normalizedPaths.js:320:23:320:49 | pathMod ... , path) | +| normalizedPaths.js:320:23:320:49 | pathMod ... , path) | +| normalizedPaths.js:320:23:320:49 | pathMod ... , path) | +| normalizedPaths.js:320:45:320:48 | path | +| normalizedPaths.js:320:45:320:48 | path | +| normalizedPaths.js:320:45:320:48 | path | +| normalizedPaths.js:325:19:325:32 | normalizedPath | +| normalizedPaths.js:325:19:325:32 | normalizedPath | +| normalizedPaths.js:325:19:325:32 | normalizedPath | +| normalizedPaths.js:325:19:325:32 | normalizedPath | +| normalizedPaths.js:332:19:332:32 | normalizedPath | +| normalizedPaths.js:332:19:332:32 | normalizedPath | +| normalizedPaths.js:332:19:332:32 | normalizedPath | +| normalizedPaths.js:332:19:332:32 | normalizedPath | | 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") | @@ -1631,6 +2003,64 @@ nodes | 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:22:18:22:21 | path | +| tainted-string-steps.js:22:18:22:21 | path | +| tainted-string-steps.js:22:18:22:21 | path | +| tainted-string-steps.js:22:18:22:21 | path | +| tainted-string-steps.js:22:18:22:21 | path | +| tainted-string-steps.js:22:18:22:21 | path | +| tainted-string-steps.js:22:18:22:21 | path | +| tainted-string-steps.js:22:18:22:21 | path | +| tainted-string-steps.js:22:18:22:21 | path | +| tainted-string-steps.js:22:18:22:21 | path | +| tainted-string-steps.js:22:18:22:21 | path | +| tainted-string-steps.js:22:18:22:21 | path | +| tainted-string-steps.js:22:18:22:32 | path.split('/') | +| tainted-string-steps.js:22:18:22:32 | path.split('/') | +| tainted-string-steps.js:22:18:22:32 | path.split('/') | +| tainted-string-steps.js:22:18:22:32 | path.split('/') | +| tainted-string-steps.js:22:18:22:35 | path.split('/')[i] | +| tainted-string-steps.js:22:18:22:35 | path.split('/')[i] | +| tainted-string-steps.js:22:18:22:35 | path.split('/')[i] | +| tainted-string-steps.js:22:18:22:35 | path.split('/')[i] | +| tainted-string-steps.js:22:18:22:35 | path.split('/')[i] | +| tainted-string-steps.js:22:18:22:35 | path.split('/')[i] | +| tainted-string-steps.js:22:18:22:35 | path.split('/')[i] | +| tainted-string-steps.js:22:18:22:35 | path.split('/')[i] | +| tainted-string-steps.js:22:18:22:35 | path.split('/')[i] | +| tainted-string-steps.js:22:18:22:35 | path.split('/')[i] | +| tainted-string-steps.js:22:18:22:35 | path.split('/')[i] | +| tainted-string-steps.js:22:18:22:35 | path.split('/')[i] | +| tainted-string-steps.js:22:18:22:35 | path.split('/')[i] | +| tainted-string-steps.js:23:18:23:21 | path | +| tainted-string-steps.js:23:18:23:21 | path | +| tainted-string-steps.js:23:18:23:21 | path | +| tainted-string-steps.js:23:18:23:21 | path | +| tainted-string-steps.js:23:18:23:21 | path | +| tainted-string-steps.js:23:18:23:21 | path | +| tainted-string-steps.js:23:18:23:21 | path | +| tainted-string-steps.js:23:18:23:21 | path | +| tainted-string-steps.js:23:18:23:21 | path | +| tainted-string-steps.js:23:18:23:21 | path | +| tainted-string-steps.js:23:18:23:21 | path | +| tainted-string-steps.js:23:18:23:21 | path | +| tainted-string-steps.js:23:18:23:33 | path.split(/\\//) | +| tainted-string-steps.js:23:18:23:33 | path.split(/\\//) | +| tainted-string-steps.js:23:18:23:33 | path.split(/\\//) | +| tainted-string-steps.js:23:18:23:33 | path.split(/\\//) | +| tainted-string-steps.js:23:18:23:36 | path.split(/\\//)[i] | +| tainted-string-steps.js:23:18:23:36 | path.split(/\\//)[i] | +| tainted-string-steps.js:23:18:23:36 | path.split(/\\//)[i] | +| tainted-string-steps.js:23:18:23:36 | path.split(/\\//)[i] | +| tainted-string-steps.js:23:18:23:36 | path.split(/\\//)[i] | +| tainted-string-steps.js:23:18:23:36 | path.split(/\\//)[i] | +| tainted-string-steps.js:23:18:23:36 | path.split(/\\//)[i] | +| tainted-string-steps.js:23:18:23:36 | path.split(/\\//)[i] | +| tainted-string-steps.js:23:18:23:36 | path.split(/\\//)[i] | +| tainted-string-steps.js:23:18:23:36 | path.split(/\\//)[i] | +| tainted-string-steps.js:23:18:23:36 | path.split(/\\//)[i] | +| tainted-string-steps.js:23:18:23:36 | path.split(/\\//)[i] | +| tainted-string-steps.js:23:18:23:36 | path.split(/\\//)[i] | | 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 | @@ -3082,118 +3512,422 @@ edges | TaintedPath.js:111:32:111:39 | realpath | TaintedPath.js:112:45:112:52 | realpath | | TaintedPath.js:111:32:111:39 | realpath | TaintedPath.js:112:45:112:52 | realpath | | TaintedPath.js:111:32:111:39 | realpath | TaintedPath.js:112:45:112:52 | realpath | -| TaintedPath.js:119:6:119:47 | path | TaintedPath.js:121:23:121:26 | path | -| TaintedPath.js:119:6:119:47 | path | TaintedPath.js:121:23:121:26 | path | -| TaintedPath.js:119:6:119:47 | path | TaintedPath.js:121:23:121:26 | path | -| TaintedPath.js:119:6:119:47 | path | TaintedPath.js:121:23:121:26 | path | -| TaintedPath.js:119:6:119:47 | path | TaintedPath.js:121:23:121:26 | path | -| TaintedPath.js:119:6:119:47 | path | TaintedPath.js:121:23:121:26 | path | -| TaintedPath.js:119:6:119:47 | path | TaintedPath.js:121:23:121:26 | path | -| TaintedPath.js:119:6:119:47 | path | TaintedPath.js:121:23:121:26 | path | -| TaintedPath.js:119:6:119:47 | path | TaintedPath.js:121:23:121:26 | path | -| TaintedPath.js:119:6:119:47 | path | TaintedPath.js:121:23:121:26 | path | -| TaintedPath.js:119:6:119:47 | path | TaintedPath.js:121:23:121:26 | path | -| TaintedPath.js:119:6:119:47 | path | TaintedPath.js:121:23:121:26 | path | -| TaintedPath.js:119:6:119:47 | path | TaintedPath.js:121:23:121:26 | path | -| TaintedPath.js:119:6:119:47 | path | TaintedPath.js:121:23:121:26 | path | -| TaintedPath.js:119:6:119:47 | path | TaintedPath.js:121:23:121:26 | path | -| TaintedPath.js:119:6:119:47 | path | TaintedPath.js:121:23:121:26 | path | -| TaintedPath.js:119:6:119:47 | path | TaintedPath.js:121:23:121:26 | path | -| TaintedPath.js:119:6:119:47 | path | TaintedPath.js:121:23:121:26 | path | -| TaintedPath.js:119:6:119:47 | path | TaintedPath.js:121:23:121:26 | path | -| TaintedPath.js:119:6:119:47 | path | TaintedPath.js:121:23:121:26 | path | -| TaintedPath.js:119:6:119:47 | path | TaintedPath.js:121:23:121:26 | path | -| TaintedPath.js:119:6:119:47 | path | TaintedPath.js:121:23:121:26 | path | -| TaintedPath.js:119:6:119:47 | path | TaintedPath.js:121:23:121:26 | path | -| TaintedPath.js:119:6:119:47 | path | TaintedPath.js:121:23:121:26 | path | -| TaintedPath.js:119:6:119:47 | path | TaintedPath.js:121:23:121:26 | path | -| TaintedPath.js:119:6:119:47 | path | TaintedPath.js:121:23:121:26 | path | -| TaintedPath.js:119:6:119:47 | path | TaintedPath.js:121:23:121:26 | path | -| TaintedPath.js:119:6:119:47 | path | TaintedPath.js:121:23:121:26 | path | -| TaintedPath.js:119:6:119:47 | path | TaintedPath.js:121:23:121:26 | path | -| TaintedPath.js:119:6:119:47 | path | TaintedPath.js:121:23:121:26 | path | -| TaintedPath.js:119:6:119:47 | path | TaintedPath.js:121:23:121:26 | path | -| TaintedPath.js:119:6:119:47 | path | TaintedPath.js:121:23:121:26 | path | -| TaintedPath.js:119:13:119:36 | url.par ... , true) | TaintedPath.js:119:13:119:42 | url.par ... ).query | -| TaintedPath.js:119:13:119:36 | url.par ... , true) | TaintedPath.js:119:13:119:42 | url.par ... ).query | -| TaintedPath.js:119:13:119:36 | url.par ... , true) | TaintedPath.js:119:13:119:42 | url.par ... ).query | -| TaintedPath.js:119:13:119:36 | url.par ... , true) | TaintedPath.js:119:13:119:42 | url.par ... ).query | -| TaintedPath.js:119:13:119:36 | url.par ... , true) | TaintedPath.js:119:13:119:42 | url.par ... ).query | -| TaintedPath.js:119:13:119:36 | url.par ... , true) | TaintedPath.js:119:13:119:42 | url.par ... ).query | -| TaintedPath.js:119:13:119:36 | url.par ... , true) | TaintedPath.js:119:13:119:42 | url.par ... ).query | -| TaintedPath.js:119:13:119:36 | url.par ... , true) | TaintedPath.js:119:13:119:42 | url.par ... ).query | -| TaintedPath.js:119:13:119:36 | url.par ... , true) | TaintedPath.js:119:13:119:42 | url.par ... ).query | -| TaintedPath.js:119:13:119:36 | url.par ... , true) | TaintedPath.js:119:13:119:42 | url.par ... ).query | -| TaintedPath.js:119:13:119:36 | url.par ... , true) | TaintedPath.js:119:13:119:42 | url.par ... ).query | -| TaintedPath.js:119:13:119:36 | url.par ... , true) | TaintedPath.js:119:13:119:42 | url.par ... ).query | -| TaintedPath.js:119:13:119:36 | url.par ... , true) | TaintedPath.js:119:13:119:42 | url.par ... ).query | -| TaintedPath.js:119:13:119:36 | url.par ... , true) | TaintedPath.js:119:13:119:42 | url.par ... ).query | -| TaintedPath.js:119:13:119:36 | url.par ... , true) | TaintedPath.js:119:13:119:42 | url.par ... ).query | -| TaintedPath.js:119:13:119:36 | url.par ... , true) | TaintedPath.js:119:13:119:42 | url.par ... ).query | -| TaintedPath.js:119:13:119:42 | url.par ... ).query | TaintedPath.js:119:13:119:47 | url.par ... ry.path | -| TaintedPath.js:119:13:119:42 | url.par ... ).query | TaintedPath.js:119:13:119:47 | url.par ... ry.path | -| TaintedPath.js:119:13:119:42 | url.par ... ).query | TaintedPath.js:119:13:119:47 | url.par ... ry.path | -| TaintedPath.js:119:13:119:42 | url.par ... ).query | TaintedPath.js:119:13:119:47 | url.par ... ry.path | -| TaintedPath.js:119:13:119:42 | url.par ... ).query | TaintedPath.js:119:13:119:47 | url.par ... ry.path | -| TaintedPath.js:119:13:119:42 | url.par ... ).query | TaintedPath.js:119:13:119:47 | url.par ... ry.path | -| TaintedPath.js:119:13:119:42 | url.par ... ).query | TaintedPath.js:119:13:119:47 | url.par ... ry.path | -| TaintedPath.js:119:13:119:42 | url.par ... ).query | TaintedPath.js:119:13:119:47 | url.par ... ry.path | -| TaintedPath.js:119:13:119:42 | url.par ... ).query | TaintedPath.js:119:13:119:47 | url.par ... ry.path | -| TaintedPath.js:119:13:119:42 | url.par ... ).query | TaintedPath.js:119:13:119:47 | url.par ... ry.path | -| TaintedPath.js:119:13:119:42 | url.par ... ).query | TaintedPath.js:119:13:119:47 | url.par ... ry.path | -| TaintedPath.js:119:13:119:42 | url.par ... ).query | TaintedPath.js:119:13:119:47 | url.par ... ry.path | -| TaintedPath.js:119:13:119:42 | url.par ... ).query | TaintedPath.js:119:13:119:47 | url.par ... ry.path | -| TaintedPath.js:119:13:119:42 | url.par ... ).query | TaintedPath.js:119:13:119:47 | url.par ... ry.path | -| TaintedPath.js:119:13:119:42 | url.par ... ).query | TaintedPath.js:119:13:119:47 | url.par ... ry.path | -| TaintedPath.js:119:13:119:42 | url.par ... ).query | TaintedPath.js:119:13:119:47 | url.par ... ry.path | -| TaintedPath.js:119:13:119:47 | url.par ... ry.path | TaintedPath.js:119:6:119:47 | path | -| TaintedPath.js:119:13:119:47 | url.par ... ry.path | TaintedPath.js:119:6:119:47 | path | -| TaintedPath.js:119:13:119:47 | url.par ... ry.path | TaintedPath.js:119:6:119:47 | path | -| TaintedPath.js:119:13:119:47 | url.par ... ry.path | TaintedPath.js:119:6:119:47 | path | -| TaintedPath.js:119:13:119:47 | url.par ... ry.path | TaintedPath.js:119:6:119:47 | path | -| TaintedPath.js:119:13:119:47 | url.par ... ry.path | TaintedPath.js:119:6:119:47 | path | -| TaintedPath.js:119:13:119:47 | url.par ... ry.path | TaintedPath.js:119:6:119:47 | path | -| TaintedPath.js:119:13:119:47 | url.par ... ry.path | TaintedPath.js:119:6:119:47 | path | -| TaintedPath.js:119:13:119:47 | url.par ... ry.path | TaintedPath.js:119:6:119:47 | path | -| TaintedPath.js:119:13:119:47 | url.par ... ry.path | TaintedPath.js:119:6:119:47 | path | -| TaintedPath.js:119:13:119:47 | url.par ... ry.path | TaintedPath.js:119:6:119:47 | path | -| TaintedPath.js:119:13:119:47 | url.par ... ry.path | TaintedPath.js:119:6:119:47 | path | -| TaintedPath.js:119:13:119:47 | url.par ... ry.path | TaintedPath.js:119:6:119:47 | path | -| TaintedPath.js:119:13:119:47 | url.par ... ry.path | TaintedPath.js:119:6:119:47 | path | -| TaintedPath.js:119:13:119:47 | url.par ... ry.path | TaintedPath.js:119:6:119:47 | path | -| TaintedPath.js:119:13:119:47 | url.par ... ry.path | TaintedPath.js:119:6:119:47 | path | -| TaintedPath.js:119:23:119:29 | req.url | TaintedPath.js:119:13:119:36 | url.par ... , true) | -| TaintedPath.js:119:23:119:29 | req.url | TaintedPath.js:119:13:119:36 | url.par ... , true) | -| TaintedPath.js:119:23:119:29 | req.url | TaintedPath.js:119:13:119:36 | url.par ... , true) | -| TaintedPath.js:119:23:119:29 | req.url | TaintedPath.js:119:13:119:36 | url.par ... , true) | -| TaintedPath.js:119:23:119:29 | req.url | TaintedPath.js:119:13:119:36 | url.par ... , true) | -| TaintedPath.js:119:23:119:29 | req.url | TaintedPath.js:119:13:119:36 | url.par ... , true) | -| TaintedPath.js:119:23:119:29 | req.url | TaintedPath.js:119:13:119:36 | url.par ... , true) | -| TaintedPath.js:119:23:119:29 | req.url | TaintedPath.js:119:13:119:36 | url.par ... , true) | -| TaintedPath.js:119:23:119:29 | req.url | TaintedPath.js:119:13:119:36 | url.par ... , true) | -| TaintedPath.js:119:23:119:29 | req.url | TaintedPath.js:119:13:119:36 | url.par ... , true) | -| TaintedPath.js:119:23:119:29 | req.url | TaintedPath.js:119:13:119:36 | url.par ... , true) | -| TaintedPath.js:119:23:119:29 | req.url | TaintedPath.js:119:13:119:36 | url.par ... , true) | -| TaintedPath.js:119:23:119:29 | req.url | TaintedPath.js:119:13:119:36 | url.par ... , true) | -| TaintedPath.js:119:23:119:29 | req.url | TaintedPath.js:119:13:119:36 | url.par ... , true) | -| TaintedPath.js:119:23:119:29 | req.url | TaintedPath.js:119:13:119:36 | url.par ... , true) | -| TaintedPath.js:119:23:119:29 | req.url | TaintedPath.js:119:13:119:36 | url.par ... , true) | -| TaintedPath.js:119:23:119:29 | req.url | TaintedPath.js:119:13:119:36 | url.par ... , true) | -| TaintedPath.js:119:23:119:29 | req.url | TaintedPath.js:119:13:119:36 | url.par ... , true) | -| TaintedPath.js:119:23:119:29 | req.url | TaintedPath.js:119:13:119:36 | url.par ... , true) | -| TaintedPath.js:119:23:119:29 | req.url | TaintedPath.js:119:13:119:36 | url.par ... , true) | -| TaintedPath.js:119:23:119:29 | req.url | TaintedPath.js:119:13:119:36 | url.par ... , true) | -| TaintedPath.js:119:23:119:29 | req.url | TaintedPath.js:119:13:119:36 | url.par ... , true) | -| TaintedPath.js:119:23:119:29 | req.url | TaintedPath.js:119:13:119:36 | url.par ... , true) | -| TaintedPath.js:119:23:119:29 | req.url | TaintedPath.js:119:13:119:36 | url.par ... , true) | -| TaintedPath.js:119:23:119:29 | req.url | TaintedPath.js:119:13:119:36 | url.par ... , true) | -| TaintedPath.js:119:23:119:29 | req.url | TaintedPath.js:119:13:119:36 | url.par ... , true) | -| TaintedPath.js:119:23:119:29 | req.url | TaintedPath.js:119:13:119:36 | url.par ... , true) | -| TaintedPath.js:119:23:119:29 | req.url | TaintedPath.js:119:13:119:36 | url.par ... , true) | -| TaintedPath.js:119:23:119:29 | req.url | TaintedPath.js:119:13:119:36 | url.par ... , true) | -| TaintedPath.js:119:23:119:29 | req.url | TaintedPath.js:119:13:119:36 | url.par ... , true) | -| TaintedPath.js:119:23:119:29 | req.url | TaintedPath.js:119:13:119:36 | url.par ... , true) | -| TaintedPath.js:119:23:119:29 | req.url | TaintedPath.js:119:13:119:36 | url.par ... , true) | +| TaintedPath.js:143:6:143:47 | path | TaintedPath.js:145:23:145:26 | path | +| TaintedPath.js:143:6:143:47 | path | TaintedPath.js:145:23:145:26 | path | +| TaintedPath.js:143:6:143:47 | path | TaintedPath.js:145:23:145:26 | path | +| TaintedPath.js:143:6:143:47 | path | TaintedPath.js:145:23:145:26 | path | +| TaintedPath.js:143:6:143:47 | path | TaintedPath.js:145:23:145:26 | path | +| TaintedPath.js:143:6:143:47 | path | TaintedPath.js:145:23:145:26 | path | +| TaintedPath.js:143:6:143:47 | path | TaintedPath.js:145:23:145:26 | path | +| TaintedPath.js:143:6:143:47 | path | TaintedPath.js:145:23:145:26 | path | +| TaintedPath.js:143:6:143:47 | path | TaintedPath.js:145:23:145:26 | path | +| TaintedPath.js:143:6:143:47 | path | TaintedPath.js:145:23:145:26 | path | +| TaintedPath.js:143:6:143:47 | path | TaintedPath.js:145:23:145:26 | path | +| TaintedPath.js:143:6:143:47 | path | TaintedPath.js:145:23:145:26 | path | +| TaintedPath.js:143:6:143:47 | path | TaintedPath.js:145:23:145:26 | path | +| TaintedPath.js:143:6:143:47 | path | TaintedPath.js:145:23:145:26 | path | +| TaintedPath.js:143:6:143:47 | path | TaintedPath.js:145:23:145:26 | path | +| TaintedPath.js:143:6:143:47 | path | TaintedPath.js:145:23:145:26 | path | +| TaintedPath.js:143:6:143:47 | path | TaintedPath.js:145:23:145:26 | path | +| TaintedPath.js:143:6:143:47 | path | TaintedPath.js:145:23:145:26 | path | +| TaintedPath.js:143:6:143:47 | path | TaintedPath.js:145:23:145:26 | path | +| TaintedPath.js:143:6:143:47 | path | TaintedPath.js:145:23:145:26 | path | +| TaintedPath.js:143:6:143:47 | path | TaintedPath.js:145:23:145:26 | path | +| TaintedPath.js:143:6:143:47 | path | TaintedPath.js:145:23:145:26 | path | +| TaintedPath.js:143:6:143:47 | path | TaintedPath.js:145:23:145:26 | path | +| TaintedPath.js:143:6:143:47 | path | TaintedPath.js:145:23:145:26 | path | +| TaintedPath.js:143:6:143:47 | path | TaintedPath.js:145:23:145:26 | path | +| TaintedPath.js:143:6:143:47 | path | TaintedPath.js:145:23:145:26 | path | +| TaintedPath.js:143:6:143:47 | path | TaintedPath.js:145:23:145:26 | path | +| TaintedPath.js:143:6:143:47 | path | TaintedPath.js:145:23:145:26 | path | +| TaintedPath.js:143:6:143:47 | path | TaintedPath.js:145:23:145:26 | path | +| TaintedPath.js:143:6:143:47 | path | TaintedPath.js:145:23:145:26 | path | +| TaintedPath.js:143:6:143:47 | path | TaintedPath.js:145:23:145:26 | path | +| TaintedPath.js:143:6:143:47 | path | TaintedPath.js:145:23:145:26 | path | +| TaintedPath.js:143:13:143:36 | url.par ... , true) | TaintedPath.js:143:13:143:42 | url.par ... ).query | +| TaintedPath.js:143:13:143:36 | url.par ... , true) | TaintedPath.js:143:13:143:42 | url.par ... ).query | +| TaintedPath.js:143:13:143:36 | url.par ... , true) | TaintedPath.js:143:13:143:42 | url.par ... ).query | +| TaintedPath.js:143:13:143:36 | url.par ... , true) | TaintedPath.js:143:13:143:42 | url.par ... ).query | +| TaintedPath.js:143:13:143:36 | url.par ... , true) | TaintedPath.js:143:13:143:42 | url.par ... ).query | +| TaintedPath.js:143:13:143:36 | url.par ... , true) | TaintedPath.js:143:13:143:42 | url.par ... ).query | +| TaintedPath.js:143:13:143:36 | url.par ... , true) | TaintedPath.js:143:13:143:42 | url.par ... ).query | +| TaintedPath.js:143:13:143:36 | url.par ... , true) | TaintedPath.js:143:13:143:42 | url.par ... ).query | +| TaintedPath.js:143:13:143:36 | url.par ... , true) | TaintedPath.js:143:13:143:42 | url.par ... ).query | +| TaintedPath.js:143:13:143:36 | url.par ... , true) | TaintedPath.js:143:13:143:42 | url.par ... ).query | +| TaintedPath.js:143:13:143:36 | url.par ... , true) | TaintedPath.js:143:13:143:42 | url.par ... ).query | +| TaintedPath.js:143:13:143:36 | url.par ... , true) | TaintedPath.js:143:13:143:42 | url.par ... ).query | +| TaintedPath.js:143:13:143:36 | url.par ... , true) | TaintedPath.js:143:13:143:42 | url.par ... ).query | +| TaintedPath.js:143:13:143:36 | url.par ... , true) | TaintedPath.js:143:13:143:42 | url.par ... ).query | +| TaintedPath.js:143:13:143:36 | url.par ... , true) | TaintedPath.js:143:13:143:42 | url.par ... ).query | +| TaintedPath.js:143:13:143:36 | url.par ... , true) | TaintedPath.js:143:13:143:42 | url.par ... ).query | +| TaintedPath.js:143:13:143:42 | url.par ... ).query | TaintedPath.js:143:13:143:47 | url.par ... ry.path | +| TaintedPath.js:143:13:143:42 | url.par ... ).query | TaintedPath.js:143:13:143:47 | url.par ... ry.path | +| TaintedPath.js:143:13:143:42 | url.par ... ).query | TaintedPath.js:143:13:143:47 | url.par ... ry.path | +| TaintedPath.js:143:13:143:42 | url.par ... ).query | TaintedPath.js:143:13:143:47 | url.par ... ry.path | +| TaintedPath.js:143:13:143:42 | url.par ... ).query | TaintedPath.js:143:13:143:47 | url.par ... ry.path | +| TaintedPath.js:143:13:143:42 | url.par ... ).query | TaintedPath.js:143:13:143:47 | url.par ... ry.path | +| TaintedPath.js:143:13:143:42 | url.par ... ).query | TaintedPath.js:143:13:143:47 | url.par ... ry.path | +| TaintedPath.js:143:13:143:42 | url.par ... ).query | TaintedPath.js:143:13:143:47 | url.par ... ry.path | +| TaintedPath.js:143:13:143:42 | url.par ... ).query | TaintedPath.js:143:13:143:47 | url.par ... ry.path | +| TaintedPath.js:143:13:143:42 | url.par ... ).query | TaintedPath.js:143:13:143:47 | url.par ... ry.path | +| TaintedPath.js:143:13:143:42 | url.par ... ).query | TaintedPath.js:143:13:143:47 | url.par ... ry.path | +| TaintedPath.js:143:13:143:42 | url.par ... ).query | TaintedPath.js:143:13:143:47 | url.par ... ry.path | +| TaintedPath.js:143:13:143:42 | url.par ... ).query | TaintedPath.js:143:13:143:47 | url.par ... ry.path | +| TaintedPath.js:143:13:143:42 | url.par ... ).query | TaintedPath.js:143:13:143:47 | url.par ... ry.path | +| TaintedPath.js:143:13:143:42 | url.par ... ).query | TaintedPath.js:143:13:143:47 | url.par ... ry.path | +| TaintedPath.js:143:13:143:42 | url.par ... ).query | TaintedPath.js:143:13:143:47 | url.par ... ry.path | +| TaintedPath.js:143:13:143:47 | url.par ... ry.path | TaintedPath.js:143:6:143:47 | path | +| TaintedPath.js:143:13:143:47 | url.par ... ry.path | TaintedPath.js:143:6:143:47 | path | +| TaintedPath.js:143:13:143:47 | url.par ... ry.path | TaintedPath.js:143:6:143:47 | path | +| TaintedPath.js:143:13:143:47 | url.par ... ry.path | TaintedPath.js:143:6:143:47 | path | +| TaintedPath.js:143:13:143:47 | url.par ... ry.path | TaintedPath.js:143:6:143:47 | path | +| TaintedPath.js:143:13:143:47 | url.par ... ry.path | TaintedPath.js:143:6:143:47 | path | +| TaintedPath.js:143:13:143:47 | url.par ... ry.path | TaintedPath.js:143:6:143:47 | path | +| TaintedPath.js:143:13:143:47 | url.par ... ry.path | TaintedPath.js:143:6:143:47 | path | +| TaintedPath.js:143:13:143:47 | url.par ... ry.path | TaintedPath.js:143:6:143:47 | path | +| TaintedPath.js:143:13:143:47 | url.par ... ry.path | TaintedPath.js:143:6:143:47 | path | +| TaintedPath.js:143:13:143:47 | url.par ... ry.path | TaintedPath.js:143:6:143:47 | path | +| TaintedPath.js:143:13:143:47 | url.par ... ry.path | TaintedPath.js:143:6:143:47 | path | +| TaintedPath.js:143:13:143:47 | url.par ... ry.path | TaintedPath.js:143:6:143:47 | path | +| TaintedPath.js:143:13:143:47 | url.par ... ry.path | TaintedPath.js:143:6:143:47 | path | +| TaintedPath.js:143:13:143:47 | url.par ... ry.path | TaintedPath.js:143:6:143:47 | path | +| TaintedPath.js:143:13:143:47 | url.par ... ry.path | TaintedPath.js:143:6:143:47 | path | +| TaintedPath.js:143:23:143:29 | req.url | TaintedPath.js:143:13:143:36 | url.par ... , true) | +| TaintedPath.js:143:23:143:29 | req.url | TaintedPath.js:143:13:143:36 | url.par ... , true) | +| TaintedPath.js:143:23:143:29 | req.url | TaintedPath.js:143:13:143:36 | url.par ... , true) | +| TaintedPath.js:143:23:143:29 | req.url | TaintedPath.js:143:13:143:36 | url.par ... , true) | +| TaintedPath.js:143:23:143:29 | req.url | TaintedPath.js:143:13:143:36 | url.par ... , true) | +| TaintedPath.js:143:23:143:29 | req.url | TaintedPath.js:143:13:143:36 | url.par ... , true) | +| TaintedPath.js:143:23:143:29 | req.url | TaintedPath.js:143:13:143:36 | url.par ... , true) | +| TaintedPath.js:143:23:143:29 | req.url | TaintedPath.js:143:13:143:36 | url.par ... , true) | +| TaintedPath.js:143:23:143:29 | req.url | TaintedPath.js:143:13:143:36 | url.par ... , true) | +| TaintedPath.js:143:23:143:29 | req.url | TaintedPath.js:143:13:143:36 | url.par ... , true) | +| TaintedPath.js:143:23:143:29 | req.url | TaintedPath.js:143:13:143:36 | url.par ... , true) | +| TaintedPath.js:143:23:143:29 | req.url | TaintedPath.js:143:13:143:36 | url.par ... , true) | +| TaintedPath.js:143:23:143:29 | req.url | TaintedPath.js:143:13:143:36 | url.par ... , true) | +| TaintedPath.js:143:23:143:29 | req.url | TaintedPath.js:143:13:143:36 | url.par ... , true) | +| TaintedPath.js:143:23:143:29 | req.url | TaintedPath.js:143:13:143:36 | url.par ... , true) | +| TaintedPath.js:143:23:143:29 | req.url | TaintedPath.js:143:13:143:36 | url.par ... , true) | +| TaintedPath.js:143:23:143:29 | req.url | TaintedPath.js:143:13:143:36 | url.par ... , true) | +| TaintedPath.js:143:23:143:29 | req.url | TaintedPath.js:143:13:143:36 | url.par ... , true) | +| TaintedPath.js:143:23:143:29 | req.url | TaintedPath.js:143:13:143:36 | url.par ... , true) | +| TaintedPath.js:143:23:143:29 | req.url | TaintedPath.js:143:13:143:36 | url.par ... , true) | +| TaintedPath.js:143:23:143:29 | req.url | TaintedPath.js:143:13:143:36 | url.par ... , true) | +| TaintedPath.js:143:23:143:29 | req.url | TaintedPath.js:143:13:143:36 | url.par ... , true) | +| TaintedPath.js:143:23:143:29 | req.url | TaintedPath.js:143:13:143:36 | url.par ... , true) | +| TaintedPath.js:143:23:143:29 | req.url | TaintedPath.js:143:13:143:36 | url.par ... , true) | +| TaintedPath.js:143:23:143:29 | req.url | TaintedPath.js:143:13:143:36 | url.par ... , true) | +| TaintedPath.js:143:23:143:29 | req.url | TaintedPath.js:143:13:143:36 | url.par ... , true) | +| TaintedPath.js:143:23:143:29 | req.url | TaintedPath.js:143:13:143:36 | url.par ... , true) | +| TaintedPath.js:143:23:143:29 | req.url | TaintedPath.js:143:13:143:36 | url.par ... , true) | +| TaintedPath.js:143:23:143:29 | req.url | TaintedPath.js:143:13:143:36 | url.par ... , true) | +| TaintedPath.js:143:23:143:29 | req.url | TaintedPath.js:143:13:143:36 | url.par ... , true) | +| TaintedPath.js:143:23:143:29 | req.url | TaintedPath.js:143:13:143:36 | url.par ... , true) | +| TaintedPath.js:143:23:143:29 | req.url | TaintedPath.js:143:13:143:36 | url.par ... , true) | +| TaintedPath.js:149:7:149:48 | path | TaintedPath.js:151:19:151:22 | path | +| TaintedPath.js:149:7:149:48 | path | TaintedPath.js:151:19:151:22 | path | +| TaintedPath.js:149:7:149:48 | path | TaintedPath.js:151:19:151:22 | path | +| TaintedPath.js:149:7:149:48 | path | TaintedPath.js:151:19:151:22 | path | +| TaintedPath.js:149:7:149:48 | path | TaintedPath.js:151:19:151:22 | path | +| TaintedPath.js:149:7:149:48 | path | TaintedPath.js:151:19:151:22 | path | +| TaintedPath.js:149:7:149:48 | path | TaintedPath.js:151:19:151:22 | path | +| TaintedPath.js:149:7:149:48 | path | TaintedPath.js:151:19:151:22 | path | +| TaintedPath.js:149:7:149:48 | path | TaintedPath.js:151:19:151:22 | path | +| TaintedPath.js:149:7:149:48 | path | TaintedPath.js:151:19:151:22 | path | +| TaintedPath.js:149:7:149:48 | path | TaintedPath.js:151:19:151:22 | path | +| TaintedPath.js:149:7:149:48 | path | TaintedPath.js:151:19:151:22 | path | +| TaintedPath.js:149:7:149:48 | path | TaintedPath.js:151:19:151:22 | path | +| TaintedPath.js:149:7:149:48 | path | TaintedPath.js:151:19:151:22 | path | +| TaintedPath.js:149:7:149:48 | path | TaintedPath.js:151:19:151:22 | path | +| TaintedPath.js:149:7:149:48 | path | TaintedPath.js:151:19:151:22 | path | +| TaintedPath.js:149:7:149:48 | path | TaintedPath.js:151:19:151:22 | path | +| TaintedPath.js:149:7:149:48 | path | TaintedPath.js:151:19:151:22 | path | +| TaintedPath.js:149:7:149:48 | path | TaintedPath.js:151:19:151:22 | path | +| TaintedPath.js:149:7:149:48 | path | TaintedPath.js:151:19:151:22 | path | +| TaintedPath.js:149:7:149:48 | path | TaintedPath.js:151:19:151:22 | path | +| TaintedPath.js:149:7:149:48 | path | TaintedPath.js:151:19:151:22 | path | +| TaintedPath.js:149:7:149:48 | path | TaintedPath.js:151:19:151:22 | path | +| TaintedPath.js:149:7:149:48 | path | TaintedPath.js:151:19:151:22 | path | +| TaintedPath.js:149:7:149:48 | path | TaintedPath.js:151:19:151:22 | path | +| TaintedPath.js:149:7:149:48 | path | TaintedPath.js:151:19:151:22 | path | +| TaintedPath.js:149:7:149:48 | path | TaintedPath.js:151:19:151:22 | path | +| TaintedPath.js:149:7:149:48 | path | TaintedPath.js:151:19:151:22 | path | +| TaintedPath.js:149:7:149:48 | path | TaintedPath.js:151:19:151:22 | path | +| TaintedPath.js:149:7:149:48 | path | TaintedPath.js:151:19:151:22 | path | +| TaintedPath.js:149:7:149:48 | path | TaintedPath.js:151:19:151:22 | path | +| TaintedPath.js:149:7:149:48 | path | TaintedPath.js:151:19:151:22 | path | +| TaintedPath.js:149:7:149:48 | path | TaintedPath.js:153:15:153:18 | path | +| TaintedPath.js:149:7:149:48 | path | TaintedPath.js:153:15:153:18 | path | +| TaintedPath.js:149:7:149:48 | path | TaintedPath.js:153:15:153:18 | path | +| TaintedPath.js:149:7:149:48 | path | TaintedPath.js:153:15:153:18 | path | +| TaintedPath.js:149:7:149:48 | path | TaintedPath.js:153:15:153:18 | path | +| TaintedPath.js:149:7:149:48 | path | TaintedPath.js:153:15:153:18 | path | +| TaintedPath.js:149:7:149:48 | path | TaintedPath.js:153:15:153:18 | path | +| TaintedPath.js:149:7:149:48 | path | TaintedPath.js:153:15:153:18 | path | +| TaintedPath.js:149:7:149:48 | path | TaintedPath.js:153:15:153:18 | path | +| TaintedPath.js:149:7:149:48 | path | TaintedPath.js:153:15:153:18 | path | +| TaintedPath.js:149:7:149:48 | path | TaintedPath.js:153:15:153:18 | path | +| TaintedPath.js:149:7:149:48 | path | TaintedPath.js:153:15:153:18 | path | +| TaintedPath.js:149:14:149:37 | url.par ... , true) | TaintedPath.js:149:14:149:43 | url.par ... ).query | +| TaintedPath.js:149:14:149:37 | url.par ... , true) | TaintedPath.js:149:14:149:43 | url.par ... ).query | +| TaintedPath.js:149:14:149:37 | url.par ... , true) | TaintedPath.js:149:14:149:43 | url.par ... ).query | +| TaintedPath.js:149:14:149:37 | url.par ... , true) | TaintedPath.js:149:14:149:43 | url.par ... ).query | +| TaintedPath.js:149:14:149:37 | url.par ... , true) | TaintedPath.js:149:14:149:43 | url.par ... ).query | +| TaintedPath.js:149:14:149:37 | url.par ... , true) | TaintedPath.js:149:14:149:43 | url.par ... ).query | +| TaintedPath.js:149:14:149:37 | url.par ... , true) | TaintedPath.js:149:14:149:43 | url.par ... ).query | +| TaintedPath.js:149:14:149:37 | url.par ... , true) | TaintedPath.js:149:14:149:43 | url.par ... ).query | +| TaintedPath.js:149:14:149:37 | url.par ... , true) | TaintedPath.js:149:14:149:43 | url.par ... ).query | +| TaintedPath.js:149:14:149:37 | url.par ... , true) | TaintedPath.js:149:14:149:43 | url.par ... ).query | +| TaintedPath.js:149:14:149:37 | url.par ... , true) | TaintedPath.js:149:14:149:43 | url.par ... ).query | +| TaintedPath.js:149:14:149:37 | url.par ... , true) | TaintedPath.js:149:14:149:43 | url.par ... ).query | +| TaintedPath.js:149:14:149:37 | url.par ... , true) | TaintedPath.js:149:14:149:43 | url.par ... ).query | +| TaintedPath.js:149:14:149:37 | url.par ... , true) | TaintedPath.js:149:14:149:43 | url.par ... ).query | +| TaintedPath.js:149:14:149:37 | url.par ... , true) | TaintedPath.js:149:14:149:43 | url.par ... ).query | +| TaintedPath.js:149:14:149:37 | url.par ... , true) | TaintedPath.js:149:14:149:43 | url.par ... ).query | +| TaintedPath.js:149:14:149:43 | url.par ... ).query | TaintedPath.js:149:14:149:48 | url.par ... ry.path | +| TaintedPath.js:149:14:149:43 | url.par ... ).query | TaintedPath.js:149:14:149:48 | url.par ... ry.path | +| TaintedPath.js:149:14:149:43 | url.par ... ).query | TaintedPath.js:149:14:149:48 | url.par ... ry.path | +| TaintedPath.js:149:14:149:43 | url.par ... ).query | TaintedPath.js:149:14:149:48 | url.par ... ry.path | +| TaintedPath.js:149:14:149:43 | url.par ... ).query | TaintedPath.js:149:14:149:48 | url.par ... ry.path | +| TaintedPath.js:149:14:149:43 | url.par ... ).query | TaintedPath.js:149:14:149:48 | url.par ... ry.path | +| TaintedPath.js:149:14:149:43 | url.par ... ).query | TaintedPath.js:149:14:149:48 | url.par ... ry.path | +| TaintedPath.js:149:14:149:43 | url.par ... ).query | TaintedPath.js:149:14:149:48 | url.par ... ry.path | +| TaintedPath.js:149:14:149:43 | url.par ... ).query | TaintedPath.js:149:14:149:48 | url.par ... ry.path | +| TaintedPath.js:149:14:149:43 | url.par ... ).query | TaintedPath.js:149:14:149:48 | url.par ... ry.path | +| TaintedPath.js:149:14:149:43 | url.par ... ).query | TaintedPath.js:149:14:149:48 | url.par ... ry.path | +| TaintedPath.js:149:14:149:43 | url.par ... ).query | TaintedPath.js:149:14:149:48 | url.par ... ry.path | +| TaintedPath.js:149:14:149:43 | url.par ... ).query | TaintedPath.js:149:14:149:48 | url.par ... ry.path | +| TaintedPath.js:149:14:149:43 | url.par ... ).query | TaintedPath.js:149:14:149:48 | url.par ... ry.path | +| TaintedPath.js:149:14:149:43 | url.par ... ).query | TaintedPath.js:149:14:149:48 | url.par ... ry.path | +| TaintedPath.js:149:14:149:43 | url.par ... ).query | TaintedPath.js:149:14:149:48 | url.par ... ry.path | +| TaintedPath.js:149:14:149:48 | url.par ... ry.path | TaintedPath.js:149:7:149:48 | path | +| TaintedPath.js:149:14:149:48 | url.par ... ry.path | TaintedPath.js:149:7:149:48 | path | +| TaintedPath.js:149:14:149:48 | url.par ... ry.path | TaintedPath.js:149:7:149:48 | path | +| TaintedPath.js:149:14:149:48 | url.par ... ry.path | TaintedPath.js:149:7:149:48 | path | +| TaintedPath.js:149:14:149:48 | url.par ... ry.path | TaintedPath.js:149:7:149:48 | path | +| TaintedPath.js:149:14:149:48 | url.par ... ry.path | TaintedPath.js:149:7:149:48 | path | +| TaintedPath.js:149:14:149:48 | url.par ... ry.path | TaintedPath.js:149:7:149:48 | path | +| TaintedPath.js:149:14:149:48 | url.par ... ry.path | TaintedPath.js:149:7:149:48 | path | +| TaintedPath.js:149:14:149:48 | url.par ... ry.path | TaintedPath.js:149:7:149:48 | path | +| TaintedPath.js:149:14:149:48 | url.par ... ry.path | TaintedPath.js:149:7:149:48 | path | +| TaintedPath.js:149:14:149:48 | url.par ... ry.path | TaintedPath.js:149:7:149:48 | path | +| TaintedPath.js:149:14:149:48 | url.par ... ry.path | TaintedPath.js:149:7:149:48 | path | +| TaintedPath.js:149:14:149:48 | url.par ... ry.path | TaintedPath.js:149:7:149:48 | path | +| TaintedPath.js:149:14:149:48 | url.par ... ry.path | TaintedPath.js:149:7:149:48 | path | +| TaintedPath.js:149:14:149:48 | url.par ... ry.path | TaintedPath.js:149:7:149:48 | path | +| TaintedPath.js:149:14:149:48 | url.par ... ry.path | TaintedPath.js:149:7:149:48 | path | +| TaintedPath.js:149:24:149:30 | req.url | TaintedPath.js:149:14:149:37 | url.par ... , true) | +| TaintedPath.js:149:24:149:30 | req.url | TaintedPath.js:149:14:149:37 | url.par ... , true) | +| TaintedPath.js:149:24:149:30 | req.url | TaintedPath.js:149:14:149:37 | url.par ... , true) | +| TaintedPath.js:149:24:149:30 | req.url | TaintedPath.js:149:14:149:37 | url.par ... , true) | +| TaintedPath.js:149:24:149:30 | req.url | TaintedPath.js:149:14:149:37 | url.par ... , true) | +| TaintedPath.js:149:24:149:30 | req.url | TaintedPath.js:149:14:149:37 | url.par ... , true) | +| TaintedPath.js:149:24:149:30 | req.url | TaintedPath.js:149:14:149:37 | url.par ... , true) | +| TaintedPath.js:149:24:149:30 | req.url | TaintedPath.js:149:14:149:37 | url.par ... , true) | +| TaintedPath.js:149:24:149:30 | req.url | TaintedPath.js:149:14:149:37 | url.par ... , true) | +| TaintedPath.js:149:24:149:30 | req.url | TaintedPath.js:149:14:149:37 | url.par ... , true) | +| TaintedPath.js:149:24:149:30 | req.url | TaintedPath.js:149:14:149:37 | url.par ... , true) | +| TaintedPath.js:149:24:149:30 | req.url | TaintedPath.js:149:14:149:37 | url.par ... , true) | +| TaintedPath.js:149:24:149:30 | req.url | TaintedPath.js:149:14:149:37 | url.par ... , true) | +| TaintedPath.js:149:24:149:30 | req.url | TaintedPath.js:149:14:149:37 | url.par ... , true) | +| TaintedPath.js:149:24:149:30 | req.url | TaintedPath.js:149:14:149:37 | url.par ... , true) | +| TaintedPath.js:149:24:149:30 | req.url | TaintedPath.js:149:14:149:37 | url.par ... , true) | +| TaintedPath.js:149:24:149:30 | req.url | TaintedPath.js:149:14:149:37 | url.par ... , true) | +| TaintedPath.js:149:24:149:30 | req.url | TaintedPath.js:149:14:149:37 | url.par ... , true) | +| TaintedPath.js:149:24:149:30 | req.url | TaintedPath.js:149:14:149:37 | url.par ... , true) | +| TaintedPath.js:149:24:149:30 | req.url | TaintedPath.js:149:14:149:37 | url.par ... , true) | +| TaintedPath.js:149:24:149:30 | req.url | TaintedPath.js:149:14:149:37 | url.par ... , true) | +| TaintedPath.js:149:24:149:30 | req.url | TaintedPath.js:149:14:149:37 | url.par ... , true) | +| TaintedPath.js:149:24:149:30 | req.url | TaintedPath.js:149:14:149:37 | url.par ... , true) | +| TaintedPath.js:149:24:149:30 | req.url | TaintedPath.js:149:14:149:37 | url.par ... , true) | +| TaintedPath.js:149:24:149:30 | req.url | TaintedPath.js:149:14:149:37 | url.par ... , true) | +| TaintedPath.js:149:24:149:30 | req.url | TaintedPath.js:149:14:149:37 | url.par ... , true) | +| TaintedPath.js:149:24:149:30 | req.url | TaintedPath.js:149:14:149:37 | url.par ... , true) | +| TaintedPath.js:149:24:149:30 | req.url | TaintedPath.js:149:14:149:37 | url.par ... , true) | +| TaintedPath.js:149:24:149:30 | req.url | TaintedPath.js:149:14:149:37 | url.par ... , true) | +| TaintedPath.js:149:24:149:30 | req.url | TaintedPath.js:149:14:149:37 | url.par ... , true) | +| TaintedPath.js:149:24:149:30 | req.url | TaintedPath.js:149:14:149:37 | url.par ... , true) | +| TaintedPath.js:149:24:149:30 | req.url | TaintedPath.js:149:14:149:37 | url.par ... , true) | +| TaintedPath.js:153:7:153:29 | split | TaintedPath.js:155:19:155:23 | split | +| TaintedPath.js:153:7:153:29 | split | TaintedPath.js:155:19:155:23 | split | +| TaintedPath.js:153:7:153:29 | split | TaintedPath.js:155:19:155:23 | split | +| TaintedPath.js:153:7:153:29 | split | TaintedPath.js:155:19:155:23 | split | +| TaintedPath.js:153:7:153:29 | split | TaintedPath.js:159:19:159:23 | split | +| TaintedPath.js:153:7:153:29 | split | TaintedPath.js:159:19:159:23 | split | +| TaintedPath.js:153:7:153:29 | split | TaintedPath.js:159:19:159:23 | split | +| TaintedPath.js:153:7:153:29 | split | TaintedPath.js:159:19:159:23 | split | +| TaintedPath.js:153:7:153:29 | split | TaintedPath.js:160:28:160:32 | split | +| TaintedPath.js:153:7:153:29 | split | TaintedPath.js:160:28:160:32 | split | +| TaintedPath.js:153:7:153:29 | split | TaintedPath.js:160:28:160:32 | split | +| TaintedPath.js:153:7:153:29 | split | TaintedPath.js:160:28:160:32 | split | +| TaintedPath.js:153:7:153:29 | split | TaintedPath.js:162:33:162:37 | split | +| TaintedPath.js:153:7:153:29 | split | TaintedPath.js:162:33:162:37 | split | +| TaintedPath.js:153:7:153:29 | split | TaintedPath.js:162:33:162:37 | split | +| TaintedPath.js:153:7:153:29 | split | TaintedPath.js:162:33:162:37 | split | +| TaintedPath.js:153:7:153:29 | split | TaintedPath.js:165:20:165:24 | split | +| TaintedPath.js:153:7:153:29 | split | TaintedPath.js:165:20:165:24 | split | +| TaintedPath.js:153:7:153:29 | split | TaintedPath.js:165:20:165:24 | split | +| TaintedPath.js:153:7:153:29 | split | TaintedPath.js:165:20:165:24 | split | +| TaintedPath.js:153:7:153:29 | split | TaintedPath.js:168:19:168:23 | split | +| TaintedPath.js:153:7:153:29 | split | TaintedPath.js:168:19:168:23 | split | +| TaintedPath.js:153:7:153:29 | split | TaintedPath.js:168:19:168:23 | split | +| TaintedPath.js:153:7:153:29 | split | TaintedPath.js:168:19:168:23 | split | +| TaintedPath.js:153:15:153:18 | path | TaintedPath.js:153:15:153:29 | path.split("/") | +| TaintedPath.js:153:15:153:18 | path | TaintedPath.js:153:15:153:29 | path.split("/") | +| TaintedPath.js:153:15:153:18 | path | TaintedPath.js:153:15:153:29 | path.split("/") | +| TaintedPath.js:153:15:153:18 | path | TaintedPath.js:153:15:153:29 | path.split("/") | +| TaintedPath.js:153:15:153:18 | path | TaintedPath.js:153:15:153:29 | path.split("/") | +| TaintedPath.js:153:15:153:18 | path | TaintedPath.js:153:15:153:29 | path.split("/") | +| TaintedPath.js:153:15:153:18 | path | TaintedPath.js:153:15:153:29 | path.split("/") | +| TaintedPath.js:153:15:153:18 | path | TaintedPath.js:153:15:153:29 | path.split("/") | +| TaintedPath.js:153:15:153:18 | path | TaintedPath.js:153:15:153:29 | path.split("/") | +| TaintedPath.js:153:15:153:18 | path | TaintedPath.js:153:15:153:29 | path.split("/") | +| TaintedPath.js:153:15:153:18 | path | TaintedPath.js:153:15:153:29 | path.split("/") | +| TaintedPath.js:153:15:153:18 | path | TaintedPath.js:153:15:153:29 | path.split("/") | +| TaintedPath.js:153:15:153:29 | path.split("/") | TaintedPath.js:153:7:153:29 | split | +| TaintedPath.js:153:15:153:29 | path.split("/") | TaintedPath.js:153:7:153:29 | split | +| TaintedPath.js:153:15:153:29 | path.split("/") | TaintedPath.js:153:7:153:29 | split | +| TaintedPath.js:153:15:153:29 | path.split("/") | TaintedPath.js:153:7:153:29 | split | +| TaintedPath.js:155:19:155:23 | split | TaintedPath.js:155:19:155:33 | split.join("/") | +| TaintedPath.js:155:19:155:23 | split | TaintedPath.js:155:19:155:33 | split.join("/") | +| TaintedPath.js:155:19:155:23 | split | TaintedPath.js:155:19:155:33 | split.join("/") | +| TaintedPath.js:155:19:155:23 | split | TaintedPath.js:155:19:155:33 | split.join("/") | +| TaintedPath.js:155:19:155:23 | split | TaintedPath.js:155:19:155:33 | split.join("/") | +| TaintedPath.js:155:19:155:23 | split | TaintedPath.js:155:19:155:33 | split.join("/") | +| TaintedPath.js:155:19:155:23 | split | TaintedPath.js:155:19:155:33 | split.join("/") | +| TaintedPath.js:155:19:155:23 | split | TaintedPath.js:155:19:155:33 | split.join("/") | +| TaintedPath.js:155:19:155:23 | split | TaintedPath.js:155:19:155:33 | split.join("/") | +| TaintedPath.js:155:19:155:23 | split | TaintedPath.js:155:19:155:33 | split.join("/") | +| TaintedPath.js:155:19:155:23 | split | TaintedPath.js:155:19:155:33 | split.join("/") | +| TaintedPath.js:155:19:155:23 | split | TaintedPath.js:155:19:155:33 | split.join("/") | +| TaintedPath.js:155:19:155:23 | split | TaintedPath.js:155:19:155:33 | split.join("/") | +| TaintedPath.js:155:19:155:23 | split | TaintedPath.js:155:19:155:33 | split.join("/") | +| TaintedPath.js:155:19:155:23 | split | TaintedPath.js:155:19:155:33 | split.join("/") | +| TaintedPath.js:155:19:155:23 | split | TaintedPath.js:155:19:155:33 | split.join("/") | +| TaintedPath.js:159:19:159:23 | split | TaintedPath.js:159:19:159:26 | split[x] | +| TaintedPath.js:159:19:159:23 | split | TaintedPath.js:159:19:159:26 | split[x] | +| TaintedPath.js:159:19:159:23 | split | TaintedPath.js:159:19:159:26 | split[x] | +| TaintedPath.js:159:19:159:23 | split | TaintedPath.js:159:19:159:26 | split[x] | +| TaintedPath.js:159:19:159:23 | split | TaintedPath.js:159:19:159:26 | split[x] | +| TaintedPath.js:159:19:159:23 | split | TaintedPath.js:159:19:159:26 | split[x] | +| TaintedPath.js:159:19:159:23 | split | TaintedPath.js:159:19:159:26 | split[x] | +| TaintedPath.js:159:19:159:23 | split | TaintedPath.js:159:19:159:26 | split[x] | +| TaintedPath.js:159:19:159:23 | split | TaintedPath.js:159:19:159:26 | split[x] | +| TaintedPath.js:159:19:159:23 | split | TaintedPath.js:159:19:159:26 | split[x] | +| TaintedPath.js:159:19:159:23 | split | TaintedPath.js:159:19:159:26 | split[x] | +| TaintedPath.js:159:19:159:23 | split | TaintedPath.js:159:19:159:26 | split[x] | +| TaintedPath.js:159:19:159:23 | split | TaintedPath.js:159:19:159:26 | split[x] | +| TaintedPath.js:159:19:159:23 | split | TaintedPath.js:159:19:159:26 | split[x] | +| TaintedPath.js:159:19:159:23 | split | TaintedPath.js:159:19:159:26 | split[x] | +| TaintedPath.js:159:19:159:23 | split | TaintedPath.js:159:19:159:26 | split[x] | +| TaintedPath.js:160:28:160:32 | split | TaintedPath.js:160:28:160:35 | split[x] | +| TaintedPath.js:160:28:160:32 | split | TaintedPath.js:160:28:160:35 | split[x] | +| TaintedPath.js:160:28:160:32 | split | TaintedPath.js:160:28:160:35 | split[x] | +| TaintedPath.js:160:28:160:32 | split | TaintedPath.js:160:28:160:35 | split[x] | +| TaintedPath.js:160:28:160:32 | split | TaintedPath.js:160:28:160:35 | split[x] | +| TaintedPath.js:160:28:160:32 | split | TaintedPath.js:160:28:160:35 | split[x] | +| TaintedPath.js:160:28:160:32 | split | TaintedPath.js:160:28:160:35 | split[x] | +| TaintedPath.js:160:28:160:32 | split | TaintedPath.js:160:28:160:35 | split[x] | +| TaintedPath.js:160:28:160:32 | split | TaintedPath.js:160:28:160:35 | split[x] | +| TaintedPath.js:160:28:160:32 | split | TaintedPath.js:160:28:160:35 | split[x] | +| TaintedPath.js:160:28:160:32 | split | TaintedPath.js:160:28:160:35 | split[x] | +| TaintedPath.js:160:28:160:32 | split | TaintedPath.js:160:28:160:35 | split[x] | +| TaintedPath.js:160:28:160:35 | split[x] | TaintedPath.js:160:19:160:35 | prefix + split[x] | +| TaintedPath.js:160:28:160:35 | split[x] | TaintedPath.js:160:19:160:35 | prefix + split[x] | +| TaintedPath.js:160:28:160:35 | split[x] | TaintedPath.js:160:19:160:35 | prefix + split[x] | +| TaintedPath.js:160:28:160:35 | split[x] | TaintedPath.js:160:19:160:35 | prefix + split[x] | +| TaintedPath.js:160:28:160:35 | split[x] | TaintedPath.js:160:19:160:35 | prefix + split[x] | +| TaintedPath.js:160:28:160:35 | split[x] | TaintedPath.js:160:19:160:35 | prefix + split[x] | +| TaintedPath.js:160:28:160:35 | split[x] | TaintedPath.js:160:19:160:35 | prefix + split[x] | +| TaintedPath.js:160:28:160:35 | split[x] | TaintedPath.js:160:19:160:35 | prefix + split[x] | +| TaintedPath.js:160:28:160:35 | split[x] | TaintedPath.js:160:19:160:35 | prefix + split[x] | +| TaintedPath.js:160:28:160:35 | split[x] | TaintedPath.js:160:19:160:35 | prefix + split[x] | +| TaintedPath.js:160:28:160:35 | split[x] | TaintedPath.js:160:19:160:35 | prefix + split[x] | +| TaintedPath.js:160:28:160:35 | split[x] | TaintedPath.js:160:19:160:35 | prefix + split[x] | +| TaintedPath.js:160:28:160:35 | split[x] | TaintedPath.js:160:19:160:35 | prefix + split[x] | +| TaintedPath.js:160:28:160:35 | split[x] | TaintedPath.js:160:19:160:35 | prefix + split[x] | +| TaintedPath.js:160:28:160:35 | split[x] | TaintedPath.js:160:19:160:35 | prefix + split[x] | +| TaintedPath.js:160:28:160:35 | split[x] | TaintedPath.js:160:19:160:35 | prefix + split[x] | +| TaintedPath.js:160:28:160:35 | split[x] | TaintedPath.js:160:19:160:35 | prefix + split[x] | +| TaintedPath.js:160:28:160:35 | split[x] | TaintedPath.js:160:19:160:35 | prefix + split[x] | +| TaintedPath.js:160:28:160:35 | split[x] | TaintedPath.js:160:19:160:35 | prefix + split[x] | +| TaintedPath.js:160:28:160:35 | split[x] | TaintedPath.js:160:19:160:35 | prefix + split[x] | +| TaintedPath.js:160:28:160:35 | split[x] | TaintedPath.js:160:19:160:35 | prefix + split[x] | +| TaintedPath.js:160:28:160:35 | split[x] | TaintedPath.js:160:19:160:35 | prefix + split[x] | +| TaintedPath.js:160:28:160:35 | split[x] | TaintedPath.js:160:19:160:35 | prefix + split[x] | +| TaintedPath.js:160:28:160:35 | split[x] | TaintedPath.js:160:19:160:35 | prefix + split[x] | +| TaintedPath.js:162:7:162:38 | concatted | TaintedPath.js:163:19:163:27 | concatted | +| TaintedPath.js:162:7:162:38 | concatted | TaintedPath.js:163:19:163:27 | concatted | +| TaintedPath.js:162:7:162:38 | concatted | TaintedPath.js:163:19:163:27 | concatted | +| TaintedPath.js:162:7:162:38 | concatted | TaintedPath.js:163:19:163:27 | concatted | +| TaintedPath.js:162:19:162:38 | prefix.concat(split) | TaintedPath.js:162:7:162:38 | concatted | +| TaintedPath.js:162:19:162:38 | prefix.concat(split) | TaintedPath.js:162:7:162:38 | concatted | +| TaintedPath.js:162:19:162:38 | prefix.concat(split) | TaintedPath.js:162:7:162:38 | concatted | +| TaintedPath.js:162:19:162:38 | prefix.concat(split) | TaintedPath.js:162:7:162:38 | concatted | +| TaintedPath.js:162:33:162:37 | split | TaintedPath.js:162:19:162:38 | prefix.concat(split) | +| TaintedPath.js:162:33:162:37 | split | TaintedPath.js:162:19:162:38 | prefix.concat(split) | +| TaintedPath.js:162:33:162:37 | split | TaintedPath.js:162:19:162:38 | prefix.concat(split) | +| TaintedPath.js:162:33:162:37 | split | TaintedPath.js:162:19:162:38 | prefix.concat(split) | +| TaintedPath.js:163:19:163:27 | concatted | TaintedPath.js:163:19:163:37 | concatted.join("/") | +| TaintedPath.js:163:19:163:27 | concatted | TaintedPath.js:163:19:163:37 | concatted.join("/") | +| TaintedPath.js:163:19:163:27 | concatted | TaintedPath.js:163:19:163:37 | concatted.join("/") | +| TaintedPath.js:163:19:163:27 | concatted | TaintedPath.js:163:19:163:37 | concatted.join("/") | +| TaintedPath.js:163:19:163:27 | concatted | TaintedPath.js:163:19:163:37 | concatted.join("/") | +| TaintedPath.js:163:19:163:27 | concatted | TaintedPath.js:163:19:163:37 | concatted.join("/") | +| TaintedPath.js:163:19:163:27 | concatted | TaintedPath.js:163:19:163:37 | concatted.join("/") | +| TaintedPath.js:163:19:163:27 | concatted | TaintedPath.js:163:19:163:37 | concatted.join("/") | +| TaintedPath.js:163:19:163:27 | concatted | TaintedPath.js:163:19:163:37 | concatted.join("/") | +| TaintedPath.js:163:19:163:27 | concatted | TaintedPath.js:163:19:163:37 | concatted.join("/") | +| TaintedPath.js:163:19:163:27 | concatted | TaintedPath.js:163:19:163:37 | concatted.join("/") | +| TaintedPath.js:163:19:163:27 | concatted | TaintedPath.js:163:19:163:37 | concatted.join("/") | +| TaintedPath.js:163:19:163:27 | concatted | TaintedPath.js:163:19:163:37 | concatted.join("/") | +| TaintedPath.js:163:19:163:27 | concatted | TaintedPath.js:163:19:163:37 | concatted.join("/") | +| TaintedPath.js:163:19:163:27 | concatted | TaintedPath.js:163:19:163:37 | concatted.join("/") | +| TaintedPath.js:163:19:163:27 | concatted | TaintedPath.js:163:19:163:37 | concatted.join("/") | +| TaintedPath.js:165:7:165:39 | concatted2 | TaintedPath.js:166:19:166:28 | concatted2 | +| TaintedPath.js:165:7:165:39 | concatted2 | TaintedPath.js:166:19:166:28 | concatted2 | +| TaintedPath.js:165:7:165:39 | concatted2 | TaintedPath.js:166:19:166:28 | concatted2 | +| TaintedPath.js:165:7:165:39 | concatted2 | TaintedPath.js:166:19:166:28 | concatted2 | +| TaintedPath.js:165:20:165:24 | split | TaintedPath.js:165:20:165:39 | split.concat(prefix) | +| TaintedPath.js:165:20:165:24 | split | TaintedPath.js:165:20:165:39 | split.concat(prefix) | +| TaintedPath.js:165:20:165:24 | split | TaintedPath.js:165:20:165:39 | split.concat(prefix) | +| TaintedPath.js:165:20:165:24 | split | TaintedPath.js:165:20:165:39 | split.concat(prefix) | +| TaintedPath.js:165:20:165:39 | split.concat(prefix) | TaintedPath.js:165:7:165:39 | concatted2 | +| TaintedPath.js:165:20:165:39 | split.concat(prefix) | TaintedPath.js:165:7:165:39 | concatted2 | +| TaintedPath.js:165:20:165:39 | split.concat(prefix) | TaintedPath.js:165:7:165:39 | concatted2 | +| TaintedPath.js:165:20:165:39 | split.concat(prefix) | TaintedPath.js:165:7:165:39 | concatted2 | +| TaintedPath.js:166:19:166:28 | concatted2 | TaintedPath.js:166:19:166:38 | concatted2.join("/") | +| TaintedPath.js:166:19:166:28 | concatted2 | TaintedPath.js:166:19:166:38 | concatted2.join("/") | +| TaintedPath.js:166:19:166:28 | concatted2 | TaintedPath.js:166:19:166:38 | concatted2.join("/") | +| TaintedPath.js:166:19:166:28 | concatted2 | TaintedPath.js:166:19:166:38 | concatted2.join("/") | +| TaintedPath.js:166:19:166:28 | concatted2 | TaintedPath.js:166:19:166:38 | concatted2.join("/") | +| TaintedPath.js:166:19:166:28 | concatted2 | TaintedPath.js:166:19:166:38 | concatted2.join("/") | +| TaintedPath.js:166:19:166:28 | concatted2 | TaintedPath.js:166:19:166:38 | concatted2.join("/") | +| TaintedPath.js:166:19:166:28 | concatted2 | TaintedPath.js:166:19:166:38 | concatted2.join("/") | +| TaintedPath.js:166:19:166:28 | concatted2 | TaintedPath.js:166:19:166:38 | concatted2.join("/") | +| TaintedPath.js:166:19:166:28 | concatted2 | TaintedPath.js:166:19:166:38 | concatted2.join("/") | +| TaintedPath.js:166:19:166:28 | concatted2 | TaintedPath.js:166:19:166:38 | concatted2.join("/") | +| TaintedPath.js:166:19:166:28 | concatted2 | TaintedPath.js:166:19:166:38 | concatted2.join("/") | +| TaintedPath.js:166:19:166:28 | concatted2 | TaintedPath.js:166:19:166:38 | concatted2.join("/") | +| TaintedPath.js:166:19:166:28 | concatted2 | TaintedPath.js:166:19:166:38 | concatted2.join("/") | +| TaintedPath.js:166:19:166:28 | concatted2 | TaintedPath.js:166:19:166:38 | concatted2.join("/") | +| TaintedPath.js:166:19:166:28 | concatted2 | TaintedPath.js:166:19:166:38 | concatted2.join("/") | +| TaintedPath.js:168:19:168:23 | split | TaintedPath.js:168:19:168:29 | split.pop() | +| TaintedPath.js:168:19:168:23 | split | TaintedPath.js:168:19:168:29 | split.pop() | +| TaintedPath.js:168:19:168:23 | split | TaintedPath.js:168:19:168:29 | split.pop() | +| TaintedPath.js:168:19:168:23 | split | TaintedPath.js:168:19:168:29 | split.pop() | +| TaintedPath.js:168:19:168:23 | split | TaintedPath.js:168:19:168:29 | split.pop() | +| TaintedPath.js:168:19:168:23 | split | TaintedPath.js:168:19:168:29 | split.pop() | +| TaintedPath.js:168:19:168:23 | split | TaintedPath.js:168:19:168:29 | split.pop() | +| TaintedPath.js:168:19:168:23 | split | TaintedPath.js:168:19:168:29 | split.pop() | +| TaintedPath.js:168:19:168:23 | split | TaintedPath.js:168:19:168:29 | split.pop() | +| TaintedPath.js:168:19:168:23 | split | TaintedPath.js:168:19:168:29 | split.pop() | +| TaintedPath.js:168:19:168:23 | split | TaintedPath.js:168:19:168:29 | split.pop() | +| TaintedPath.js:168:19:168:23 | split | TaintedPath.js:168:19:168:29 | split.pop() | +| TaintedPath.js:168:19:168:23 | split | TaintedPath.js:168:19:168:29 | split.pop() | +| TaintedPath.js:168:19:168:23 | split | TaintedPath.js:168:19:168:29 | split.pop() | +| TaintedPath.js:168:19:168:23 | split | TaintedPath.js:168:19:168:29 | split.pop() | +| TaintedPath.js:168:19:168:23 | split | TaintedPath.js:168:19:168:29 | split.pop() | | normalizedPaths.js:11:7:11:27 | path | normalizedPaths.js:13:19:13:22 | path | | normalizedPaths.js:11:7:11:27 | path | normalizedPaths.js:13:19:13:22 | path | | normalizedPaths.js:11:7:11:27 | path | normalizedPaths.js:13:19:13:22 | path | @@ -3630,6 +4364,173 @@ edges | 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:254:7:254:47 | path | normalizedPaths.js:256:19:256:22 | path | +| normalizedPaths.js:254:7:254:47 | path | normalizedPaths.js:256:19:256:22 | path | +| normalizedPaths.js:254:7:254:47 | path | normalizedPaths.js:256:19:256:22 | path | +| normalizedPaths.js:254:7:254:47 | path | normalizedPaths.js:256:19:256:22 | path | +| normalizedPaths.js:254:7:254:47 | path | normalizedPaths.js:256:19:256:22 | path | +| normalizedPaths.js:254:7:254:47 | path | normalizedPaths.js:256:19:256:22 | path | +| normalizedPaths.js:254:7:254:47 | path | normalizedPaths.js:256:19:256:22 | path | +| normalizedPaths.js:254:7:254:47 | path | normalizedPaths.js:256:19:256:22 | path | +| normalizedPaths.js:254:7:254:47 | path | normalizedPaths.js:262:21:262:24 | path | +| normalizedPaths.js:254:7:254:47 | path | normalizedPaths.js:262:21:262:24 | path | +| normalizedPaths.js:254:7:254:47 | path | normalizedPaths.js:262:21:262:24 | path | +| normalizedPaths.js:254:7:254:47 | path | normalizedPaths.js:262:21:262:24 | path | +| normalizedPaths.js:254:7:254:47 | path | normalizedPaths.js:262:21:262:24 | path | +| normalizedPaths.js:254:7:254:47 | path | normalizedPaths.js:262:21:262:24 | path | +| normalizedPaths.js:254:7:254:47 | path | normalizedPaths.js:262:21:262:24 | path | +| normalizedPaths.js:254:7:254:47 | path | normalizedPaths.js:262:21:262:24 | path | +| normalizedPaths.js:254:7:254:47 | path | normalizedPaths.js:267:38:267:41 | path | +| normalizedPaths.js:254:7:254:47 | path | normalizedPaths.js:267:38:267:41 | path | +| normalizedPaths.js:254:7:254:47 | path | normalizedPaths.js:267:38:267:41 | path | +| normalizedPaths.js:254:7:254:47 | path | normalizedPaths.js:267:38:267:41 | path | +| normalizedPaths.js:254:7:254:47 | path | normalizedPaths.js:275:38:275:41 | path | +| normalizedPaths.js:254:7:254:47 | path | normalizedPaths.js:275:38:275:41 | path | +| normalizedPaths.js:254:7:254:47 | path | normalizedPaths.js:275:38:275:41 | path | +| normalizedPaths.js:254:7:254:47 | path | normalizedPaths.js:275:38:275:41 | path | +| normalizedPaths.js:254:7:254:47 | path | normalizedPaths.js:283:38:283:41 | path | +| normalizedPaths.js:254:7:254:47 | path | normalizedPaths.js:283:38:283:41 | path | +| normalizedPaths.js:254:7:254:47 | path | normalizedPaths.js:283:38:283:41 | path | +| normalizedPaths.js:254:7:254:47 | path | normalizedPaths.js:283:38:283:41 | path | +| normalizedPaths.js:254:7:254:47 | path | normalizedPaths.js:291:38:291:41 | path | +| normalizedPaths.js:254:7:254:47 | path | normalizedPaths.js:291:38:291:41 | path | +| normalizedPaths.js:254:7:254:47 | path | normalizedPaths.js:291:38:291:41 | path | +| normalizedPaths.js:254:7:254:47 | path | normalizedPaths.js:291:38:291:41 | path | +| normalizedPaths.js:254:14:254:47 | pathMod ... y.path) | normalizedPaths.js:254:7:254:47 | path | +| normalizedPaths.js:254:14:254:47 | pathMod ... y.path) | normalizedPaths.js:254:7:254:47 | path | +| normalizedPaths.js:254:14:254:47 | pathMod ... y.path) | normalizedPaths.js:254:7:254:47 | path | +| normalizedPaths.js:254:14:254:47 | pathMod ... y.path) | normalizedPaths.js:254:7:254:47 | path | +| normalizedPaths.js:254:33:254:46 | req.query.path | normalizedPaths.js:254:14:254:47 | pathMod ... y.path) | +| normalizedPaths.js:254:33:254:46 | req.query.path | normalizedPaths.js:254:14:254:47 | pathMod ... y.path) | +| normalizedPaths.js:254:33:254:46 | req.query.path | normalizedPaths.js:254:14:254:47 | pathMod ... y.path) | +| normalizedPaths.js:254:33:254:46 | req.query.path | normalizedPaths.js:254:14:254:47 | pathMod ... y.path) | +| normalizedPaths.js:254:33:254:46 | req.query.path | normalizedPaths.js:254:14:254:47 | pathMod ... y.path) | +| normalizedPaths.js:254:33:254:46 | req.query.path | normalizedPaths.js:254:14:254:47 | pathMod ... y.path) | +| normalizedPaths.js:254:33:254:46 | req.query.path | normalizedPaths.js:254:14:254:47 | pathMod ... y.path) | +| normalizedPaths.js:254:33:254:46 | req.query.path | normalizedPaths.js:254:14:254:47 | pathMod ... y.path) | +| normalizedPaths.js:267:7:267:42 | newpath | normalizedPaths.js:270:21:270:27 | newpath | +| normalizedPaths.js:267:7:267:42 | newpath | normalizedPaths.js:270:21:270:27 | newpath | +| normalizedPaths.js:267:7:267:42 | newpath | normalizedPaths.js:270:21:270:27 | newpath | +| normalizedPaths.js:267:7:267:42 | newpath | normalizedPaths.js:270:21:270:27 | newpath | +| normalizedPaths.js:267:7:267:42 | newpath | normalizedPaths.js:270:21:270:27 | newpath | +| normalizedPaths.js:267:7:267:42 | newpath | normalizedPaths.js:270:21:270:27 | newpath | +| normalizedPaths.js:267:7:267:42 | newpath | normalizedPaths.js:270:21:270:27 | newpath | +| normalizedPaths.js:267:7:267:42 | newpath | normalizedPaths.js:270:21:270:27 | newpath | +| normalizedPaths.js:267:17:267:42 | pathMod ... e(path) | normalizedPaths.js:267:7:267:42 | newpath | +| normalizedPaths.js:267:17:267:42 | pathMod ... e(path) | normalizedPaths.js:267:7:267:42 | newpath | +| normalizedPaths.js:267:17:267:42 | pathMod ... e(path) | normalizedPaths.js:267:7:267:42 | newpath | +| normalizedPaths.js:267:17:267:42 | pathMod ... e(path) | normalizedPaths.js:267:7:267:42 | newpath | +| normalizedPaths.js:267:38:267:41 | path | normalizedPaths.js:267:17:267:42 | pathMod ... e(path) | +| normalizedPaths.js:267:38:267:41 | path | normalizedPaths.js:267:17:267:42 | pathMod ... e(path) | +| normalizedPaths.js:267:38:267:41 | path | normalizedPaths.js:267:17:267:42 | pathMod ... e(path) | +| normalizedPaths.js:267:38:267:41 | path | normalizedPaths.js:267:17:267:42 | pathMod ... e(path) | +| normalizedPaths.js:275:7:275:42 | newpath | normalizedPaths.js:278:21:278:27 | newpath | +| normalizedPaths.js:275:7:275:42 | newpath | normalizedPaths.js:278:21:278:27 | newpath | +| normalizedPaths.js:275:7:275:42 | newpath | normalizedPaths.js:278:21:278:27 | newpath | +| normalizedPaths.js:275:7:275:42 | newpath | normalizedPaths.js:278:21:278:27 | newpath | +| normalizedPaths.js:275:7:275:42 | newpath | normalizedPaths.js:278:21:278:27 | newpath | +| normalizedPaths.js:275:7:275:42 | newpath | normalizedPaths.js:278:21:278:27 | newpath | +| normalizedPaths.js:275:7:275:42 | newpath | normalizedPaths.js:278:21:278:27 | newpath | +| normalizedPaths.js:275:7:275:42 | newpath | normalizedPaths.js:278:21:278:27 | newpath | +| normalizedPaths.js:275:17:275:42 | pathMod ... e(path) | normalizedPaths.js:275:7:275:42 | newpath | +| normalizedPaths.js:275:17:275:42 | pathMod ... e(path) | normalizedPaths.js:275:7:275:42 | newpath | +| normalizedPaths.js:275:17:275:42 | pathMod ... e(path) | normalizedPaths.js:275:7:275:42 | newpath | +| normalizedPaths.js:275:17:275:42 | pathMod ... e(path) | normalizedPaths.js:275:7:275:42 | newpath | +| normalizedPaths.js:275:38:275:41 | path | normalizedPaths.js:275:17:275:42 | pathMod ... e(path) | +| normalizedPaths.js:275:38:275:41 | path | normalizedPaths.js:275:17:275:42 | pathMod ... e(path) | +| normalizedPaths.js:275:38:275:41 | path | normalizedPaths.js:275:17:275:42 | pathMod ... e(path) | +| normalizedPaths.js:275:38:275:41 | path | normalizedPaths.js:275:17:275:42 | pathMod ... e(path) | +| normalizedPaths.js:283:7:283:42 | newpath | normalizedPaths.js:286:21:286:27 | newpath | +| normalizedPaths.js:283:7:283:42 | newpath | normalizedPaths.js:286:21:286:27 | newpath | +| normalizedPaths.js:283:7:283:42 | newpath | normalizedPaths.js:286:21:286:27 | newpath | +| normalizedPaths.js:283:7:283:42 | newpath | normalizedPaths.js:286:21:286:27 | newpath | +| normalizedPaths.js:283:7:283:42 | newpath | normalizedPaths.js:286:21:286:27 | newpath | +| normalizedPaths.js:283:7:283:42 | newpath | normalizedPaths.js:286:21:286:27 | newpath | +| normalizedPaths.js:283:7:283:42 | newpath | normalizedPaths.js:286:21:286:27 | newpath | +| normalizedPaths.js:283:7:283:42 | newpath | normalizedPaths.js:286:21:286:27 | newpath | +| normalizedPaths.js:283:17:283:42 | pathMod ... e(path) | normalizedPaths.js:283:7:283:42 | newpath | +| normalizedPaths.js:283:17:283:42 | pathMod ... e(path) | normalizedPaths.js:283:7:283:42 | newpath | +| normalizedPaths.js:283:17:283:42 | pathMod ... e(path) | normalizedPaths.js:283:7:283:42 | newpath | +| normalizedPaths.js:283:17:283:42 | pathMod ... e(path) | normalizedPaths.js:283:7:283:42 | newpath | +| normalizedPaths.js:283:38:283:41 | path | normalizedPaths.js:283:17:283:42 | pathMod ... e(path) | +| normalizedPaths.js:283:38:283:41 | path | normalizedPaths.js:283:17:283:42 | pathMod ... e(path) | +| normalizedPaths.js:283:38:283:41 | path | normalizedPaths.js:283:17:283:42 | pathMod ... e(path) | +| normalizedPaths.js:283:38:283:41 | path | normalizedPaths.js:283:17:283:42 | pathMod ... e(path) | +| normalizedPaths.js:291:7:291:42 | newpath | normalizedPaths.js:296:21:296:27 | newpath | +| normalizedPaths.js:291:7:291:42 | newpath | normalizedPaths.js:296:21:296:27 | newpath | +| normalizedPaths.js:291:7:291:42 | newpath | normalizedPaths.js:296:21:296:27 | newpath | +| normalizedPaths.js:291:7:291:42 | newpath | normalizedPaths.js:296:21:296:27 | newpath | +| normalizedPaths.js:291:7:291:42 | newpath | normalizedPaths.js:296:21:296:27 | newpath | +| normalizedPaths.js:291:7:291:42 | newpath | normalizedPaths.js:296:21:296:27 | newpath | +| normalizedPaths.js:291:7:291:42 | newpath | normalizedPaths.js:296:21:296:27 | newpath | +| normalizedPaths.js:291:7:291:42 | newpath | normalizedPaths.js:296:21:296:27 | newpath | +| normalizedPaths.js:291:17:291:42 | pathMod ... e(path) | normalizedPaths.js:291:7:291:42 | newpath | +| normalizedPaths.js:291:17:291:42 | pathMod ... e(path) | normalizedPaths.js:291:7:291:42 | newpath | +| normalizedPaths.js:291:17:291:42 | pathMod ... e(path) | normalizedPaths.js:291:7:291:42 | newpath | +| normalizedPaths.js:291:17:291:42 | pathMod ... e(path) | normalizedPaths.js:291:7:291:42 | newpath | +| normalizedPaths.js:291:38:291:41 | path | normalizedPaths.js:291:17:291:42 | pathMod ... e(path) | +| normalizedPaths.js:291:38:291:41 | path | normalizedPaths.js:291:17:291:42 | pathMod ... e(path) | +| normalizedPaths.js:291:38:291:41 | path | normalizedPaths.js:291:17:291:42 | pathMod ... e(path) | +| normalizedPaths.js:291:38:291:41 | path | normalizedPaths.js:291:17:291:42 | pathMod ... e(path) | +| normalizedPaths.js:303:6:303:26 | path | normalizedPaths.js:304:18:304:21 | path | +| normalizedPaths.js:303:6:303:26 | path | normalizedPaths.js:304:18:304:21 | path | +| normalizedPaths.js:303:6:303:26 | path | normalizedPaths.js:304:18:304:21 | path | +| normalizedPaths.js:303:6:303:26 | path | normalizedPaths.js:304:18:304:21 | path | +| normalizedPaths.js:303:6:303:26 | path | normalizedPaths.js:304:18:304:21 | path | +| normalizedPaths.js:303:6:303:26 | path | normalizedPaths.js:304:18:304:21 | path | +| normalizedPaths.js:303:6:303:26 | path | normalizedPaths.js:304:18:304:21 | path | +| normalizedPaths.js:303:6:303:26 | path | normalizedPaths.js:304:18:304:21 | path | +| normalizedPaths.js:303:6:303:26 | path | normalizedPaths.js:309:19:309:22 | path | +| normalizedPaths.js:303:6:303:26 | path | normalizedPaths.js:309:19:309:22 | path | +| normalizedPaths.js:303:6:303:26 | path | normalizedPaths.js:309:19:309:22 | path | +| normalizedPaths.js:303:6:303:26 | path | normalizedPaths.js:309:19:309:22 | path | +| normalizedPaths.js:303:6:303:26 | path | normalizedPaths.js:309:19:309:22 | path | +| normalizedPaths.js:303:6:303:26 | path | normalizedPaths.js:309:19:309:22 | path | +| normalizedPaths.js:303:6:303:26 | path | normalizedPaths.js:309:19:309:22 | path | +| normalizedPaths.js:303:6:303:26 | path | normalizedPaths.js:309:19:309:22 | path | +| normalizedPaths.js:303:6:303:26 | path | normalizedPaths.js:313:19:313:22 | path | +| normalizedPaths.js:303:6:303:26 | path | normalizedPaths.js:313:19:313:22 | path | +| normalizedPaths.js:303:6:303:26 | path | normalizedPaths.js:313:19:313:22 | path | +| normalizedPaths.js:303:6:303:26 | path | normalizedPaths.js:313:19:313:22 | path | +| normalizedPaths.js:303:6:303:26 | path | normalizedPaths.js:313:19:313:22 | path | +| normalizedPaths.js:303:6:303:26 | path | normalizedPaths.js:313:19:313:22 | path | +| normalizedPaths.js:303:6:303:26 | path | normalizedPaths.js:316:19:316:22 | path | +| normalizedPaths.js:303:6:303:26 | path | normalizedPaths.js:316:19:316:22 | path | +| normalizedPaths.js:303:6:303:26 | path | normalizedPaths.js:316:19:316:22 | path | +| normalizedPaths.js:303:6:303:26 | path | normalizedPaths.js:316:19:316:22 | path | +| normalizedPaths.js:303:6:303:26 | path | normalizedPaths.js:316:19:316:22 | path | +| normalizedPaths.js:303:6:303:26 | path | normalizedPaths.js:316:19:316:22 | path | +| normalizedPaths.js:303:6:303:26 | path | normalizedPaths.js:316:19:316:22 | path | +| normalizedPaths.js:303:6:303:26 | path | normalizedPaths.js:316:19:316:22 | path | +| normalizedPaths.js:303:6:303:26 | path | normalizedPaths.js:320:45:320:48 | path | +| normalizedPaths.js:303:6:303:26 | path | normalizedPaths.js:320:45:320:48 | path | +| normalizedPaths.js:303:6:303:26 | path | normalizedPaths.js:320:45:320:48 | path | +| normalizedPaths.js:303:13:303:26 | req.query.path | normalizedPaths.js:303:6:303:26 | path | +| normalizedPaths.js:303:13:303:26 | req.query.path | normalizedPaths.js:303:6:303:26 | path | +| normalizedPaths.js:303:13:303:26 | req.query.path | normalizedPaths.js:303:6:303:26 | path | +| normalizedPaths.js:303:13:303:26 | req.query.path | normalizedPaths.js:303:6:303:26 | path | +| normalizedPaths.js:303:13:303:26 | req.query.path | normalizedPaths.js:303:6:303:26 | path | +| normalizedPaths.js:303:13:303:26 | req.query.path | normalizedPaths.js:303:6:303:26 | path | +| normalizedPaths.js:303:13:303:26 | req.query.path | normalizedPaths.js:303:6:303:26 | path | +| normalizedPaths.js:303:13:303:26 | req.query.path | normalizedPaths.js:303:6:303:26 | path | +| normalizedPaths.js:320:6:320:49 | normalizedPath | normalizedPaths.js:325:19:325:32 | normalizedPath | +| normalizedPaths.js:320:6:320:49 | normalizedPath | normalizedPaths.js:325:19:325:32 | normalizedPath | +| normalizedPaths.js:320:6:320:49 | normalizedPath | normalizedPaths.js:325:19:325:32 | normalizedPath | +| normalizedPaths.js:320:6:320:49 | normalizedPath | normalizedPaths.js:325:19:325:32 | normalizedPath | +| normalizedPaths.js:320:6:320:49 | normalizedPath | normalizedPaths.js:325:19:325:32 | normalizedPath | +| normalizedPaths.js:320:6:320:49 | normalizedPath | normalizedPaths.js:325:19:325:32 | normalizedPath | +| normalizedPaths.js:320:6:320:49 | normalizedPath | normalizedPaths.js:332:19:332:32 | normalizedPath | +| normalizedPaths.js:320:6:320:49 | normalizedPath | normalizedPaths.js:332:19:332:32 | normalizedPath | +| normalizedPaths.js:320:6:320:49 | normalizedPath | normalizedPaths.js:332:19:332:32 | normalizedPath | +| normalizedPaths.js:320:6:320:49 | normalizedPath | normalizedPaths.js:332:19:332:32 | normalizedPath | +| normalizedPaths.js:320:6:320:49 | normalizedPath | normalizedPaths.js:332:19:332:32 | normalizedPath | +| normalizedPaths.js:320:6:320:49 | normalizedPath | normalizedPaths.js:332:19:332:32 | normalizedPath | +| normalizedPaths.js:320:23:320:49 | pathMod ... , path) | normalizedPaths.js:320:6:320:49 | normalizedPath | +| normalizedPaths.js:320:23:320:49 | pathMod ... , path) | normalizedPaths.js:320:6:320:49 | normalizedPath | +| normalizedPaths.js:320:23:320:49 | pathMod ... , path) | normalizedPaths.js:320:6:320:49 | normalizedPath | +| normalizedPaths.js:320:45:320:48 | path | normalizedPaths.js:320:23:320:49 | pathMod ... , path) | +| normalizedPaths.js:320:45:320:48 | path | normalizedPaths.js:320:23:320:49 | pathMod ... , path) | +| normalizedPaths.js:320:45:320:48 | path | normalizedPaths.js:320:23:320:49 | pathMod ... , 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") | @@ -3793,6 +4694,30 @@ edges | 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:22:18:22:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:22:18:22:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:22:18:22:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:22:18:22:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:22:18:22:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:22:18:22:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:22:18:22:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:22:18:22:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:22:18:22:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:22:18:22:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:22:18:22:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:22:18:22:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:23:18:23:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:23:18:23:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:23:18:23:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:23:18:23:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:23:18:23:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:23:18:23:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:23:18:23:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:23:18:23:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:23:18:23:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:23:18:23:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:23:18:23:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:23:18:23: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 | @@ -4193,6 +5118,62 @@ edges | 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:22:18:22:21 | path | tainted-string-steps.js:22:18:22:32 | path.split('/') | +| tainted-string-steps.js:22:18:22:21 | path | tainted-string-steps.js:22:18:22:32 | path.split('/') | +| tainted-string-steps.js:22:18:22:21 | path | tainted-string-steps.js:22:18:22:32 | path.split('/') | +| tainted-string-steps.js:22:18:22:21 | path | tainted-string-steps.js:22:18:22:32 | path.split('/') | +| tainted-string-steps.js:22:18:22:21 | path | tainted-string-steps.js:22:18:22:32 | path.split('/') | +| tainted-string-steps.js:22:18:22:21 | path | tainted-string-steps.js:22:18:22:32 | path.split('/') | +| tainted-string-steps.js:22:18:22:21 | path | tainted-string-steps.js:22:18:22:32 | path.split('/') | +| tainted-string-steps.js:22:18:22:21 | path | tainted-string-steps.js:22:18:22:32 | path.split('/') | +| tainted-string-steps.js:22:18:22:21 | path | tainted-string-steps.js:22:18:22:32 | path.split('/') | +| tainted-string-steps.js:22:18:22:21 | path | tainted-string-steps.js:22:18:22:32 | path.split('/') | +| tainted-string-steps.js:22:18:22:21 | path | tainted-string-steps.js:22:18:22:32 | path.split('/') | +| tainted-string-steps.js:22:18:22:21 | path | tainted-string-steps.js:22:18:22:32 | path.split('/') | +| tainted-string-steps.js:22:18:22:32 | path.split('/') | tainted-string-steps.js:22:18:22:35 | path.split('/')[i] | +| tainted-string-steps.js:22:18:22:32 | path.split('/') | tainted-string-steps.js:22:18:22:35 | path.split('/')[i] | +| tainted-string-steps.js:22:18:22:32 | path.split('/') | tainted-string-steps.js:22:18:22:35 | path.split('/')[i] | +| tainted-string-steps.js:22:18:22:32 | path.split('/') | tainted-string-steps.js:22:18:22:35 | path.split('/')[i] | +| tainted-string-steps.js:22:18:22:32 | path.split('/') | tainted-string-steps.js:22:18:22:35 | path.split('/')[i] | +| tainted-string-steps.js:22:18:22:32 | path.split('/') | tainted-string-steps.js:22:18:22:35 | path.split('/')[i] | +| tainted-string-steps.js:22:18:22:32 | path.split('/') | tainted-string-steps.js:22:18:22:35 | path.split('/')[i] | +| tainted-string-steps.js:22:18:22:32 | path.split('/') | tainted-string-steps.js:22:18:22:35 | path.split('/')[i] | +| tainted-string-steps.js:22:18:22:32 | path.split('/') | tainted-string-steps.js:22:18:22:35 | path.split('/')[i] | +| tainted-string-steps.js:22:18:22:32 | path.split('/') | tainted-string-steps.js:22:18:22:35 | path.split('/')[i] | +| tainted-string-steps.js:22:18:22:32 | path.split('/') | tainted-string-steps.js:22:18:22:35 | path.split('/')[i] | +| tainted-string-steps.js:22:18:22:32 | path.split('/') | tainted-string-steps.js:22:18:22:35 | path.split('/')[i] | +| tainted-string-steps.js:22:18:22:32 | path.split('/') | tainted-string-steps.js:22:18:22:35 | path.split('/')[i] | +| tainted-string-steps.js:22:18:22:32 | path.split('/') | tainted-string-steps.js:22:18:22:35 | path.split('/')[i] | +| tainted-string-steps.js:22:18:22:32 | path.split('/') | tainted-string-steps.js:22:18:22:35 | path.split('/')[i] | +| tainted-string-steps.js:22:18:22:32 | path.split('/') | tainted-string-steps.js:22:18:22:35 | path.split('/')[i] | +| tainted-string-steps.js:23:18:23:21 | path | tainted-string-steps.js:23:18:23:33 | path.split(/\\//) | +| tainted-string-steps.js:23:18:23:21 | path | tainted-string-steps.js:23:18:23:33 | path.split(/\\//) | +| tainted-string-steps.js:23:18:23:21 | path | tainted-string-steps.js:23:18:23:33 | path.split(/\\//) | +| tainted-string-steps.js:23:18:23:21 | path | tainted-string-steps.js:23:18:23:33 | path.split(/\\//) | +| tainted-string-steps.js:23:18:23:21 | path | tainted-string-steps.js:23:18:23:33 | path.split(/\\//) | +| tainted-string-steps.js:23:18:23:21 | path | tainted-string-steps.js:23:18:23:33 | path.split(/\\//) | +| tainted-string-steps.js:23:18:23:21 | path | tainted-string-steps.js:23:18:23:33 | path.split(/\\//) | +| tainted-string-steps.js:23:18:23:21 | path | tainted-string-steps.js:23:18:23:33 | path.split(/\\//) | +| tainted-string-steps.js:23:18:23:21 | path | tainted-string-steps.js:23:18:23:33 | path.split(/\\//) | +| tainted-string-steps.js:23:18:23:21 | path | tainted-string-steps.js:23:18:23:33 | path.split(/\\//) | +| tainted-string-steps.js:23:18:23:21 | path | tainted-string-steps.js:23:18:23:33 | path.split(/\\//) | +| tainted-string-steps.js:23:18:23:21 | path | tainted-string-steps.js:23:18:23:33 | path.split(/\\//) | +| tainted-string-steps.js:23:18:23:33 | path.split(/\\//) | tainted-string-steps.js:23:18:23:36 | path.split(/\\//)[i] | +| tainted-string-steps.js:23:18:23:33 | path.split(/\\//) | tainted-string-steps.js:23:18:23:36 | path.split(/\\//)[i] | +| tainted-string-steps.js:23:18:23:33 | path.split(/\\//) | tainted-string-steps.js:23:18:23:36 | path.split(/\\//)[i] | +| tainted-string-steps.js:23:18:23:33 | path.split(/\\//) | tainted-string-steps.js:23:18:23:36 | path.split(/\\//)[i] | +| tainted-string-steps.js:23:18:23:33 | path.split(/\\//) | tainted-string-steps.js:23:18:23:36 | path.split(/\\//)[i] | +| tainted-string-steps.js:23:18:23:33 | path.split(/\\//) | tainted-string-steps.js:23:18:23:36 | path.split(/\\//)[i] | +| tainted-string-steps.js:23:18:23:33 | path.split(/\\//) | tainted-string-steps.js:23:18:23:36 | path.split(/\\//)[i] | +| tainted-string-steps.js:23:18:23:33 | path.split(/\\//) | tainted-string-steps.js:23:18:23:36 | path.split(/\\//)[i] | +| tainted-string-steps.js:23:18:23:33 | path.split(/\\//) | tainted-string-steps.js:23:18:23:36 | path.split(/\\//)[i] | +| tainted-string-steps.js:23:18:23:33 | path.split(/\\//) | tainted-string-steps.js:23:18:23:36 | path.split(/\\//)[i] | +| tainted-string-steps.js:23:18:23:33 | path.split(/\\//) | tainted-string-steps.js:23:18:23:36 | path.split(/\\//)[i] | +| tainted-string-steps.js:23:18:23:33 | path.split(/\\//) | tainted-string-steps.js:23:18:23:36 | path.split(/\\//)[i] | +| tainted-string-steps.js:23:18:23:33 | path.split(/\\//) | tainted-string-steps.js:23:18:23:36 | path.split(/\\//)[i] | +| tainted-string-steps.js:23:18:23:33 | path.split(/\\//) | tainted-string-steps.js:23:18:23:36 | path.split(/\\//)[i] | +| tainted-string-steps.js:23:18:23:33 | path.split(/\\//) | tainted-string-steps.js:23:18:23:36 | path.split(/\\//)[i] | +| tainted-string-steps.js:23:18:23:33 | path.split(/\\//) | tainted-string-steps.js:23:18:23:36 | path.split(/\\//)[i] | | 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("?") | @@ -4369,7 +5350,14 @@ edges | TaintedPath.js:94:48:94:60 | req.params[0] | TaintedPath.js:94:48:94:60 | req.params[0] | TaintedPath.js:94:48:94:60 | req.params[0] | This path depends on $@. | TaintedPath.js:94:48:94:60 | req.params[0] | a user-provided value | | TaintedPath.js:109:28:109:48 | fs.real ... c(path) | TaintedPath.js:107:23:107:29 | req.url | TaintedPath.js:109:28:109:48 | fs.real ... c(path) | This path depends on $@. | TaintedPath.js:107:23:107:29 | req.url | a user-provided value | | TaintedPath.js:112:45:112:52 | realpath | TaintedPath.js:107:23:107:29 | req.url | TaintedPath.js:112:45:112:52 | realpath | This path depends on $@. | TaintedPath.js:107:23:107:29 | req.url | a user-provided value | -| TaintedPath.js:121:23:121:26 | path | TaintedPath.js:119:23:119:29 | req.url | TaintedPath.js:121:23:121:26 | path | This path depends on $@. | TaintedPath.js:119:23:119:29 | req.url | a user-provided value | +| TaintedPath.js:145:23:145:26 | path | TaintedPath.js:143:23:143:29 | req.url | TaintedPath.js:145:23:145:26 | path | This path depends on $@. | TaintedPath.js:143:23:143:29 | req.url | a user-provided value | +| TaintedPath.js:151:19:151:22 | path | TaintedPath.js:149:24:149:30 | req.url | TaintedPath.js:151:19:151:22 | path | This path depends on $@. | TaintedPath.js:149:24:149:30 | req.url | a user-provided value | +| TaintedPath.js:155:19:155:33 | split.join("/") | TaintedPath.js:149:24:149:30 | req.url | TaintedPath.js:155:19:155:33 | split.join("/") | This path depends on $@. | TaintedPath.js:149:24:149:30 | req.url | a user-provided value | +| TaintedPath.js:159:19:159:26 | split[x] | TaintedPath.js:149:24:149:30 | req.url | TaintedPath.js:159:19:159:26 | split[x] | This path depends on $@. | TaintedPath.js:149:24:149:30 | req.url | a user-provided value | +| TaintedPath.js:160:19:160:35 | prefix + split[x] | TaintedPath.js:149:24:149:30 | req.url | TaintedPath.js:160:19:160:35 | prefix + split[x] | This path depends on $@. | TaintedPath.js:149:24:149:30 | req.url | a user-provided value | +| TaintedPath.js:163:19:163:37 | concatted.join("/") | TaintedPath.js:149:24:149:30 | req.url | TaintedPath.js:163:19:163:37 | concatted.join("/") | This path depends on $@. | TaintedPath.js:149:24:149:30 | req.url | a user-provided value | +| TaintedPath.js:166:19:166:38 | concatted2.join("/") | TaintedPath.js:149:24:149:30 | req.url | TaintedPath.js:166:19:166:38 | concatted2.join("/") | This path depends on $@. | TaintedPath.js:149:24:149:30 | req.url | a user-provided value | +| TaintedPath.js:168:19:168:29 | split.pop() | TaintedPath.js:149:24:149:30 | req.url | TaintedPath.js:168:19:168:29 | split.pop() | This path depends on $@. | TaintedPath.js:149:24:149:30 | req.url | a user-provided value | | normalizedPaths.js:13:19:13:22 | path | normalizedPaths.js:11:14:11:27 | req.query.path | normalizedPaths.js:13:19:13:22 | path | This path depends on $@. | normalizedPaths.js:11:14:11:27 | req.query.path | a user-provided value | | normalizedPaths.js:14:19:14:29 | './' + path | normalizedPaths.js:11:14:11:27 | req.query.path | normalizedPaths.js:14:19:14:29 | './' + path | This path depends on $@. | normalizedPaths.js:11:14:11:27 | req.query.path | a user-provided value | | normalizedPaths.js:15:19:15:38 | path + '/index.html' | normalizedPaths.js:11:14:11:27 | req.query.path | normalizedPaths.js:15:19:15:38 | path + '/index.html' | This path depends on $@. | normalizedPaths.js:11:14:11:27 | req.query.path | a user-provided value | @@ -4411,6 +5399,18 @@ edges | 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 | +| normalizedPaths.js:256:19:256:22 | path | normalizedPaths.js:254:33:254:46 | req.query.path | normalizedPaths.js:256:19:256:22 | path | This path depends on $@. | normalizedPaths.js:254:33:254:46 | req.query.path | a user-provided value | +| normalizedPaths.js:262:21:262:24 | path | normalizedPaths.js:254:33:254:46 | req.query.path | normalizedPaths.js:262:21:262:24 | path | This path depends on $@. | normalizedPaths.js:254:33:254:46 | req.query.path | a user-provided value | +| normalizedPaths.js:270:21:270:27 | newpath | normalizedPaths.js:254:33:254:46 | req.query.path | normalizedPaths.js:270:21:270:27 | newpath | This path depends on $@. | normalizedPaths.js:254:33:254:46 | req.query.path | a user-provided value | +| normalizedPaths.js:278:21:278:27 | newpath | normalizedPaths.js:254:33:254:46 | req.query.path | normalizedPaths.js:278:21:278:27 | newpath | This path depends on $@. | normalizedPaths.js:254:33:254:46 | req.query.path | a user-provided value | +| normalizedPaths.js:286:21:286:27 | newpath | normalizedPaths.js:254:33:254:46 | req.query.path | normalizedPaths.js:286:21:286:27 | newpath | This path depends on $@. | normalizedPaths.js:254:33:254:46 | req.query.path | a user-provided value | +| normalizedPaths.js:296:21:296:27 | newpath | normalizedPaths.js:254:33:254:46 | req.query.path | normalizedPaths.js:296:21:296:27 | newpath | This path depends on $@. | normalizedPaths.js:254:33:254:46 | req.query.path | a user-provided value | +| normalizedPaths.js:304:18:304:21 | path | normalizedPaths.js:303:13:303:26 | req.query.path | normalizedPaths.js:304:18:304:21 | path | This path depends on $@. | normalizedPaths.js:303:13:303:26 | req.query.path | a user-provided value | +| normalizedPaths.js:309:19:309:22 | path | normalizedPaths.js:303:13:303:26 | req.query.path | normalizedPaths.js:309:19:309:22 | path | This path depends on $@. | normalizedPaths.js:303:13:303:26 | req.query.path | a user-provided value | +| normalizedPaths.js:313:19:313:22 | path | normalizedPaths.js:303:13:303:26 | req.query.path | normalizedPaths.js:313:19:313:22 | path | This path depends on $@. | normalizedPaths.js:303:13:303:26 | req.query.path | a user-provided value | +| normalizedPaths.js:316:19:316:22 | path | normalizedPaths.js:303:13:303:26 | req.query.path | normalizedPaths.js:316:19:316:22 | path | This path depends on $@. | normalizedPaths.js:303:13:303:26 | req.query.path | a user-provided value | +| normalizedPaths.js:325:19:325:32 | normalizedPath | normalizedPaths.js:303:13:303:26 | req.query.path | normalizedPaths.js:325:19:325:32 | normalizedPath | This path depends on $@. | normalizedPaths.js:303:13:303:26 | req.query.path | a user-provided value | +| normalizedPaths.js:332:19:332:32 | normalizedPath | normalizedPaths.js:303:13:303:26 | req.query.path | normalizedPaths.js:332:19:332:32 | normalizedPath | This path depends on $@. | normalizedPaths.js:303:13:303:26 | 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 | @@ -4426,6 +5426,8 @@ edges | tainted-string-steps.js:15:18:15:46 | unknown ... , path) | tainted-string-steps.js:6:24:6:30 | req.url | tainted-string-steps.js:15:18:15:46 | unknown ... , path) | 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:22:18:22:35 | path.split('/')[i] | tainted-string-steps.js:6:24:6:30 | req.url | tainted-string-steps.js:22:18:22:35 | path.split('/')[i] | This path depends on $@. | tainted-string-steps.js:6:24:6:30 | req.url | a user-provided value | +| tainted-string-steps.js:23:18:23:36 | path.split(/\\//)[i] | tainted-string-steps.js:6:24:6:30 | req.url | tainted-string-steps.js:23:18:23:36 | path.split(/\\//)[i] | 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 | diff --git a/javascript/ql/test/query-tests/Security/CWE-022/TaintedPath/TaintedPath.js b/javascript/ql/test/query-tests/Security/CWE-022/TaintedPath/TaintedPath.js index 36604536686..df38860cd8a 100644 --- a/javascript/ql/test/query-tests/Security/CWE-022/TaintedPath/TaintedPath.js +++ b/javascript/ql/test/query-tests/Security/CWE-022/TaintedPath/TaintedPath.js @@ -115,9 +115,56 @@ var server = http.createServer(function(req, res) { }); +var server = http.createServer(function(req, res) { + let path = url.parse(req.url, true).query.path; + + if (path) { // sanitization + path = path.replace(/[\]\[*,;'"`<>\\?\/]/g, ''); // remove all invalid characters from states plus slashes + path = path.replace(/\.\./g, ''); // remove all ".." + } + + res.write(fs.readFileSync(path)); // OK. Is sanitized above. +}); + +var server = http.createServer(function(req, res) { + let path = url.parse(req.url, true).query.path; + + if (!path) { + + } else { // sanitization + path = path.replace(/[\]\[*,;'"`<>\\?\/]/g, ''); // remove all invalid characters from states plus slashes + path = path.replace(/\.\./g, ''); // remove all ".." + } + + res.write(fs.readFileSync(path)); // OK. Is sanitized above. +}); + var server = http.createServer(function(req, res) { let path = url.parse(req.url, true).query.path; require('send')(req, path); // NOT OK - }); + +var server = http.createServer(function(req, res) { + let path = url.parse(req.url, true).query.path; + + fs.readFileSync(path); // NOT OK + + var split = path.split("/"); + + fs.readFileSync(split.join("/")); // NOT OK + + fs.readFileSync(prefix + split[split.length - 1]) // OK + + fs.readFileSync(split[x]) // NOT OK + fs.readFileSync(prefix + split[x]) // NOT OK + + var concatted = prefix.concat(split); + fs.readFileSync(concatted.join("/")); // NOT OK + + var concatted2 = split.concat(prefix); + fs.readFileSync(concatted2.join("/")); // NOT OK + + fs.readFileSync(split.pop()); // NOT OK + +}); \ No newline at end of file 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 c0be777dd84..6a7a143f7bd 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 @@ -249,3 +249,88 @@ app.get('/resolve-path', (req, res) => { else fs.readFileSync(path); // NOT OK - wrong polarity }); + +app.get('/relative-startswith', (req, res) => { + let path = pathModule.resolve(req.query.path); + + fs.readFileSync(path); // NOT OK + + var self = something(); + + var relative = pathModule.relative(self.webroot, path); + if(relative.startsWith(".." + pathModule.sep) || relative == "..") { + fs.readFileSync(path); // NOT OK! + } else { + fs.readFileSync(path); // OK! + } + + let newpath = pathModule.normalize(path); + var relativePath = pathModule.relative(pathModule.normalize(workspaceDir), newpath); + if (relativePath.indexOf('..' + pathModule.sep) === 0) { + fs.readFileSync(newpath); // NOT OK! + } else { + fs.readFileSync(newpath); // OK! + } + + let newpath = pathModule.normalize(path); + var relativePath = pathModule.relative(pathModule.normalize(workspaceDir), newpath); + if (relativePath.indexOf('../') === 0) { + fs.readFileSync(newpath); // NOT OK! + } else { + fs.readFileSync(newpath); // OK! + } + + let newpath = pathModule.normalize(path); + var relativePath = pathModule.relative(pathModule.normalize(workspaceDir), newpath); + if (pathModule.normalize(relativePath).indexOf('../') === 0) { + fs.readFileSync(newpath); // NOT OK! + } else { + fs.readFileSync(newpath); // OK! + } + + let newpath = pathModule.normalize(path); + var relativePath = pathModule.relative(pathModule.normalize(workspaceDir), newpath); + if (pathModule.normalize(relativePath).indexOf('../')) { + fs.readFileSync(newpath); // OK! + } else { + fs.readFileSync(newpath); // NOT OK! + } +}); + +var isPathInside = require("is-path-inside"), + pathIsInside = require("path-is-inside"); +app.get('/pseudo-normalizations', (req, res) => { + let path = req.query.path; + fs.readFileSync(path); // NOT OK + if (isPathInside(path, SAFE)) { + fs.readFileSync(path); // OK + return; + } else { + fs.readFileSync(path); // NOT OK + + } + if (pathIsInside(path, SAFE)) { + fs.readFileSync(path); // NOT OK - can be of the form 'safe/directory/../../../etc/passwd' + return; + } else { + fs.readFileSync(path); // NOT OK + + } + + let normalizedPath = pathModule.join(SAFE, path); + if (pathIsInside(normalizedPath, SAFE)) { + fs.readFileSync(normalizedPath); // OK + return; + } else { + fs.readFileSync(normalizedPath); // NOT OK + } + + if (pathIsInside(normalizedPath, SAFE)) { + fs.readFileSync(normalizedPath); // OK + return; + } else { + fs.readFileSync(normalizedPath); // NOT OK + + } + +}); 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 index d61f650d388..aa56b191465 100644 --- 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 @@ -17,14 +17,15 @@ var server = http.createServer(function(req, res) { fs.readFileSync(path.trim()); // NOT OK fs.readFileSync(path.toLowerCase()); // NOT OK - fs.readFileSync(path.split('/')); // OK -- for now + fs.readFileSync(path.split('/')); // OK (readFile throws an exception when the filename is an array) 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('/')[i]); // NOT OK + fs.readFileSync(path.split(/\//)[i]); // NOT OK 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 + fs.readFileSync(path.split("?")[i]); // NOT OK -- but not yet flagged }); server.listen(); 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 88fd5914d69..2a40a00d1ef 100644 --- a/javascript/ql/test/query-tests/Security/CWE-400/PrototypePollutionUtility.expected +++ b/javascript/ql/test/query-tests/Security/CWE-400/PrototypePollutionUtility.expected @@ -1042,6 +1042,54 @@ nodes | PrototypePollutionUtility/tests.js:461:24:461:28 | value | | PrototypePollutionUtility/tests.js:461:24:461:28 | value | | PrototypePollutionUtility/tests.js:461:24:461:28 | value | +| PrototypePollutionUtility/tests.js:467:26:467:28 | dst | +| PrototypePollutionUtility/tests.js:467:31:467:33 | src | +| PrototypePollutionUtility/tests.js:467:31:467:33 | src | +| PrototypePollutionUtility/tests.js:468:14:468:16 | key | +| PrototypePollutionUtility/tests.js:468:14:468:16 | key | +| PrototypePollutionUtility/tests.js:471:29:471:31 | dst | +| PrototypePollutionUtility/tests.js:471:29:471:36 | dst[key] | +| PrototypePollutionUtility/tests.js:471:29:471:36 | dst[key] | +| PrototypePollutionUtility/tests.js:471:33:471:35 | key | +| PrototypePollutionUtility/tests.js:471:39:471:41 | src | +| PrototypePollutionUtility/tests.js:471:39:471:46 | src[key] | +| PrototypePollutionUtility/tests.js:471:39:471:46 | src[key] | +| PrototypePollutionUtility/tests.js:471:39:471:46 | src[key] | +| PrototypePollutionUtility/tests.js:471:39:471:46 | src[key] | +| PrototypePollutionUtility/tests.js:471:43:471:45 | key | +| PrototypePollutionUtility/tests.js:473:13:473:15 | dst | +| PrototypePollutionUtility/tests.js:473:13:473:15 | dst | +| PrototypePollutionUtility/tests.js:473:17:473:19 | key | +| PrototypePollutionUtility/tests.js:473:17:473:19 | key | +| PrototypePollutionUtility/tests.js:473:24:473:26 | src | +| PrototypePollutionUtility/tests.js:473:24:473:26 | src | +| PrototypePollutionUtility/tests.js:473:24:473:31 | src[key] | +| PrototypePollutionUtility/tests.js:473:24:473:31 | src[key] | +| PrototypePollutionUtility/tests.js:473:24:473:31 | src[key] | +| PrototypePollutionUtility/tests.js:473:24:473:31 | src[key] | +| PrototypePollutionUtility/tests.js:473:24:473:31 | src[key] | +| PrototypePollutionUtility/tests.js:473:24:473:31 | src[key] | +| PrototypePollutionUtility/tests.js:473:28:473:30 | key | +| PrototypePollutionUtility/tests.js:478:32:478:34 | src | +| PrototypePollutionUtility/tests.js:479:14:479:16 | key | +| PrototypePollutionUtility/tests.js:479:14:479:16 | key | +| PrototypePollutionUtility/tests.js:482:13:482:28 | value | +| PrototypePollutionUtility/tests.js:482:13:482:28 | value | +| PrototypePollutionUtility/tests.js:482:13:482:28 | value | +| PrototypePollutionUtility/tests.js:482:21:482:23 | src | +| PrototypePollutionUtility/tests.js:482:21:482:28 | src[key] | +| PrototypePollutionUtility/tests.js:482:21:482:28 | src[key] | +| PrototypePollutionUtility/tests.js:482:21:482:28 | src[key] | +| PrototypePollutionUtility/tests.js:482:21:482:28 | src[key] | +| PrototypePollutionUtility/tests.js:482:25:482:27 | key | +| PrototypePollutionUtility/tests.js:484:38:484:42 | value | +| PrototypePollutionUtility/tests.js:484:38:484:42 | value | +| PrototypePollutionUtility/tests.js:486:17:486:19 | key | +| PrototypePollutionUtility/tests.js:486:17:486:19 | key | +| PrototypePollutionUtility/tests.js:486:24:486:28 | value | +| PrototypePollutionUtility/tests.js:486:24:486:28 | value | +| PrototypePollutionUtility/tests.js:486:24:486:28 | value | +| PrototypePollutionUtility/tests.js:486:24:486: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 | @@ -2457,6 +2505,64 @@ edges | PrototypePollutionUtility/tests.js:459:41:459:48 | dst[key] | PrototypePollutionUtility/tests.js:456:38:456:40 | dst | | PrototypePollutionUtility/tests.js:459:45:459:47 | key | PrototypePollutionUtility/tests.js:459:41:459:48 | dst[key] | | PrototypePollutionUtility/tests.js:459:45:459:47 | key | PrototypePollutionUtility/tests.js:459:41:459:48 | dst[key] | +| PrototypePollutionUtility/tests.js:467:26:467:28 | dst | PrototypePollutionUtility/tests.js:471:29:471:31 | dst | +| PrototypePollutionUtility/tests.js:467:26:467:28 | dst | PrototypePollutionUtility/tests.js:473:13:473:15 | dst | +| PrototypePollutionUtility/tests.js:467:26:467:28 | dst | PrototypePollutionUtility/tests.js:473:13:473:15 | dst | +| PrototypePollutionUtility/tests.js:467:31:467:33 | src | PrototypePollutionUtility/tests.js:471:39:471:41 | src | +| PrototypePollutionUtility/tests.js:467:31:467:33 | src | PrototypePollutionUtility/tests.js:473:24:473:26 | src | +| PrototypePollutionUtility/tests.js:467:31:467:33 | src | PrototypePollutionUtility/tests.js:473:24:473:26 | src | +| PrototypePollutionUtility/tests.js:468:14:468:16 | key | PrototypePollutionUtility/tests.js:471:33:471:35 | key | +| PrototypePollutionUtility/tests.js:468:14:468:16 | key | PrototypePollutionUtility/tests.js:471:33:471:35 | key | +| PrototypePollutionUtility/tests.js:468:14:468:16 | key | PrototypePollutionUtility/tests.js:471:43:471:45 | key | +| PrototypePollutionUtility/tests.js:468:14:468:16 | key | PrototypePollutionUtility/tests.js:471:43:471:45 | key | +| PrototypePollutionUtility/tests.js:468:14:468:16 | key | PrototypePollutionUtility/tests.js:473:17:473:19 | key | +| PrototypePollutionUtility/tests.js:468:14:468:16 | key | PrototypePollutionUtility/tests.js:473:17:473:19 | key | +| PrototypePollutionUtility/tests.js:468:14:468:16 | key | PrototypePollutionUtility/tests.js:473:17:473:19 | key | +| PrototypePollutionUtility/tests.js:468:14:468:16 | key | PrototypePollutionUtility/tests.js:473:17:473:19 | key | +| PrototypePollutionUtility/tests.js:468:14:468:16 | key | PrototypePollutionUtility/tests.js:473:28:473:30 | key | +| PrototypePollutionUtility/tests.js:468:14:468:16 | key | PrototypePollutionUtility/tests.js:473:28:473:30 | key | +| PrototypePollutionUtility/tests.js:471:29:471:31 | dst | PrototypePollutionUtility/tests.js:471:29:471:36 | dst[key] | +| PrototypePollutionUtility/tests.js:471:29:471:36 | dst[key] | PrototypePollutionUtility/tests.js:467:26:467:28 | dst | +| PrototypePollutionUtility/tests.js:471:29:471:36 | dst[key] | PrototypePollutionUtility/tests.js:467:26:467:28 | dst | +| PrototypePollutionUtility/tests.js:471:33:471:35 | key | PrototypePollutionUtility/tests.js:471:29:471:36 | dst[key] | +| PrototypePollutionUtility/tests.js:471:39:471:41 | src | PrototypePollutionUtility/tests.js:471:39:471:46 | src[key] | +| PrototypePollutionUtility/tests.js:471:39:471:46 | src[key] | PrototypePollutionUtility/tests.js:467:31:467:33 | src | +| PrototypePollutionUtility/tests.js:471:39:471:46 | src[key] | PrototypePollutionUtility/tests.js:467:31:467:33 | src | +| PrototypePollutionUtility/tests.js:471:39:471:46 | src[key] | PrototypePollutionUtility/tests.js:467:31:467:33 | src | +| PrototypePollutionUtility/tests.js:471:39:471:46 | src[key] | PrototypePollutionUtility/tests.js:467:31:467:33 | src | +| PrototypePollutionUtility/tests.js:471:39:471:46 | src[key] | PrototypePollutionUtility/tests.js:467:31:467:33 | src | +| PrototypePollutionUtility/tests.js:471:43:471:45 | key | PrototypePollutionUtility/tests.js:471:39:471:46 | src[key] | +| PrototypePollutionUtility/tests.js:473:24:473:26 | src | PrototypePollutionUtility/tests.js:473:24:473:31 | src[key] | +| PrototypePollutionUtility/tests.js:473:24:473:26 | src | PrototypePollutionUtility/tests.js:473:24:473:31 | src[key] | +| PrototypePollutionUtility/tests.js:473:24:473:26 | src | PrototypePollutionUtility/tests.js:473:24:473:31 | src[key] | +| PrototypePollutionUtility/tests.js:473:24:473:26 | src | PrototypePollutionUtility/tests.js:473:24:473:31 | src[key] | +| PrototypePollutionUtility/tests.js:473:24:473:31 | src[key] | PrototypePollutionUtility/tests.js:473:24:473:31 | src[key] | +| PrototypePollutionUtility/tests.js:473:28:473:30 | key | PrototypePollutionUtility/tests.js:473:24:473:31 | src[key] | +| PrototypePollutionUtility/tests.js:473:28:473:30 | key | PrototypePollutionUtility/tests.js:473:24:473:31 | src[key] | +| PrototypePollutionUtility/tests.js:478:32:478:34 | src | PrototypePollutionUtility/tests.js:482:21:482:23 | src | +| PrototypePollutionUtility/tests.js:479:14:479:16 | key | PrototypePollutionUtility/tests.js:482:25:482:27 | key | +| PrototypePollutionUtility/tests.js:479:14:479:16 | key | PrototypePollutionUtility/tests.js:482:25:482:27 | key | +| PrototypePollutionUtility/tests.js:479:14:479:16 | key | PrototypePollutionUtility/tests.js:486:17:486:19 | key | +| PrototypePollutionUtility/tests.js:479:14:479:16 | key | PrototypePollutionUtility/tests.js:486:17:486:19 | key | +| PrototypePollutionUtility/tests.js:479:14:479:16 | key | PrototypePollutionUtility/tests.js:486:17:486:19 | key | +| PrototypePollutionUtility/tests.js:479:14:479:16 | key | PrototypePollutionUtility/tests.js:486:17:486:19 | key | +| PrototypePollutionUtility/tests.js:482:13:482:28 | value | PrototypePollutionUtility/tests.js:484:38:484:42 | value | +| PrototypePollutionUtility/tests.js:482:13:482:28 | value | PrototypePollutionUtility/tests.js:484:38:484:42 | value | +| PrototypePollutionUtility/tests.js:482:13:482:28 | value | PrototypePollutionUtility/tests.js:486:24:486:28 | value | +| PrototypePollutionUtility/tests.js:482:13:482:28 | value | PrototypePollutionUtility/tests.js:486:24:486:28 | value | +| PrototypePollutionUtility/tests.js:482:13:482:28 | value | PrototypePollutionUtility/tests.js:486:24:486:28 | value | +| PrototypePollutionUtility/tests.js:482:13:482:28 | value | PrototypePollutionUtility/tests.js:486:24:486:28 | value | +| PrototypePollutionUtility/tests.js:482:13:482:28 | value | PrototypePollutionUtility/tests.js:486:24:486:28 | value | +| PrototypePollutionUtility/tests.js:482:13:482:28 | value | PrototypePollutionUtility/tests.js:486:24:486:28 | value | +| PrototypePollutionUtility/tests.js:482:21:482:23 | src | PrototypePollutionUtility/tests.js:482:21:482:28 | src[key] | +| PrototypePollutionUtility/tests.js:482:21:482:28 | src[key] | PrototypePollutionUtility/tests.js:482:13:482:28 | value | +| PrototypePollutionUtility/tests.js:482:21:482:28 | src[key] | PrototypePollutionUtility/tests.js:482:13:482:28 | value | +| PrototypePollutionUtility/tests.js:482:21:482:28 | src[key] | PrototypePollutionUtility/tests.js:482:13:482:28 | value | +| PrototypePollutionUtility/tests.js:482:21:482:28 | src[key] | PrototypePollutionUtility/tests.js:482:13:482:28 | value | +| PrototypePollutionUtility/tests.js:482:21:482:28 | src[key] | PrototypePollutionUtility/tests.js:482:13:482:28 | value | +| PrototypePollutionUtility/tests.js:482:25:482:27 | key | PrototypePollutionUtility/tests.js:482:21:482:28 | src[key] | +| PrototypePollutionUtility/tests.js:484:38:484:42 | value | PrototypePollutionUtility/tests.js:478:32:478:34 | src | +| PrototypePollutionUtility/tests.js:484:38:484:42 | value | PrototypePollutionUtility/tests.js:478:32:478:34 | 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 | @@ -2583,4 +2689,5 @@ edges | PrototypePollutionUtility/tests.js:450:30:450:32 | dst | PrototypePollutionUtility/tests.js:444:25:444:27 | key | PrototypePollutionUtility/tests.js:450:30:450:32 | dst | Properties are copied from $@ to $@ without guarding against prototype pollution. | PrototypePollutionUtility/tests.js:444:12:444:14 | src | src | PrototypePollutionUtility/tests.js:450:30:450:32 | dst | dst | | PrototypePollutionUtility/tests.js:451:30:451:32 | dst | PrototypePollutionUtility/tests.js:444:25:444:27 | key | PrototypePollutionUtility/tests.js:451:30:451:32 | dst | Properties are copied from $@ to $@ without guarding against prototype pollution. | PrototypePollutionUtility/tests.js:444:12:444:14 | src | src | PrototypePollutionUtility/tests.js:451:30:451:32 | dst | dst | | PrototypePollutionUtility/tests.js:461:13:461:15 | dst | PrototypePollutionUtility/tests.js:457:25:457:27 | key | PrototypePollutionUtility/tests.js:461:13:461:15 | dst | Properties are copied from $@ to $@ without guarding against prototype pollution. | PrototypePollutionUtility/tests.js:457:12:457:14 | src | src | PrototypePollutionUtility/tests.js:461:13:461:15 | dst | dst | +| PrototypePollutionUtility/tests.js:473:13:473:15 | dst | PrototypePollutionUtility/tests.js:468:14:468:16 | key | PrototypePollutionUtility/tests.js:473:13:473:15 | dst | Properties are copied from $@ to $@ without guarding against prototype pollution. | PrototypePollutionUtility/tests.js:468:21:468:23 | src | src | PrototypePollutionUtility/tests.js:473:13:473: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 2de68103c63..6bcfcefbadc 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 @@ -462,3 +462,28 @@ function copyUsingUnderscoreOrLodash(dst, src) { } }); } + +let isPlainObject = require('is-plain-object'); +function copyPlainObject(dst, src) { + for (let key in src) { + if (key === '__proto__') continue; + if (dst[key] && isPlainObject(src)) { + copyPlainObject(dst[key], src[key]); + } else { + dst[key] = src[key]; // OK - but flagged anyway + } + } +} + +function copyPlainObject2(dst, src) { + for (let key in src) { + if (key === '__proto__') continue; + let target = dst[key]; + let value = src[key]; + if (isPlainObject(target) && isPlainObject(value)) { + copyPlainObject2(target, value); + } else { + dst[key] = value; // OK + } + } +} diff --git a/javascript/upgrades/dad09eeeff5cf8c9c2b674d5053c63ab44e091df/old.dbscheme b/javascript/upgrades/dad09eeeff5cf8c9c2b674d5053c63ab44e091df/old.dbscheme new file mode 100644 index 00000000000..dad09eeeff5 --- /dev/null +++ b/javascript/upgrades/dad09eeeff5cf8c9c2b674d5053c63ab44e091df/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 | @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/dad09eeeff5cf8c9c2b674d5053c63ab44e091df/semmlecode.javascript.dbscheme b/javascript/upgrades/dad09eeeff5cf8c9c2b674d5053c63ab44e091df/semmlecode.javascript.dbscheme new file mode 100644 index 00000000000..03c078a7e6e --- /dev/null +++ b/javascript/upgrades/dad09eeeff5cf8c9c2b674d5053c63ab44e091df/semmlecode.javascript.dbscheme @@ -0,0 +1,1189 @@ +/*** 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; + +@import_or_export_declaration = @importdeclaration | @exportdeclaration; + +@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); +hasTypeKeyword (int id: @import_or_export_declaration 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/dad09eeeff5cf8c9c2b674d5053c63ab44e091df/upgrade.properties b/javascript/upgrades/dad09eeeff5cf8c9c2b674d5053c63ab44e091df/upgrade.properties new file mode 100644 index 00000000000..d0a417d20a6 --- /dev/null +++ b/javascript/upgrades/dad09eeeff5cf8c9c2b674d5053c63ab44e091df/upgrade.properties @@ -0,0 +1,2 @@ +description: add support for TypeScript 3.8 +compatibility: backwards diff --git a/python/ql/src/Classes/WrongNameForArgumentInClassInstantiation.ql b/python/ql/src/Classes/WrongNameForArgumentInClassInstantiation.ql index 33bf524ae94..022d6a515e6 100644 --- a/python/ql/src/Classes/WrongNameForArgumentInClassInstantiation.ql +++ b/python/ql/src/Classes/WrongNameForArgumentInClassInstantiation.ql @@ -20,8 +20,8 @@ import Expressions.CallArgs from Call call, ClassObject cls, string name, FunctionObject init where - illegally_named_parameter(call, cls, name) - and init = get_function_or_initializer(cls) + illegally_named_parameter_objectapi(call, cls, name) + and init = get_function_or_initializer_objectapi(cls) select call, "Keyword argument '" + name + "' is not a supported parameter name of $@.", init, init.getQualifiedName() diff --git a/python/ql/src/Classes/WrongNumberArgumentsInClassInstantiation.ql b/python/ql/src/Classes/WrongNumberArgumentsInClassInstantiation.ql index 915856319e0..f94b5ac5b3e 100644 --- a/python/ql/src/Classes/WrongNumberArgumentsInClassInstantiation.ql +++ b/python/ql/src/Classes/WrongNumberArgumentsInClassInstantiation.ql @@ -18,8 +18,8 @@ import Expressions.CallArgs from Call call, ClassObject cls, string too, string should, int limit, FunctionObject init where ( - too_many_args(call, cls, limit) and too = "too many arguments" and should = "no more than " + too_many_args_objectapi(call, cls, limit) and too = "too many arguments" and should = "no more than " or - too_few_args(call, cls, limit) and too = "too few arguments" and should = "no fewer than " -) and init = get_function_or_initializer(cls) + too_few_args_objectapi(call, cls, limit) and too = "too few arguments" and should = "no fewer than " +) and init = get_function_or_initializer_objectapi(cls) select call, "Call to $@ with " + too + "; should be " + should + limit.toString() + ".", init, init.getQualifiedName() diff --git a/python/ql/src/Expressions/CallArgs.qll b/python/ql/src/Expressions/CallArgs.qll index fc61f38a826..72326a1c30c 100644 --- a/python/ql/src/Expressions/CallArgs.qll +++ b/python/ql/src/Expressions/CallArgs.qll @@ -2,7 +2,7 @@ import python import Testing.Mox -private int varargs_length(Call call) { +private int varargs_length_objectapi(Call call) { not exists(call.getStarargs()) and result = 0 or exists(TupleObject t | @@ -14,7 +14,7 @@ private int varargs_length(Call call) { } /** Gets a keyword argument that is not a keyword-only parameter. */ -private Keyword not_keyword_only_arg(Call call, FunctionObject func) { +private Keyword not_keyword_only_arg_objectapi(Call call, FunctionObject func) { func.getACall().getNode() = call and result = call.getAKeyword() and not func.getFunction().getAKeywordOnlyArg().getId() = result.getArg() @@ -26,33 +26,33 @@ private Keyword not_keyword_only_arg(Call call, FunctionObject func) { * plus the number of keyword arguments that do not match keyword-only arguments (if the function does not take **kwargs). */ -private int positional_arg_count_for_call(Call call, Object callable) { - call = get_a_call(callable).getNode() and +private int positional_arg_count_objectapi_for_call_objectapi(Call call, Object callable) { + call = get_a_call_objectapi(callable).getNode() and exists(int positional_keywords | - exists(FunctionObject func | func = get_function_or_initializer(callable) | + exists(FunctionObject func | func = get_function_or_initializer_objectapi(callable) | not func.getFunction().hasKwArg() and - positional_keywords = count(not_keyword_only_arg(call, func)) + positional_keywords = count(not_keyword_only_arg_objectapi(call, func)) or func.getFunction().hasKwArg() and positional_keywords = 0 ) | - result = count(call.getAnArg()) + varargs_length(call) + positional_keywords + result = count(call.getAnArg()) + varargs_length_objectapi(call) + positional_keywords ) } -int arg_count(Call call) { - result = count(call.getAnArg()) + varargs_length(call) + count(call.getAKeyword()) +int arg_count_objectapi(Call call) { + result = count(call.getAnArg()) + varargs_length_objectapi(call) + count(call.getAKeyword()) } /* Gets a call corresponding to the given class or function*/ -private ControlFlowNode get_a_call(Object callable) { +private ControlFlowNode get_a_call_objectapi(Object callable) { result = callable.(ClassObject).getACall() or result = callable.(FunctionObject).getACall() } /* Gets the function object corresponding to the given class or function*/ -FunctionObject get_function_or_initializer(Object func_or_cls) { +FunctionObject get_function_or_initializer_objectapi(Object func_or_cls) { result = func_or_cls.(FunctionObject) or result = func_or_cls.(ClassObject).declaredAttribute("__init__") @@ -60,20 +60,20 @@ FunctionObject get_function_or_initializer(Object func_or_cls) { /**Whether there is an illegally named parameter called `name` in the `call` to `func` */ -predicate illegally_named_parameter(Call call, Object func, string name) { +predicate illegally_named_parameter_objectapi(Call call, Object func, string name) { not func.isC() and name = call.getANamedArgumentName() and - call.getAFlowNode() = get_a_call(func) and - not get_function_or_initializer(func).isLegalArgumentName(name) + call.getAFlowNode() = get_a_call_objectapi(func) and + not get_function_or_initializer_objectapi(func).isLegalArgumentName(name) } /**Whether there are too few arguments in the `call` to `callable` where `limit` is the lowest number of legal arguments */ -predicate too_few_args(Call call, Object callable, int limit) { +predicate too_few_args_objectapi(Call call, Object callable, int limit) { // Exclude cases where an incorrect name is used as that is covered by 'Wrong name for an argument in a call' - not illegally_named_parameter(call, callable, _) and + not illegally_named_parameter_objectapi(call, callable, _) and not exists(call.getStarargs()) and not exists(call.getKwargs()) and - arg_count(call) < limit and - exists(FunctionObject func | func = get_function_or_initializer(callable) | + arg_count_objectapi(call) < limit and + exists(FunctionObject func | func = get_function_or_initializer_objectapi(callable) | call = func.getAFunctionCall().getNode() and limit = func.minParameters() and /* The combination of misuse of `mox.Mox().StubOutWithMock()` * and a bug in mox's implementation of methods results in having to @@ -84,16 +84,16 @@ predicate too_few_args(Call call, Object callable, int limit) { call = func.getAMethodCall().getNode() and limit = func.minParameters() - 1 or callable instanceof ClassObject and - call.getAFlowNode() = get_a_call(callable) and limit = func.minParameters() - 1 + call.getAFlowNode() = get_a_call_objectapi(callable) and limit = func.minParameters() - 1 ) } /**Whether there are too many arguments in the `call` to `func` where `limit` is the highest number of legal arguments */ -predicate too_many_args(Call call, Object callable, int limit) { +predicate too_many_args_objectapi(Call call, Object callable, int limit) { // Exclude cases where an incorrect name is used as that is covered by 'Wrong name for an argument in a call' - not illegally_named_parameter(call, callable, _) and + not illegally_named_parameter_objectapi(call, callable, _) and exists(FunctionObject func | - func = get_function_or_initializer(callable) and + func = get_function_or_initializer_objectapi(callable) and not func.getFunction().hasVarArg() and limit >= 0 | call = func.getAFunctionCall().getNode() and limit = func.maxParameters() @@ -101,30 +101,30 @@ predicate too_many_args(Call call, Object callable, int limit) { call = func.getAMethodCall().getNode() and limit = func.maxParameters() - 1 or callable instanceof ClassObject and - call.getAFlowNode() = get_a_call(callable) and limit = func.maxParameters() - 1 + call.getAFlowNode() = get_a_call_objectapi(callable) and limit = func.maxParameters() - 1 ) and - positional_arg_count_for_call(call, callable) > limit + positional_arg_count_objectapi_for_call_objectapi(call, callable) > limit } /** Holds if `call` has too many or too few arguments for `func` */ -predicate wrong_args(Call call, FunctionObject func, int limit, string too) { - too_few_args(call, func, limit) and too = "too few" +predicate wrong_args_objectapi(Call call, FunctionObject func, int limit, string too) { + too_few_args_objectapi(call, func, limit) and too = "too few" or - too_many_args(call, func, limit) and too = "too many" + too_many_args_objectapi(call, func, limit) and too = "too many" } /** Holds if `call` has correct number of arguments for `func`. * Implies nothing about whether `call` could call `func`. */ bindingset[call, func] -predicate correct_args_if_called_as_method(Call call, FunctionObject func) { - arg_count(call)+1 >= func.minParameters() +predicate correct_args_if_called_as_method_objectapi(Call call, FunctionObject func) { + arg_count_objectapi(call)+1 >= func.minParameters() and - arg_count(call) < func.maxParameters() + arg_count_objectapi(call) < func.maxParameters() } /** Holds if `call` is a call to `overriding`, which overrides `func`. */ -predicate overridden_call(FunctionObject func, FunctionObject overriding, Call call) { +predicate overridden_call_objectapi(FunctionObject func, FunctionObject overriding, Call call) { overriding.overrides(func) and overriding.getACall().getNode() = call } diff --git a/python/ql/src/Expressions/HashedButNoHash.ql b/python/ql/src/Expressions/HashedButNoHash.ql index a654793d458..37da9407b7f 100644 --- a/python/ql/src/Expressions/HashedButNoHash.ql +++ b/python/ql/src/Expressions/HashedButNoHash.ql @@ -69,7 +69,7 @@ predicate is_unhashable(ControlFlowNode f, ClassValue cls, ControlFlowNode origi predicate typeerror_is_caught(ControlFlowNode f) { exists (Try try | try.getBody().contains(f.getNode()) and - try.getAHandler().getType().refersTo(theTypeErrorType())) + try.getAHandler().getType().pointsTo(ClassValue::typeError())) } from ControlFlowNode f, ClassValue c, ControlFlowNode origin diff --git a/python/ql/src/Expressions/UnnecessaryLambda.ql b/python/ql/src/Expressions/UnnecessaryLambda.ql index 93b78238e9a..2d7bbf72682 100644 --- a/python/ql/src/Expressions/UnnecessaryLambda.ql +++ b/python/ql/src/Expressions/UnnecessaryLambda.ql @@ -38,14 +38,14 @@ predicate unnecessary_lambda(Lambda l, Expr e) { simple_wrapper(l, e) and ( /* plain class */ - exists(ClassObject c | e.refersTo(c)) + exists(ClassValue c | e.pointsTo(c)) or /* plain function */ - exists(FunctionObject f | e.refersTo(f)) + exists(FunctionValue f | e.pointsTo(f)) or /* bound-method of enclosing instance */ - exists(ClassObject cls, Attribute a | - cls.getPyClass() = l.getScope().getScope() and a = e | + exists(ClassValue cls, Attribute a | + cls.getScope() = l.getScope().getScope() and a = e | ((Name)a.getObject()).getId() = "self" and cls.hasAttribute(a.getName()) ) diff --git a/python/ql/src/Expressions/WrongNameForArgumentInCall.ql b/python/ql/src/Expressions/WrongNameForArgumentInCall.ql index 92c17f8e2ee..6abab859f5f 100644 --- a/python/ql/src/Expressions/WrongNameForArgumentInCall.ql +++ b/python/ql/src/Expressions/WrongNameForArgumentInCall.ql @@ -19,7 +19,7 @@ import Expressions.CallArgs from Call call, FunctionObject func, string name where -illegally_named_parameter(call, func, name) and +illegally_named_parameter_objectapi(call, func, name) and not func.isAbstract() and not exists(FunctionObject overridden | func.overrides(overridden) and overridden.getFunction().getAnArg().(Name).getId() = name) select diff --git a/python/ql/src/Expressions/WrongNumberArgumentsInCall.ql b/python/ql/src/Expressions/WrongNumberArgumentsInCall.ql index b31e9e70445..9f636213a34 100644 --- a/python/ql/src/Expressions/WrongNumberArgumentsInCall.ql +++ b/python/ql/src/Expressions/WrongNumberArgumentsInCall.ql @@ -17,12 +17,12 @@ import CallArgs from Call call, FunctionObject func, string too, string should, int limit where ( - too_many_args(call, func, limit) and too = "too many arguments" and should = "no more than " + too_many_args_objectapi(call, func, limit) and too = "too many arguments" and should = "no more than " or - too_few_args(call, func, limit) and too = "too few arguments" and should = "no fewer than " + too_few_args_objectapi(call, func, limit) and too = "too few arguments" and should = "no fewer than " ) and not func.isAbstract() and -not exists(FunctionObject overridden | func.overrides(overridden) and correct_args_if_called_as_method(call, overridden)) +not exists(FunctionObject overridden | func.overrides(overridden) and correct_args_if_called_as_method_objectapi(call, overridden)) /* The semantics of `__new__` can be a bit subtle, so we simply exclude `__new__` methods */ and not func.getName() = "__new__" diff --git a/python/ql/src/Functions/IncorrectlyOverriddenMethod.ql b/python/ql/src/Functions/IncorrectlyOverriddenMethod.ql index e5d3947a1a7..a425079cce0 100644 --- a/python/ql/src/Functions/IncorrectlyOverriddenMethod.ql +++ b/python/ql/src/Functions/IncorrectlyOverriddenMethod.ql @@ -15,10 +15,10 @@ import Expressions.CallArgs from Call call, FunctionObject func, FunctionObject overridden, string problem where func.overrides(overridden) and ( - wrong_args(call, func, _, problem) and correct_args_if_called_as_method(call, overridden) + wrong_args_objectapi(call, func, _, problem) and correct_args_if_called_as_method_objectapi(call, overridden) or exists(string name | - illegally_named_parameter(call, func, name) and problem = "an argument named '" + name + "'" and + illegally_named_parameter_objectapi(call, func, name) and problem = "an argument named '" + name + "'" and overridden.getFunction().getAnArg().(Name).getId() = name ) ) diff --git a/python/ql/src/Functions/IncorrectlySpecifiedOverriddenMethod.ql b/python/ql/src/Functions/IncorrectlySpecifiedOverriddenMethod.ql index 3af03a23602..9636c7c22db 100644 --- a/python/ql/src/Functions/IncorrectlySpecifiedOverriddenMethod.ql +++ b/python/ql/src/Functions/IncorrectlySpecifiedOverriddenMethod.ql @@ -18,11 +18,11 @@ where not func.getName() = "__init__" and overriding.overrides(func) and call = overriding.getAMethodCall().getNode() and -correct_args_if_called_as_method(call, overriding) and +correct_args_if_called_as_method_objectapi(call, overriding) and ( - arg_count(call)+1 < func.minParameters() and problem = "too few arguments" + arg_count_objectapi(call)+1 < func.minParameters() and problem = "too few arguments" or - arg_count(call) >= func.maxParameters() and problem = "too many arguments" + arg_count_objectapi(call) >= func.maxParameters() and problem = "too many arguments" or exists(string name | call.getAKeyword().getArg() = name and overriding.getFunction().getAnArg().(Name).getId() = name and diff --git a/python/ql/src/semmle/python/AstGenerated.qll b/python/ql/src/semmle/python/AstGenerated.qll index fc79a01d8b7..6832b3f2da6 100644 --- a/python/ql/src/semmle/python/AstGenerated.qll +++ b/python/ql/src/semmle/python/AstGenerated.qll @@ -1,3 +1,8 @@ +/* + * This library file is auto-generated by 'semmle/query_gen.py'. + * WARNING: Any modifications to this file will be lost. + * Relations can be changed by modifying master.py. + */ import python library class Add_ extends @py_Add, Operator { @@ -1781,6 +1786,37 @@ library class Slice_ extends @py_Slice, Expr { } +library class SpecialOperation_ extends @py_SpecialOperation, Expr { + + + /** Gets the name of this special operation. */ + string getName() { + py_strs(result, this, 2) + } + + + /** Gets the arguments of this special operation. */ + ExprList getArguments() { + py_expr_lists(result, this, 3) + } + + + /** Gets the nth argument of this special operation. */ + Expr getArgument(int index) { + result = this.getArguments().getItem(index) + } + + /** Gets an argument of this special operation. */ + Expr getAnArgument() { + result = this.getArguments().getAnItem() + } + + override string toString() { + result = "SpecialOperation" + } + +} + library class Starred_ extends @py_Starred, Expr { diff --git a/python/ql/src/semmle/python/objects/ObjectAPI.qll b/python/ql/src/semmle/python/objects/ObjectAPI.qll index a4a93a88d7d..1f1f7001da1 100644 --- a/python/ql/src/semmle/python/objects/ObjectAPI.qll +++ b/python/ql/src/semmle/python/objects/ObjectAPI.qll @@ -698,40 +698,57 @@ module ClassValue { ClassValue bool() { result = TBuiltinClassObject(Builtin::special("bool")) } - + + /** Get the `ClassValue` for the `tuple` class. */ + ClassValue tuple() { + result = TBuiltinClassObject(Builtin::special("tuple")) + } + + /** Get the `ClassValue` for the `list` class. */ + ClassValue list() { + result = TBuiltinClassObject(Builtin::special("list")) + } + + /** Get the `ClassValue` for `xrange` (Python 2), or `range` (only Python 3) */ + ClassValue range() { + major_version() = 2 and result = TBuiltinClassObject(Builtin::special("xrange")) + or + major_version() = 3 and result = TBuiltinClassObject(Builtin::special("range")) + } + /** Get the `ClassValue` for the `dict` class. */ ClassValue dict() { result = TBuiltinClassObject(Builtin::special("dict")) } - - /** Get the `ClassValue` for the class of Python functions. */ - ClassValue function() { - result = TBuiltinClassObject(Builtin::special("FunctionType")) + + /** Get the `ClassValue` for the `set` class. */ + ClassValue set() { + result = TBuiltinClassObject(Builtin::special("set")) } - - /** Get the `ClassValue` for the `type` class. */ - ClassValue type() { - result = TType() - } - - /** Get the `ClassValue` for the class of builtin functions. */ - ClassValue builtinFunction() { - result = Value::named("len").getClass() + + /** Get the `ClassValue` for the `object` class. */ + ClassValue object() { + result = TBuiltinClassObject(Builtin::special("object")) } /** Get the `ClassValue` for the `int` class. */ ClassValue int_() { result = TBuiltinClassObject(Builtin::special("int")) } + + /** Get the `ClassValue` for the `long` class. */ + ClassValue long() { + result = TBuiltinClassObject(Builtin::special("long")) + } /** Get the `ClassValue` for the `float` class. */ ClassValue float_() { result = TBuiltinClassObject(Builtin::special("float")) } - - /** Get the `ClassValue` for the `list` class. */ - ClassValue list() { - result = TBuiltinClassObject(Builtin::special("list")) + + /** Get the `ClassValue` for the `complex` class. */ + ClassValue complex() { + result = TBuiltinClassObject(Builtin::special("complex")) } /** Get the `ClassValue` for the `bytes` class (also called `str` in Python 2). */ @@ -753,7 +770,47 @@ module ClassValue { else result = unicode() } + + /** Get the `ClassValue` for the `property` class. */ + ClassValue property() { + result = TBuiltinClassObject(Builtin::special("property")) + } + + /** Get the `ClassValue` for the class of Python functions. */ + ClassValue functionType() { + result = TBuiltinClassObject(Builtin::special("FunctionType")) + } + /** Get the `ClassValue` for the class of builtin functions. */ + ClassValue builtinFunction() { + result = Value::named("len").getClass() + } + + /** Get the `ClassValue` for the `generatorType` class. */ + ClassValue generator() { + result = TBuiltinClassObject(Builtin::special("generator")) + } + + /** Get the `ClassValue` for the `type` class. */ + ClassValue type() { + result = TType() + } + + /** Get the `ClassValue` for `ClassType`. */ + ClassValue classType() { + result = TBuiltinClassObject(Builtin::special("ClassType")) + } + + /** Get the `ClassValue` for `InstanceType`. */ + ClassValue instanceType() { + result = TBuiltinClassObject(Builtin::special("InstanceType")) + } + + /** Get the `ClassValue` for `super`. */ + ClassValue super_() { + result = TBuiltinClassObject(Builtin::special("super")) + } + /** Get the `ClassValue` for the `classmethod` class. */ ClassValue classmethod() { result = TBuiltinClassObject(Builtin::special("ClassMethod")) @@ -763,21 +820,77 @@ module ClassValue { ClassValue staticmethod() { result = TBuiltinClassObject(Builtin::special("StaticMethod")) } + + /** Get the `ClassValue` for the `MethodType` class. */ + pragma [noinline] + ClassValue methodType() { + result = TBuiltinClassObject(Builtin::special("MethodType")) + } + + /** Get the `ClassValue` for the `MethodDescriptorType` class. */ + ClassValue methodDescriptorType() { + result = TBuiltinClassObject(Builtin::special("MethodDescriptorType")) + } + + /** Get the `ClassValue` for the `GetSetDescriptorType` class. */ + ClassValue getSetDescriptorType() { + result = TBuiltinClassObject(Builtin::special("GetSetDescriptorType")) + } + + /** Get the `ClassValue` for the `StopIteration` class. */ + ClassValue stopIteration() { + result = TBuiltinClassObject(Builtin::special("StopIteration")) + } /** Get the `ClassValue` for the class of modules. */ ClassValue module_() { result = TBuiltinClassObject(Builtin::special("ModuleType")) } + /** Get the `ClassValue` for the `Exception` class. */ + ClassValue exception() { + result = TBuiltinClassObject(Builtin::special("Exception")) + } + + /** Get the `ClassValue` for the `BaseException` class. */ + ClassValue baseException() { + result = TBuiltinClassObject(Builtin::special("BaseException")) + } + /** Get the `ClassValue` for the `NoneType` class. */ ClassValue nonetype() { result = TBuiltinClassObject(Builtin::special("NoneType")) } + + /** Get the `ClassValue` for the `TypeError` class */ + ClassValue typeError() { + result = TBuiltinClassObject(Builtin::special("TypeError")) + } /** Get the `ClassValue` for the `NameError` class. */ ClassValue nameError() { result = TBuiltinClassObject(Builtin::builtin("NameError")) } + + /** Get the `ClassValue` for the `AttributeError` class. */ + ClassValue attributeError() { + result = TBuiltinClassObject(Builtin::builtin("AttributeError")) + } + + /** Get the `ClassValue` for the `KeyError` class. */ + ClassValue keyError() { + result = TBuiltinClassObject(Builtin::builtin("KeyError")) + } + + /** Get the `ClassValue` for the `IOError` class. */ + ClassValue ioError() { + result = TBuiltinClassObject(Builtin::builtin("IOError")) + } + + /** Get the `ClassValue` for the `NotImplementedError` class. */ + ClassValue notImplementedError() { + result = TBuiltinClassObject(Builtin::builtin("NotImplementedError")) + } /** Get the `ClassValue` for the `ImportError` class. */ ClassValue importError() { diff --git a/python/ql/src/semmle/python/web/ClientHttpRequest.qll b/python/ql/src/semmle/python/web/ClientHttpRequest.qll new file mode 100644 index 00000000000..edcd236e031 --- /dev/null +++ b/python/ql/src/semmle/python/web/ClientHttpRequest.qll @@ -0,0 +1,2 @@ +import semmle.python.web.client.StdLib +import semmle.python.web.client.Requests diff --git a/python/ql/src/semmle/python/web/Http.qll b/python/ql/src/semmle/python/web/Http.qll index ce9435a63ec..80b8885d62e 100644 --- a/python/ql/src/semmle/python/web/Http.qll +++ b/python/ql/src/semmle/python/web/Http.qll @@ -89,7 +89,7 @@ abstract class CookieSet extends CookieOperation {} /** Generic taint sink in a http response */ abstract class HttpResponseTaintSink extends TaintSink { - override predicate sinks(TaintKind kind) { + override predicate sinks(TaintKind kind) { kind instanceof ExternalStringKind } @@ -97,9 +97,51 @@ abstract class HttpResponseTaintSink extends TaintSink { abstract class HttpRedirectTaintSink extends TaintSink { - override predicate sinks(TaintKind kind) { + override predicate sinks(TaintKind kind) { kind instanceof ExternalStringKind } } +module Client { + + // TODO: user-input in other than URL: + // - `data`, `json` for `requests.post` + // - `body` for `HTTPConnection.request` + // - headers? + + // TODO: Add more library support + // - urllib3 https://github.com/urllib3/urllib3 + // - httpx https://github.com/encode/httpx + + /** + * An outgoing http request + * + * For example: + * conn = HTTPConnection('example.com') + conn.request('GET', '/path') + */ + abstract class HttpRequest extends ControlFlowNode { + + /** Get any ControlFlowNode that is used to construct the final URL. + * + * In the HTTPConnection example, there is a result for both `'example.com'` and for `'/path'`. + */ + abstract ControlFlowNode getAUrlPart(); + + abstract string getMethodUpper(); + } + + /** Taint sink for the URL-part of an outgoing http request */ + class HttpRequestUrlTaintSink extends TaintSink { + + HttpRequestUrlTaintSink() { + this = any(HttpRequest r).getAUrlPart() + } + + override predicate sinks(TaintKind kind) { + kind instanceof ExternalStringKind + } + + } +} diff --git a/python/ql/src/semmle/python/web/client/Requests.qll b/python/ql/src/semmle/python/web/client/Requests.qll new file mode 100644 index 00000000000..6899c651fa6 --- /dev/null +++ b/python/ql/src/semmle/python/web/client/Requests.qll @@ -0,0 +1,22 @@ +/** + * Modeling outgoing HTTP requests using the `requests` package + * https://pypi.org/project/requests/ + */ + +import python +private import semmle.python.web.Http + +class RequestsHttpRequest extends Client::HttpRequest, CallNode { + CallableValue func; + string method; + + RequestsHttpRequest() { + method = httpVerbLower() and + func = Module::named("requests").attr(method) and + this = func.getACall() + } + + override ControlFlowNode getAUrlPart() { result = func.getNamedArgumentForCall(this, "url") } + + override string getMethodUpper() { result = method.toUpperCase() } +} diff --git a/python/ql/src/semmle/python/web/client/StdLib.qll b/python/ql/src/semmle/python/web/client/StdLib.qll new file mode 100644 index 00000000000..9ee089a47f5 --- /dev/null +++ b/python/ql/src/semmle/python/web/client/StdLib.qll @@ -0,0 +1,55 @@ +import python +private import semmle.python.web.Http + +ClassValue httpConnectionClass() { + // Python 2 + result = Value::named("httplib.HTTPConnection") + or + result = Value::named("httplib.HTTPSConnection") + or + // Python 3 + result = Value::named("http.client.HTTPConnection") + or + result = Value::named("http.client.HTTPSConnection") + or + // six + result = Value::named("six.moves.http_client.HTTPConnection") + or + result = Value::named("six.moves.http_client.HTTPSConnection") +} + +class HttpConnectionHttpRequest extends Client::HttpRequest, CallNode { + CallNode constructor_call; + CallableValue func; + + HttpConnectionHttpRequest() { + exists(ClassValue cls, AttrNode call_origin, Value constructor_call_value | + cls = httpConnectionClass() and + func = cls.lookup("request") and + this = func.getACall() and + // since you can do `r = conn.request; r('GET', path)`, we need to find the origin + this.getFunction().pointsTo(_, _, call_origin) and + // Since HTTPSConnection is a subtype of HTTPConnection, up until this point, `cls` could be either class, + // because `HTTPSConnection.request == HTTPConnection.request`. To avoid generating 2 results, we filter + // on the actual class used as the constructor + call_origin.getObject().pointsTo(_, constructor_call_value, constructor_call) and + cls = constructor_call_value.getClass() and + constructor_call = cls.getACall() + ) + } + + override ControlFlowNode getAUrlPart() { + result = func.getNamedArgumentForCall(this, "url") + or + result = constructor_call.getArg(0) + or + result = constructor_call.getArgByName("host") + } + + override string getMethodUpper() { + exists(string method | + result = method.toUpperCase() and + func.getNamedArgumentForCall(this, "method").pointsTo(Value::forString(method)) + ) + } +} diff --git a/python/ql/test/3/query-tests/Imports/encoding_error/EncodingError.expected b/python/ql/test/3/query-tests/Imports/encoding_error/EncodingError.expected new file mode 100644 index 00000000000..e69de29bb2d diff --git a/python/ql/test/3/query-tests/Imports/encoding_error/EncodingError.py b/python/ql/test/3/query-tests/Imports/encoding_error/EncodingError.py new file mode 100644 index 00000000000..b1623e43952 --- /dev/null +++ b/python/ql/test/3/query-tests/Imports/encoding_error/EncodingError.py @@ -0,0 +1,7 @@ +123456789 +223456789 +323456789 +423456789 +5234567テク9 +623456789 +723456789 diff --git a/python/ql/test/3/query-tests/Imports/encoding_error/EncodingError.qlref b/python/ql/test/3/query-tests/Imports/encoding_error/EncodingError.qlref new file mode 100644 index 00000000000..a7e91769ded --- /dev/null +++ b/python/ql/test/3/query-tests/Imports/encoding_error/EncodingError.qlref @@ -0,0 +1 @@ +Imports/EncodingError.ql diff --git a/python/ql/test/3/query-tests/Imports/syntax_error/EncodingError.expected b/python/ql/test/3/query-tests/Imports/syntax_error/EncodingError.expected new file mode 100644 index 00000000000..32fa7b97bf1 --- /dev/null +++ b/python/ql/test/3/query-tests/Imports/syntax_error/EncodingError.expected @@ -0,0 +1 @@ +| bad_encoding.py:11:19:11:19 | Encoding Error | 'utf-8' codec can't decode byte 0x82 in position 82: invalid start byte | diff --git a/python/ql/test/3/query-tests/Imports/syntax_error/EncodingError.qlref b/python/ql/test/3/query-tests/Imports/syntax_error/EncodingError.qlref new file mode 100644 index 00000000000..e742356f865 --- /dev/null +++ b/python/ql/test/3/query-tests/Imports/syntax_error/EncodingError.qlref @@ -0,0 +1 @@ +Imports/EncodingError.ql \ No newline at end of file diff --git a/python/ql/test/3/query-tests/Imports/syntax_error/SyntaxError.expected b/python/ql/test/3/query-tests/Imports/syntax_error/SyntaxError.expected new file mode 100644 index 00000000000..1dfa8041767 --- /dev/null +++ b/python/ql/test/3/query-tests/Imports/syntax_error/SyntaxError.expected @@ -0,0 +1 @@ +| nonsense.py:1:2:1:2 | Syntax Error | Syntax Error (in Python 3.5). | diff --git a/python/ql/test/3/query-tests/Imports/syntax_error/SyntaxError.qlref b/python/ql/test/3/query-tests/Imports/syntax_error/SyntaxError.qlref new file mode 100644 index 00000000000..c143a01fe8b --- /dev/null +++ b/python/ql/test/3/query-tests/Imports/syntax_error/SyntaxError.qlref @@ -0,0 +1 @@ +Imports/SyntaxError.ql \ No newline at end of file diff --git a/python/ql/test/3/query-tests/Imports/syntax_error/bad_encoding.py b/python/ql/test/3/query-tests/Imports/syntax_error/bad_encoding.py new file mode 100644 index 00000000000..9c61b1e1b11 --- /dev/null +++ b/python/ql/test/3/query-tests/Imports/syntax_error/bad_encoding.py @@ -0,0 +1,12 @@ +"""Multi-line docstring + + + + +""" + +# encoding:shift-jis + +def f(): + print "Python の開発は、1990 年ごろから開始されています" +""" diff --git a/python/ql/test/3/query-tests/Imports/syntax_error/nonsense.py b/python/ql/test/3/query-tests/Imports/syntax_error/nonsense.py new file mode 100644 index 00000000000..66cdd526fba --- /dev/null +++ b/python/ql/test/3/query-tests/Imports/syntax_error/nonsense.py @@ -0,0 +1,37 @@ + `Twas brillig, and the slithy toves + Did gyre and gimble in the wabe: +All mimsy were the borogoves, + And the mome raths outgrabe. + + +"Beware the Jabberwock, my son! + The jaws that bite, the claws that catch! +Beware the Jubjub bird, and shun + The frumious Bandersnatch!" + +He took his vorpal sword in hand: + Long time the manxome foe he sought -- +So rested he by the Tumtum tree, + And stood awhile in thought. + +And, as in uffish thought he stood, + The Jabberwock, with eyes of flame, +Came whiffling through the tulgey wood, + And burbled as it came! + +One, two! One, two! And through and through + The vorpal blade went snicker-snack! +He left it dead, and with its head + He went galumphing back. + +"And, has thou slain the Jabberwock? + Come to my arms, my beamish boy! +O frabjous day! Callooh! Callay!' + He chortled in his joy. + + +`Twas brillig, and the slithy toves + Did gyre and gimble in the wabe; +All mimsy were the borogoves, + And the mome raths outgrabe. + diff --git a/python/ql/test/3/query-tests/Imports/syntax_error/test.py b/python/ql/test/3/query-tests/Imports/syntax_error/test.py new file mode 100644 index 00000000000..ce438bd29eb --- /dev/null +++ b/python/ql/test/3/query-tests/Imports/syntax_error/test.py @@ -0,0 +1 @@ +print "Hello World" diff --git a/python/ql/test/library-tests/web/client/requests/ClientHttpRequests.expected b/python/ql/test/library-tests/web/client/requests/ClientHttpRequests.expected new file mode 100644 index 00000000000..73ce6ba748c --- /dev/null +++ b/python/ql/test/library-tests/web/client/requests/ClientHttpRequests.expected @@ -0,0 +1,2 @@ +| test.py:3:1:3:27 | ControlFlowNode for Attribute() | test.py:3:14:3:26 | ControlFlowNode for Str | GET | +| test.py:4:1:4:28 | ControlFlowNode for Attribute() | test.py:4:15:4:27 | ControlFlowNode for Str | POST | diff --git a/python/ql/test/library-tests/web/client/requests/ClientHttpRequests.ql b/python/ql/test/library-tests/web/client/requests/ClientHttpRequests.ql new file mode 100644 index 00000000000..cbeed6c2e4b --- /dev/null +++ b/python/ql/test/library-tests/web/client/requests/ClientHttpRequests.ql @@ -0,0 +1,11 @@ +import python + +import semmle.python.web.Http +import semmle.python.web.ClientHttpRequest + +from Client::HttpRequest req, string method +where + if exists(req.getMethodUpper()) + then method = req.getMethodUpper() + else method = "" +select req, req.getAUrlPart(), method diff --git a/python/ql/test/library-tests/web/client/requests/options b/python/ql/test/library-tests/web/client/requests/options new file mode 100644 index 00000000000..f1858a6caf9 --- /dev/null +++ b/python/ql/test/library-tests/web/client/requests/options @@ -0,0 +1 @@ +semmle-extractor-options: -p ../../../../query-tests/Security/lib/ --max-import-depth=1 diff --git a/python/ql/test/library-tests/web/client/requests/test.py b/python/ql/test/library-tests/web/client/requests/test.py new file mode 100644 index 00000000000..1cb5e47e166 --- /dev/null +++ b/python/ql/test/library-tests/web/client/requests/test.py @@ -0,0 +1,4 @@ +import requests + +requests.get('example.com') +requests.post('example.com') diff --git a/python/ql/test/library-tests/web/client/six/ClientHttpRequests.expected b/python/ql/test/library-tests/web/client/six/ClientHttpRequests.expected new file mode 100644 index 00000000000..d2766eeeb59 --- /dev/null +++ b/python/ql/test/library-tests/web/client/six/ClientHttpRequests.expected @@ -0,0 +1,10 @@ +| test.py:6:5:6:32 | ControlFlowNode for Attribute() | test.py:5:27:5:39 | ControlFlowNode for Str | GET | +| test.py:6:5:6:32 | ControlFlowNode for Attribute() | test.py:6:25:6:31 | ControlFlowNode for Str | GET | +| test.py:15:5:15:33 | ControlFlowNode for Attribute() | test.py:10:28:10:40 | ControlFlowNode for Str | POST | +| test.py:15:5:15:33 | ControlFlowNode for Attribute() | test.py:15:26:15:32 | ControlFlowNode for Str | POST | +| test.py:20:5:20:33 | ControlFlowNode for Attribute() | test.py:19:27:19:39 | ControlFlowNode for Str | | +| test.py:20:5:20:33 | ControlFlowNode for Attribute() | test.py:20:26:20:32 | ControlFlowNode for Str | | +| test.py:30:5:30:32 | ControlFlowNode for Attribute() | test.py:28:27:28:30 | ControlFlowNode for fake | GET | +| test.py:30:5:30:32 | ControlFlowNode for Attribute() | test.py:30:25:30:31 | ControlFlowNode for Str | GET | +| test.py:37:5:37:29 | ControlFlowNode for req_meth() | test.py:35:27:35:39 | ControlFlowNode for Str | HEAD | +| test.py:37:5:37:29 | ControlFlowNode for req_meth() | test.py:37:22:37:28 | ControlFlowNode for Str | HEAD | diff --git a/python/ql/test/library-tests/web/client/six/ClientHttpRequests.ql b/python/ql/test/library-tests/web/client/six/ClientHttpRequests.ql new file mode 100644 index 00000000000..cbeed6c2e4b --- /dev/null +++ b/python/ql/test/library-tests/web/client/six/ClientHttpRequests.ql @@ -0,0 +1,11 @@ +import python + +import semmle.python.web.Http +import semmle.python.web.ClientHttpRequest + +from Client::HttpRequest req, string method +where + if exists(req.getMethodUpper()) + then method = req.getMethodUpper() + else method = "" +select req, req.getAUrlPart(), method diff --git a/python/ql/test/library-tests/web/client/six/options b/python/ql/test/library-tests/web/client/six/options new file mode 100644 index 00000000000..1f95e64d742 --- /dev/null +++ b/python/ql/test/library-tests/web/client/six/options @@ -0,0 +1,2 @@ +semmle-extractor-options: --max-import-depth=2 +optimize: true diff --git a/python/ql/test/library-tests/web/client/six/test.py b/python/ql/test/library-tests/web/client/six/test.py new file mode 100644 index 00000000000..cdd79021d87 --- /dev/null +++ b/python/ql/test/library-tests/web/client/six/test.py @@ -0,0 +1,37 @@ +from six.moves.http_client import HTTPConnection, HTTPSConnection + + +def basic(): + conn = HTTPConnection('example.com') + conn.request('GET', '/path') + + +def indirect_caller(): + conn = HTTPSConnection('example.com') + indirect_callee(conn) + + +def indirect_callee(conn): + conn.request('POST', '/path') + + +def method_not_known(method): + conn = HTTPConnection('example.com') + conn.request(method, '/path') + + +def sneaky_setting_host(): + # We don't handle that the host is overwritten directly. + # A contrived example; you're not supposed to do this, but you certainly can. + fake = 'fakehost.com' + real = 'realhost.com' + conn = HTTPConnection(fake) + conn.host = real + conn.request('GET', '/path') + + +def tricky_not_attribute_node(): + # A contrived example; you're not supposed to do this, but you certainly can. + conn = HTTPConnection('example.com') + req_meth = conn.request + req_meth('HEAD', '/path') diff --git a/python/ql/test/library-tests/web/client/stdlib/ClientHttpRequests.expected b/python/ql/test/library-tests/web/client/stdlib/ClientHttpRequests.expected new file mode 100644 index 00000000000..a514bbea41b --- /dev/null +++ b/python/ql/test/library-tests/web/client/stdlib/ClientHttpRequests.expected @@ -0,0 +1,10 @@ +| test.py:13:5:13:32 | ControlFlowNode for Attribute() | test.py:12:27:12:39 | ControlFlowNode for Str | GET | +| test.py:13:5:13:32 | ControlFlowNode for Attribute() | test.py:13:25:13:31 | ControlFlowNode for Str | GET | +| test.py:22:5:22:33 | ControlFlowNode for Attribute() | test.py:17:28:17:40 | ControlFlowNode for Str | POST | +| test.py:22:5:22:33 | ControlFlowNode for Attribute() | test.py:22:26:22:32 | ControlFlowNode for Str | POST | +| test.py:27:5:27:33 | ControlFlowNode for Attribute() | test.py:26:27:26:39 | ControlFlowNode for Str | | +| test.py:27:5:27:33 | ControlFlowNode for Attribute() | test.py:27:26:27:32 | ControlFlowNode for Str | | +| test.py:37:5:37:32 | ControlFlowNode for Attribute() | test.py:35:27:35:30 | ControlFlowNode for fake | GET | +| test.py:37:5:37:32 | ControlFlowNode for Attribute() | test.py:37:25:37:31 | ControlFlowNode for Str | GET | +| test.py:44:5:44:29 | ControlFlowNode for req_meth() | test.py:42:27:42:39 | ControlFlowNode for Str | HEAD | +| test.py:44:5:44:29 | ControlFlowNode for req_meth() | test.py:44:22:44:28 | ControlFlowNode for Str | HEAD | diff --git a/python/ql/test/library-tests/web/client/stdlib/ClientHttpRequests.ql b/python/ql/test/library-tests/web/client/stdlib/ClientHttpRequests.ql new file mode 100644 index 00000000000..cbeed6c2e4b --- /dev/null +++ b/python/ql/test/library-tests/web/client/stdlib/ClientHttpRequests.ql @@ -0,0 +1,11 @@ +import python + +import semmle.python.web.Http +import semmle.python.web.ClientHttpRequest + +from Client::HttpRequest req, string method +where + if exists(req.getMethodUpper()) + then method = req.getMethodUpper() + else method = "" +select req, req.getAUrlPart(), method diff --git a/python/ql/test/library-tests/web/client/stdlib/options b/python/ql/test/library-tests/web/client/stdlib/options new file mode 100644 index 00000000000..eb214fc2931 --- /dev/null +++ b/python/ql/test/library-tests/web/client/stdlib/options @@ -0,0 +1 @@ +semmle-extractor-options: --max-import-depth=1 diff --git a/python/ql/test/library-tests/web/client/stdlib/test.py b/python/ql/test/library-tests/web/client/stdlib/test.py new file mode 100644 index 00000000000..179f9b30858 --- /dev/null +++ b/python/ql/test/library-tests/web/client/stdlib/test.py @@ -0,0 +1,44 @@ +import sys +PY2 = sys.version_info[0] == 2 +PY3 = sys.version_info[0] == 3 + +if PY2: + from httplib import HTTPConnection, HTTPSConnection +if PY3: + from http.client import HTTPConnection, HTTPSConnection + + +def basic(): + conn = HTTPConnection('example.com') + conn.request('GET', '/path') + + +def indirect_caller(): + conn = HTTPSConnection('example.com') + indirect_callee(conn) + + +def indirect_callee(conn): + conn.request('POST', '/path') + + +def method_not_known(method): + conn = HTTPConnection('example.com') + conn.request(method, '/path') + + +def sneaky_setting_host(): + # We don't handle that the host is overwritten directly. + # A contrived example; you're not supposed to do this, but you certainly can. + fake = 'fakehost.com' + real = 'realhost.com' + conn = HTTPConnection(fake) + conn.host = real + conn.request('GET', '/path') + + +def tricky_not_attribute_node(): + # A contrived example; you're not supposed to do this, but you certainly can. + conn = HTTPConnection('example.com') + req_meth = conn.request + req_meth('HEAD', '/path')