diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f22bc016c19..649af08b996 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -46,6 +46,12 @@ Follow the steps below to help other users understand what your query does, and Query help files explain the purpose of your query to other users. Write your query help in a `.qhelp` file and save it in the same directory as your new query. For more information on writing query help, see the [Query help style guide](https://github.com/Semmle/ql/blob/master/docs/query-help-style-guide.md). +7. **Maintain backwards compatibility** + +The standard CodeQL libraries must evolve in a backwards compatible manner. If any backwards incompatible changes need to be made, the existing API must first be marked as deprecated. This is done by adding a `deprecated` annotation along with a QLDoc reference to the replacement API. Only after at least one full release cycle has elapsed may the old API be removed. + +In addition to contributions to our standard queries and libraries, we also welcome contributions of a more experimental nature, which do not need to fulfill all the requirements listed above. See the guidelines for [experimental queries and libraries](docs/experimental.md) for details. + ## Using your personal data If you contribute to this project, we will record your name and email diff --git a/change-notes/1.24/analysis-java.md b/change-notes/1.24/analysis-java.md index 0598b813415..36210c0457e 100644 --- a/change-notes/1.24/analysis-java.md +++ b/change-notes/1.24/analysis-java.md @@ -5,6 +5,7 @@ The following changes in version 1.24 affect Java analysis in all applications. ## General improvements * Alert suppression can now be done with single-line block comments (`/* ... */`) as well as line comments (`// ...`). +* A `Customizations.qll` file has been added to allow customizations of the standard library that apply to all queries. ## New queries diff --git a/change-notes/1.24/analysis-javascript.md b/change-notes/1.24/analysis-javascript.md index 7525e7c3f04..989750a6468 100644 --- a/change-notes/1.24/analysis-javascript.md +++ b/change-notes/1.24/analysis-javascript.md @@ -19,6 +19,8 @@ - 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 flow summaries has been more clearly marked as being experimental and moved to the new `experimental` folder. + * Support for the following frameworks and libraries has been improved: - [Electron](https://electronjs.org/) - [Handlebars](https://www.npmjs.com/package/handlebars) @@ -32,6 +34,7 @@ - [http2](https://nodejs.org/api/http2.html) - [lazy-cache](https://www.npmjs.com/package/lazy-cache) - [react](https://www.npmjs.com/package/react) + - [request](https://www.npmjs.com/package/request) - [send](https://www.npmjs.com/package/send) - [typeahead.js](https://www.npmjs.com/package/typeahead.js) - [ws](https://github.com/websockets/ws) @@ -43,8 +46,11 @@ | Cross-site scripting through exception (`js/xss-through-exception`) | security, external/cwe/cwe-079, external/cwe/cwe-116 | Highlights potential XSS vulnerabilities where an exception is written to the DOM. Results are not shown on LGTM by default. | | Regular expression always matches (`js/regex/always-matches`) | correctness, regular-expressions | Highlights regular expression checks that trivially succeed by matching an empty substring. Results are shown on LGTM by default. | | Missing await (`js/missing-await`) | correctness | Highlights expressions that operate directly on a promise object in a nonsensical way, instead of awaiting its result. Results are shown on LGTM by default. | -| Prototype pollution in utility function (`js/prototype-pollution-utility`) | security, external/cwe/cwe-400, external/cwe/cwe-471 | Highlights recursive copying operations that are susceptible to prototype pollution. Results are shown on LGTM by default. | +| Polynomial regular expression used on uncontrolled data (`js/polynomial-redos`) | security, external/cwe/cwe-730, external/cwe/cwe-400 | Highlights expensive regular expressions that may be used on malicious input. Results are shown on LGTM by default. | +| Prototype pollution in utility function (`js/prototype-pollution-utility`) | security, external/cwe/cwe-400, external/cwe/cwe-471 | Highlights recursive assignment operations that are susceptible to prototype pollution. Results are shown on LGTM by default. | | Unsafe jQuery plugin (`js/unsafe-jquery-plugin`) | Highlights potential XSS vulnerabilities in unsafely designed jQuery plugins. Results are shown on LGTM by default. | +| Unnecessary use of `cat` process (`js/unnecessary-use-of-cat`) | correctness, security, maintainability | Highlights command executions of `cat` where the fs API should be used instead. Results are shown on LGTM by default. | + ## Changes to existing queries @@ -57,8 +63,10 @@ | Expression has no effect (`js/useless-expression`) | Fewer false positive results | The query now recognizes block-level flow type annotations and ignores the first statement of a try block. | | Use of call stack introspection in strict mode (`js/strict-mode-call-stack-introspection`) | Fewer false positive results | The query no longer flags expression statements. | | Missing CSRF middleware (`js/missing-token-validation`) | Fewer false positive results | The query reports fewer duplicates and only flags handlers that explicitly access cookie data. | -| Uncontrolled data used in path expression (`js/path-injection`) | More results | This query now recognizes additional ways dangerous paths can be constructed. | +| Uncontrolled data used in path expression (`js/path-injection`) | More results | This query now recognizes additional ways dangerous paths can be constructed and used. | | Uncontrolled command line (`js/command-line-injection`) | More results | This query now recognizes additional ways of constructing arguments to `cmd.exe` and `/bin/sh`. | +| Syntax error (`js/syntax-error`) | Lower severity | This results of this query are now displayed with lower severity. | +| Use of password hash with insufficient computational effort (`js/insufficient-password-hash`) | Fewer false positive results | This query now recognizes additional cases that do not require secure hashing. | ## Changes to libraries diff --git a/cpp/ql/src/Likely Bugs/Arithmetic/UnsignedGEZero.qll b/cpp/ql/src/Likely Bugs/Arithmetic/UnsignedGEZero.qll index 4ca68fb27c4..e0cc92b6ce8 100644 --- a/cpp/ql/src/Likely Bugs/Arithmetic/UnsignedGEZero.qll +++ b/cpp/ql/src/Likely Bugs/Arithmetic/UnsignedGEZero.qll @@ -15,24 +15,33 @@ class ConstantZero extends Expr { } } +/** + * Holds if `candidate` is an expression such that if it's unsigned then we + * want an alert at `ge`. + */ +private predicate lookForUnsignedAt(GEExpr ge, Expr candidate) { + // Base case: `candidate >= 0` + ge.getRightOperand() instanceof ConstantZero and + candidate = ge.getLeftOperand().getFullyConverted() and + // left operand was a signed or unsigned IntegralType before conversions + // (not a pointer, checking a pointer >= 0 is an entirely different mistake) + // (not an enum, as the fully converted type of an enum is compiler dependent + // so checking an enum >= 0 is always reasonable) + ge.getLeftOperand().getUnderlyingType() instanceof IntegralType + or + // Recursive case: `...(largerType)candidate >= 0` + exists(Conversion conversion | + lookForUnsignedAt(ge, conversion) and + candidate = conversion.getExpr() and + conversion.getType().getSize() > candidate.getType().getSize() + ) +} + class UnsignedGEZero extends GEExpr { UnsignedGEZero() { - this.getRightOperand() instanceof ConstantZero and - // left operand was a signed or unsigned IntegralType before conversions - // (not a pointer, checking a pointer >= 0 is an entirely different mistake) - // (not an enum, as the fully converted type of an enum is compiler dependent - // so checking an enum >= 0 is always reasonable) - getLeftOperand().getUnderlyingType() instanceof IntegralType and exists(Expr ue | - // ue is some conversion of the left operand - ue = getLeftOperand().getConversion*() and - // ue is unsigned - ue.getUnderlyingType().(IntegralType).isUnsigned() and - // ue may be converted to zero or more strictly larger possibly signed types - // before it is fully converted - forall(Expr following | following = ue.getConversion+() | - following.getType().getSize() > ue.getType().getSize() - ) + lookForUnsignedAt(this, ue) and + ue.getUnderlyingType().(IntegralType).isUnsigned() ) } } diff --git a/cpp/ql/src/experimental/README.md b/cpp/ql/src/experimental/README.md new file mode 100644 index 00000000000..fc07363b24f --- /dev/null +++ b/cpp/ql/src/experimental/README.md @@ -0,0 +1 @@ +This directory contains [experimental](../../../../docs/experimental.md) CodeQL queries and libraries. diff --git a/cpp/ql/src/semmle/code/cpp/Field.qll b/cpp/ql/src/semmle/code/cpp/Field.qll index de52fd79788..eb864a16063 100644 --- a/cpp/ql/src/semmle/code/cpp/Field.qll +++ b/cpp/ql/src/semmle/code/cpp/Field.qll @@ -19,6 +19,8 @@ import semmle.code.cpp.exprs.Access class Field extends MemberVariable { Field() { fieldoffsets(underlyingElement(this), _, _) } + override string getCanonicalQLClass() { result = "Field" } + /** * Gets the offset of this field in bytes from the start of its declaring * type (on the machine where facts were extracted). @@ -84,6 +86,8 @@ class Field extends MemberVariable { class BitField extends Field { BitField() { bitfield(underlyingElement(this), _, _) } + override string getCanonicalQLClass() { result = "BitField" } + /** * Gets the size of this bitfield in bits (on the machine where facts * were extracted). diff --git a/cpp/ql/src/semmle/code/cpp/Variable.qll b/cpp/ql/src/semmle/code/cpp/Variable.qll index cf85f1f9fd2..8e4a5d73664 100644 --- a/cpp/ql/src/semmle/code/cpp/Variable.qll +++ b/cpp/ql/src/semmle/code/cpp/Variable.qll @@ -28,6 +28,8 @@ private import semmle.code.cpp.internal.ResolveClass * can have multiple declarations. */ class Variable extends Declaration, @variable { + override string getCanonicalQLClass() { result = "Variable" } + /** Gets the initializer of this variable, if any. */ Initializer getInitializer() { result.getDeclaration() = this } @@ -351,6 +353,8 @@ class StackVariable extends LocalScopeVariable { * A local variable can be declared by a `DeclStmt` or a `ConditionDeclExpr`. */ class LocalVariable extends LocalScopeVariable, @localvariable { + override string getCanonicalQLClass() { result = "LocalVariable" } + override string getName() { localvariables(underlyingElement(this), _, result) } override Type getType() { localvariables(underlyingElement(this), unresolveElement(result), _) } @@ -396,6 +400,8 @@ class NamespaceVariable extends GlobalOrNamespaceVariable { NamespaceVariable() { exists(Namespace n | namespacembrs(unresolveElement(n), underlyingElement(this))) } + + override string getCanonicalQLClass() { result = "NamespaceVariable" } } /** @@ -415,6 +421,8 @@ class NamespaceVariable extends GlobalOrNamespaceVariable { */ class GlobalVariable extends GlobalOrNamespaceVariable { GlobalVariable() { not this instanceof NamespaceVariable } + + override string getCanonicalQLClass() { result = "GlobalVariable" } } /** @@ -434,6 +442,8 @@ class GlobalVariable extends GlobalOrNamespaceVariable { class MemberVariable extends Variable, @membervariable { MemberVariable() { this.isMember() } + override string getCanonicalQLClass() { result = "MemberVariable" } + /** Holds if this member is private. */ predicate isPrivate() { this.hasSpecifier("private") } 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 5eb98aefe4e..e3f1da355ef 100644 --- a/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl.qll +++ b/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl.qll @@ -1307,15 +1307,20 @@ private predicate localFlowExit(Node node, Configuration config) { */ pragma[nomagic] private predicate localFlowStepPlus( - Node node1, Node node2, boolean preservesValue, Configuration config, LocalCallContext cc + Node node1, Node node2, boolean preservesValue, DataFlowType t, Configuration config, + LocalCallContext cc ) { not isUnreachableInCall(node2, cc.(LocalCallContextSpecificCall).getCall()) and ( localFlowEntry(node1, config) and ( - localFlowStep(node1, node2, config) and preservesValue = true + localFlowStep(node1, node2, config) and + preservesValue = true and + t = getErasedNodeTypeBound(node1) or - additionalLocalFlowStep(node1, node2, config) and preservesValue = false + additionalLocalFlowStep(node1, node2, config) and + preservesValue = false and + t = getErasedNodeTypeBound(node2) ) and node1 != node2 and cc.relevantFor(node1.getEnclosingCallable()) and @@ -1323,17 +1328,18 @@ private predicate localFlowStepPlus( nodeCand(TNormalNode(node2), unbind(config)) or exists(Node mid | - localFlowStepPlus(node1, mid, preservesValue, config, cc) and + localFlowStepPlus(node1, mid, preservesValue, t, config, cc) and localFlowStep(mid, node2, config) and not mid instanceof CastNode and nodeCand(TNormalNode(node2), unbind(config)) ) or exists(Node mid | - localFlowStepPlus(node1, mid, _, config, cc) and + localFlowStepPlus(node1, mid, _, _, config, cc) and additionalLocalFlowStep(mid, node2, config) and not mid instanceof CastNode and preservesValue = false and + t = getErasedNodeTypeBound(node2) and nodeCand(TNormalNode(node2), unbind(config)) ) ) @@ -1345,17 +1351,18 @@ private predicate localFlowStepPlus( */ pragma[nomagic] private predicate localFlowBigStep( - Node node1, Node node2, boolean preservesValue, Configuration config, LocalCallContext callContext + Node node1, Node node2, boolean preservesValue, DataFlowType t, Configuration config, + LocalCallContext callContext ) { - localFlowStepPlus(node1, node2, preservesValue, config, callContext) and + localFlowStepPlus(node1, node2, preservesValue, t, config, callContext) and localFlowExit(node2, config) } pragma[nomagic] private predicate localFlowBigStepExt( - NodeExt node1, NodeExt node2, boolean preservesValue, Configuration config + NodeExt node1, NodeExt node2, boolean preservesValue, AccessPathFrontNil apf, Configuration config ) { - localFlowBigStep(node1.getNode(), node2.getNode(), preservesValue, config, _) + localFlowBigStep(node1.getNode(), node2.getNode(), preservesValue, apf.getType(), config, _) } private newtype TAccessPathFront = @@ -1395,46 +1402,24 @@ private predicate flowCandFwd( else any() } -/** - * A node that requires an empty access path and should have its tracked type - * (re-)computed. This is either a source or a node reached through an - * additional step. - */ -private class AccessPathFrontNilNode extends NormalNodeExt { - AccessPathFrontNilNode() { - nodeCand(this, _) and - ( - any(Configuration c).isSource(this.getNode()) - or - localFlowBigStepExt(_, this, false, _) - or - additionalJumpStepExt(_, this, _) - ) - } - - /** Gets the `nil` path front for this node. */ - AccessPathFrontNil getApf() { result = TFrontNil(this.getErasedNodeTypeBound()) } -} - private predicate flowCandFwd0( NodeExt node, boolean fromArg, AccessPathFront apf, Configuration config ) { nodeCand2(node, _, false, config) and config.isSource(node.getNode()) and fromArg = false and - apf = node.(AccessPathFrontNilNode).getApf() + apf = TFrontNil(node.getErasedNodeTypeBound()) or nodeCand(node, unbind(config)) and ( exists(NodeExt mid | flowCandFwd(mid, fromArg, apf, config) and - localFlowBigStepExt(mid, node, true, config) + localFlowBigStepExt(mid, node, true, _, config) ) or exists(NodeExt mid, AccessPathFrontNil nil | flowCandFwd(mid, fromArg, nil, config) and - localFlowBigStepExt(mid, node, false, config) and - apf = node.(AccessPathFrontNilNode).getApf() + localFlowBigStepExt(mid, node, false, apf, config) ) or exists(NodeExt mid | @@ -1447,7 +1432,7 @@ private predicate flowCandFwd0( flowCandFwd(mid, _, nil, config) and additionalJumpStepExt(mid, node, config) and fromArg = false and - apf = node.(AccessPathFrontNilNode).getApf() + apf = TFrontNil(node.getErasedNodeTypeBound()) ) or exists(NodeExt mid, boolean allowsFieldFlow | @@ -1589,13 +1574,13 @@ private predicate flowCand0( apf instanceof AccessPathFrontNil or exists(NodeExt mid | - localFlowBigStepExt(node, mid, true, config) and + localFlowBigStepExt(node, mid, true, _, config) and flowCand(mid, toReturn, apf, config) ) or exists(NodeExt mid, AccessPathFrontNil nil | flowCandFwd(node, _, apf, config) and - localFlowBigStepExt(node, mid, false, config) and + localFlowBigStepExt(node, mid, false, _, config) and flowCand(mid, toReturn, nil, config) and apf instanceof AccessPathFrontNil ) @@ -1810,18 +1795,6 @@ private predicate popWithFront(AccessPath ap0, Content f, AccessPathFront apf, A /** Gets the access path obtained by pushing `f` onto `ap`. */ private AccessPath push(Content f, AccessPath ap) { ap = pop(f, result) } -/** - * A node that requires an empty access path and should have its tracked type - * (re-)computed. This is either a source or a node reached through an - * additional step. - */ -private class AccessPathNilNode extends NormalNodeExt { - AccessPathNilNode() { flowCand(this.(AccessPathFrontNilNode), _, _, _) } - - /** Gets the `nil` path for this node. */ - AccessPathNil getAp() { result = TNil(this.getErasedNodeTypeBound()) } -} - /** * Holds if data can flow from a source to `node` with the given `ap`. */ @@ -1838,20 +1811,19 @@ private predicate flowFwd0( flowCand(node, _, _, config) and config.isSource(node.getNode()) and fromArg = false and - ap = node.(AccessPathNilNode).getAp() and + ap = TNil(node.getErasedNodeTypeBound()) and apf = ap.(AccessPathNil).getFront() or flowCand(node, _, _, unbind(config)) and ( exists(NodeExt mid | flowFwd(mid, fromArg, apf, ap, config) and - localFlowBigStepExt(mid, node, true, config) + localFlowBigStepExt(mid, node, true, _, config) ) or exists(NodeExt mid, AccessPathNil nil | flowFwd(mid, fromArg, _, nil, config) and - localFlowBigStepExt(mid, node, false, config) and - ap = node.(AccessPathNilNode).getAp() and + localFlowBigStepExt(mid, node, false, apf, config) and apf = ap.(AccessPathNil).getFront() ) or @@ -1865,7 +1837,7 @@ private predicate flowFwd0( flowFwd(mid, _, _, nil, config) and additionalJumpStepExt(mid, node, config) and fromArg = false and - ap = node.(AccessPathNilNode).getAp() and + ap = TNil(node.getErasedNodeTypeBound()) and apf = ap.(AccessPathNil).getFront() ) or @@ -1982,13 +1954,13 @@ private predicate flow0(NodeExt node, boolean toReturn, AccessPath ap, Configura ap instanceof AccessPathNil or exists(NodeExt mid | - localFlowBigStepExt(node, mid, true, config) and + localFlowBigStepExt(node, mid, true, _, config) and flow(mid, toReturn, ap, config) ) or exists(NodeExt mid, AccessPathNil nil | flowFwd(node, _, _, ap, config) and - localFlowBigStepExt(node, mid, false, config) and + localFlowBigStepExt(node, mid, false, _, config) and flow(mid, toReturn, nil, config) and ap instanceof AccessPathNil ) @@ -2164,7 +2136,7 @@ private newtype TPathNode = config.isSource(node) and cc instanceof CallContextAny and sc instanceof SummaryCtxNone and - ap = any(AccessPathNilNode nil | nil.getNode() = node).getAp() + ap = TNil(getErasedNodeTypeBound(node)) or // ... or a step from an existing PathNode to another node. exists(PathNodeMid mid | @@ -2357,12 +2329,11 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt pathIntoLocalStep(mid, midnode, cc, enclosing, sc, ap0, conf) and localCC = getLocalCallContext(cc, enclosing) | - localFlowBigStep(midnode, node, true, conf, localCC) and + localFlowBigStep(midnode, node, true, _, conf, localCC) and ap = ap0 or - localFlowBigStep(midnode, node, false, conf, localCC) and - ap0 instanceof AccessPathNil and - ap = any(AccessPathNilNode nil | nil.getNode() = node).getAp() + localFlowBigStep(midnode, node, false, ap.(AccessPathNil).getType(), conf, localCC) and + ap0 instanceof AccessPathNil ) or jumpStep(mid.getNode(), node, mid.getConfiguration()) and @@ -2374,7 +2345,7 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt cc instanceof CallContextAny and sc instanceof SummaryCtxNone and mid.getAp() instanceof AccessPathNil and - ap = any(AccessPathNilNode nil | nil.getNode() = node).getAp() + ap = TNil(getErasedNodeTypeBound(node)) or exists(Content f, AccessPath ap0 | pathReadStep(mid, node, ap0, f, cc) and ap = pop(f, ap0)) and sc = mid.getSummaryCtx() @@ -2397,7 +2368,7 @@ private predicate pathIntoLocalStep( midnode = mid.getNode() and cc = mid.getCallContext() and conf = mid.getConfiguration() and - localFlowBigStep(midnode, _, _, conf, _) and + localFlowBigStep(midnode, _, _, _, conf, _) and enclosing = midnode.getEnclosingCallable() and sc = mid.getSummaryCtx() and ap0 = mid.getAp() @@ -2949,7 +2920,7 @@ private module FlowExploration { config = mid.getConfiguration() } - pragma[noinline] + pragma[nomagic] private predicate partialPathOutOfCallable1( PartialPathNodePriv mid, DataFlowCall call, ReturnKindExt kind, CallContext cc, PartialAccessPath ap, Configuration config 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 5eb98aefe4e..e3f1da355ef 100644 --- a/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl2.qll +++ b/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl2.qll @@ -1307,15 +1307,20 @@ private predicate localFlowExit(Node node, Configuration config) { */ pragma[nomagic] private predicate localFlowStepPlus( - Node node1, Node node2, boolean preservesValue, Configuration config, LocalCallContext cc + Node node1, Node node2, boolean preservesValue, DataFlowType t, Configuration config, + LocalCallContext cc ) { not isUnreachableInCall(node2, cc.(LocalCallContextSpecificCall).getCall()) and ( localFlowEntry(node1, config) and ( - localFlowStep(node1, node2, config) and preservesValue = true + localFlowStep(node1, node2, config) and + preservesValue = true and + t = getErasedNodeTypeBound(node1) or - additionalLocalFlowStep(node1, node2, config) and preservesValue = false + additionalLocalFlowStep(node1, node2, config) and + preservesValue = false and + t = getErasedNodeTypeBound(node2) ) and node1 != node2 and cc.relevantFor(node1.getEnclosingCallable()) and @@ -1323,17 +1328,18 @@ private predicate localFlowStepPlus( nodeCand(TNormalNode(node2), unbind(config)) or exists(Node mid | - localFlowStepPlus(node1, mid, preservesValue, config, cc) and + localFlowStepPlus(node1, mid, preservesValue, t, config, cc) and localFlowStep(mid, node2, config) and not mid instanceof CastNode and nodeCand(TNormalNode(node2), unbind(config)) ) or exists(Node mid | - localFlowStepPlus(node1, mid, _, config, cc) and + localFlowStepPlus(node1, mid, _, _, config, cc) and additionalLocalFlowStep(mid, node2, config) and not mid instanceof CastNode and preservesValue = false and + t = getErasedNodeTypeBound(node2) and nodeCand(TNormalNode(node2), unbind(config)) ) ) @@ -1345,17 +1351,18 @@ private predicate localFlowStepPlus( */ pragma[nomagic] private predicate localFlowBigStep( - Node node1, Node node2, boolean preservesValue, Configuration config, LocalCallContext callContext + Node node1, Node node2, boolean preservesValue, DataFlowType t, Configuration config, + LocalCallContext callContext ) { - localFlowStepPlus(node1, node2, preservesValue, config, callContext) and + localFlowStepPlus(node1, node2, preservesValue, t, config, callContext) and localFlowExit(node2, config) } pragma[nomagic] private predicate localFlowBigStepExt( - NodeExt node1, NodeExt node2, boolean preservesValue, Configuration config + NodeExt node1, NodeExt node2, boolean preservesValue, AccessPathFrontNil apf, Configuration config ) { - localFlowBigStep(node1.getNode(), node2.getNode(), preservesValue, config, _) + localFlowBigStep(node1.getNode(), node2.getNode(), preservesValue, apf.getType(), config, _) } private newtype TAccessPathFront = @@ -1395,46 +1402,24 @@ private predicate flowCandFwd( else any() } -/** - * A node that requires an empty access path and should have its tracked type - * (re-)computed. This is either a source or a node reached through an - * additional step. - */ -private class AccessPathFrontNilNode extends NormalNodeExt { - AccessPathFrontNilNode() { - nodeCand(this, _) and - ( - any(Configuration c).isSource(this.getNode()) - or - localFlowBigStepExt(_, this, false, _) - or - additionalJumpStepExt(_, this, _) - ) - } - - /** Gets the `nil` path front for this node. */ - AccessPathFrontNil getApf() { result = TFrontNil(this.getErasedNodeTypeBound()) } -} - private predicate flowCandFwd0( NodeExt node, boolean fromArg, AccessPathFront apf, Configuration config ) { nodeCand2(node, _, false, config) and config.isSource(node.getNode()) and fromArg = false and - apf = node.(AccessPathFrontNilNode).getApf() + apf = TFrontNil(node.getErasedNodeTypeBound()) or nodeCand(node, unbind(config)) and ( exists(NodeExt mid | flowCandFwd(mid, fromArg, apf, config) and - localFlowBigStepExt(mid, node, true, config) + localFlowBigStepExt(mid, node, true, _, config) ) or exists(NodeExt mid, AccessPathFrontNil nil | flowCandFwd(mid, fromArg, nil, config) and - localFlowBigStepExt(mid, node, false, config) and - apf = node.(AccessPathFrontNilNode).getApf() + localFlowBigStepExt(mid, node, false, apf, config) ) or exists(NodeExt mid | @@ -1447,7 +1432,7 @@ private predicate flowCandFwd0( flowCandFwd(mid, _, nil, config) and additionalJumpStepExt(mid, node, config) and fromArg = false and - apf = node.(AccessPathFrontNilNode).getApf() + apf = TFrontNil(node.getErasedNodeTypeBound()) ) or exists(NodeExt mid, boolean allowsFieldFlow | @@ -1589,13 +1574,13 @@ private predicate flowCand0( apf instanceof AccessPathFrontNil or exists(NodeExt mid | - localFlowBigStepExt(node, mid, true, config) and + localFlowBigStepExt(node, mid, true, _, config) and flowCand(mid, toReturn, apf, config) ) or exists(NodeExt mid, AccessPathFrontNil nil | flowCandFwd(node, _, apf, config) and - localFlowBigStepExt(node, mid, false, config) and + localFlowBigStepExt(node, mid, false, _, config) and flowCand(mid, toReturn, nil, config) and apf instanceof AccessPathFrontNil ) @@ -1810,18 +1795,6 @@ private predicate popWithFront(AccessPath ap0, Content f, AccessPathFront apf, A /** Gets the access path obtained by pushing `f` onto `ap`. */ private AccessPath push(Content f, AccessPath ap) { ap = pop(f, result) } -/** - * A node that requires an empty access path and should have its tracked type - * (re-)computed. This is either a source or a node reached through an - * additional step. - */ -private class AccessPathNilNode extends NormalNodeExt { - AccessPathNilNode() { flowCand(this.(AccessPathFrontNilNode), _, _, _) } - - /** Gets the `nil` path for this node. */ - AccessPathNil getAp() { result = TNil(this.getErasedNodeTypeBound()) } -} - /** * Holds if data can flow from a source to `node` with the given `ap`. */ @@ -1838,20 +1811,19 @@ private predicate flowFwd0( flowCand(node, _, _, config) and config.isSource(node.getNode()) and fromArg = false and - ap = node.(AccessPathNilNode).getAp() and + ap = TNil(node.getErasedNodeTypeBound()) and apf = ap.(AccessPathNil).getFront() or flowCand(node, _, _, unbind(config)) and ( exists(NodeExt mid | flowFwd(mid, fromArg, apf, ap, config) and - localFlowBigStepExt(mid, node, true, config) + localFlowBigStepExt(mid, node, true, _, config) ) or exists(NodeExt mid, AccessPathNil nil | flowFwd(mid, fromArg, _, nil, config) and - localFlowBigStepExt(mid, node, false, config) and - ap = node.(AccessPathNilNode).getAp() and + localFlowBigStepExt(mid, node, false, apf, config) and apf = ap.(AccessPathNil).getFront() ) or @@ -1865,7 +1837,7 @@ private predicate flowFwd0( flowFwd(mid, _, _, nil, config) and additionalJumpStepExt(mid, node, config) and fromArg = false and - ap = node.(AccessPathNilNode).getAp() and + ap = TNil(node.getErasedNodeTypeBound()) and apf = ap.(AccessPathNil).getFront() ) or @@ -1982,13 +1954,13 @@ private predicate flow0(NodeExt node, boolean toReturn, AccessPath ap, Configura ap instanceof AccessPathNil or exists(NodeExt mid | - localFlowBigStepExt(node, mid, true, config) and + localFlowBigStepExt(node, mid, true, _, config) and flow(mid, toReturn, ap, config) ) or exists(NodeExt mid, AccessPathNil nil | flowFwd(node, _, _, ap, config) and - localFlowBigStepExt(node, mid, false, config) and + localFlowBigStepExt(node, mid, false, _, config) and flow(mid, toReturn, nil, config) and ap instanceof AccessPathNil ) @@ -2164,7 +2136,7 @@ private newtype TPathNode = config.isSource(node) and cc instanceof CallContextAny and sc instanceof SummaryCtxNone and - ap = any(AccessPathNilNode nil | nil.getNode() = node).getAp() + ap = TNil(getErasedNodeTypeBound(node)) or // ... or a step from an existing PathNode to another node. exists(PathNodeMid mid | @@ -2357,12 +2329,11 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt pathIntoLocalStep(mid, midnode, cc, enclosing, sc, ap0, conf) and localCC = getLocalCallContext(cc, enclosing) | - localFlowBigStep(midnode, node, true, conf, localCC) and + localFlowBigStep(midnode, node, true, _, conf, localCC) and ap = ap0 or - localFlowBigStep(midnode, node, false, conf, localCC) and - ap0 instanceof AccessPathNil and - ap = any(AccessPathNilNode nil | nil.getNode() = node).getAp() + localFlowBigStep(midnode, node, false, ap.(AccessPathNil).getType(), conf, localCC) and + ap0 instanceof AccessPathNil ) or jumpStep(mid.getNode(), node, mid.getConfiguration()) and @@ -2374,7 +2345,7 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt cc instanceof CallContextAny and sc instanceof SummaryCtxNone and mid.getAp() instanceof AccessPathNil and - ap = any(AccessPathNilNode nil | nil.getNode() = node).getAp() + ap = TNil(getErasedNodeTypeBound(node)) or exists(Content f, AccessPath ap0 | pathReadStep(mid, node, ap0, f, cc) and ap = pop(f, ap0)) and sc = mid.getSummaryCtx() @@ -2397,7 +2368,7 @@ private predicate pathIntoLocalStep( midnode = mid.getNode() and cc = mid.getCallContext() and conf = mid.getConfiguration() and - localFlowBigStep(midnode, _, _, conf, _) and + localFlowBigStep(midnode, _, _, _, conf, _) and enclosing = midnode.getEnclosingCallable() and sc = mid.getSummaryCtx() and ap0 = mid.getAp() @@ -2949,7 +2920,7 @@ private module FlowExploration { config = mid.getConfiguration() } - pragma[noinline] + pragma[nomagic] private predicate partialPathOutOfCallable1( PartialPathNodePriv mid, DataFlowCall call, ReturnKindExt kind, CallContext cc, PartialAccessPath ap, Configuration config 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 5eb98aefe4e..e3f1da355ef 100644 --- a/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl3.qll +++ b/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl3.qll @@ -1307,15 +1307,20 @@ private predicate localFlowExit(Node node, Configuration config) { */ pragma[nomagic] private predicate localFlowStepPlus( - Node node1, Node node2, boolean preservesValue, Configuration config, LocalCallContext cc + Node node1, Node node2, boolean preservesValue, DataFlowType t, Configuration config, + LocalCallContext cc ) { not isUnreachableInCall(node2, cc.(LocalCallContextSpecificCall).getCall()) and ( localFlowEntry(node1, config) and ( - localFlowStep(node1, node2, config) and preservesValue = true + localFlowStep(node1, node2, config) and + preservesValue = true and + t = getErasedNodeTypeBound(node1) or - additionalLocalFlowStep(node1, node2, config) and preservesValue = false + additionalLocalFlowStep(node1, node2, config) and + preservesValue = false and + t = getErasedNodeTypeBound(node2) ) and node1 != node2 and cc.relevantFor(node1.getEnclosingCallable()) and @@ -1323,17 +1328,18 @@ private predicate localFlowStepPlus( nodeCand(TNormalNode(node2), unbind(config)) or exists(Node mid | - localFlowStepPlus(node1, mid, preservesValue, config, cc) and + localFlowStepPlus(node1, mid, preservesValue, t, config, cc) and localFlowStep(mid, node2, config) and not mid instanceof CastNode and nodeCand(TNormalNode(node2), unbind(config)) ) or exists(Node mid | - localFlowStepPlus(node1, mid, _, config, cc) and + localFlowStepPlus(node1, mid, _, _, config, cc) and additionalLocalFlowStep(mid, node2, config) and not mid instanceof CastNode and preservesValue = false and + t = getErasedNodeTypeBound(node2) and nodeCand(TNormalNode(node2), unbind(config)) ) ) @@ -1345,17 +1351,18 @@ private predicate localFlowStepPlus( */ pragma[nomagic] private predicate localFlowBigStep( - Node node1, Node node2, boolean preservesValue, Configuration config, LocalCallContext callContext + Node node1, Node node2, boolean preservesValue, DataFlowType t, Configuration config, + LocalCallContext callContext ) { - localFlowStepPlus(node1, node2, preservesValue, config, callContext) and + localFlowStepPlus(node1, node2, preservesValue, t, config, callContext) and localFlowExit(node2, config) } pragma[nomagic] private predicate localFlowBigStepExt( - NodeExt node1, NodeExt node2, boolean preservesValue, Configuration config + NodeExt node1, NodeExt node2, boolean preservesValue, AccessPathFrontNil apf, Configuration config ) { - localFlowBigStep(node1.getNode(), node2.getNode(), preservesValue, config, _) + localFlowBigStep(node1.getNode(), node2.getNode(), preservesValue, apf.getType(), config, _) } private newtype TAccessPathFront = @@ -1395,46 +1402,24 @@ private predicate flowCandFwd( else any() } -/** - * A node that requires an empty access path and should have its tracked type - * (re-)computed. This is either a source or a node reached through an - * additional step. - */ -private class AccessPathFrontNilNode extends NormalNodeExt { - AccessPathFrontNilNode() { - nodeCand(this, _) and - ( - any(Configuration c).isSource(this.getNode()) - or - localFlowBigStepExt(_, this, false, _) - or - additionalJumpStepExt(_, this, _) - ) - } - - /** Gets the `nil` path front for this node. */ - AccessPathFrontNil getApf() { result = TFrontNil(this.getErasedNodeTypeBound()) } -} - private predicate flowCandFwd0( NodeExt node, boolean fromArg, AccessPathFront apf, Configuration config ) { nodeCand2(node, _, false, config) and config.isSource(node.getNode()) and fromArg = false and - apf = node.(AccessPathFrontNilNode).getApf() + apf = TFrontNil(node.getErasedNodeTypeBound()) or nodeCand(node, unbind(config)) and ( exists(NodeExt mid | flowCandFwd(mid, fromArg, apf, config) and - localFlowBigStepExt(mid, node, true, config) + localFlowBigStepExt(mid, node, true, _, config) ) or exists(NodeExt mid, AccessPathFrontNil nil | flowCandFwd(mid, fromArg, nil, config) and - localFlowBigStepExt(mid, node, false, config) and - apf = node.(AccessPathFrontNilNode).getApf() + localFlowBigStepExt(mid, node, false, apf, config) ) or exists(NodeExt mid | @@ -1447,7 +1432,7 @@ private predicate flowCandFwd0( flowCandFwd(mid, _, nil, config) and additionalJumpStepExt(mid, node, config) and fromArg = false and - apf = node.(AccessPathFrontNilNode).getApf() + apf = TFrontNil(node.getErasedNodeTypeBound()) ) or exists(NodeExt mid, boolean allowsFieldFlow | @@ -1589,13 +1574,13 @@ private predicate flowCand0( apf instanceof AccessPathFrontNil or exists(NodeExt mid | - localFlowBigStepExt(node, mid, true, config) and + localFlowBigStepExt(node, mid, true, _, config) and flowCand(mid, toReturn, apf, config) ) or exists(NodeExt mid, AccessPathFrontNil nil | flowCandFwd(node, _, apf, config) and - localFlowBigStepExt(node, mid, false, config) and + localFlowBigStepExt(node, mid, false, _, config) and flowCand(mid, toReturn, nil, config) and apf instanceof AccessPathFrontNil ) @@ -1810,18 +1795,6 @@ private predicate popWithFront(AccessPath ap0, Content f, AccessPathFront apf, A /** Gets the access path obtained by pushing `f` onto `ap`. */ private AccessPath push(Content f, AccessPath ap) { ap = pop(f, result) } -/** - * A node that requires an empty access path and should have its tracked type - * (re-)computed. This is either a source or a node reached through an - * additional step. - */ -private class AccessPathNilNode extends NormalNodeExt { - AccessPathNilNode() { flowCand(this.(AccessPathFrontNilNode), _, _, _) } - - /** Gets the `nil` path for this node. */ - AccessPathNil getAp() { result = TNil(this.getErasedNodeTypeBound()) } -} - /** * Holds if data can flow from a source to `node` with the given `ap`. */ @@ -1838,20 +1811,19 @@ private predicate flowFwd0( flowCand(node, _, _, config) and config.isSource(node.getNode()) and fromArg = false and - ap = node.(AccessPathNilNode).getAp() and + ap = TNil(node.getErasedNodeTypeBound()) and apf = ap.(AccessPathNil).getFront() or flowCand(node, _, _, unbind(config)) and ( exists(NodeExt mid | flowFwd(mid, fromArg, apf, ap, config) and - localFlowBigStepExt(mid, node, true, config) + localFlowBigStepExt(mid, node, true, _, config) ) or exists(NodeExt mid, AccessPathNil nil | flowFwd(mid, fromArg, _, nil, config) and - localFlowBigStepExt(mid, node, false, config) and - ap = node.(AccessPathNilNode).getAp() and + localFlowBigStepExt(mid, node, false, apf, config) and apf = ap.(AccessPathNil).getFront() ) or @@ -1865,7 +1837,7 @@ private predicate flowFwd0( flowFwd(mid, _, _, nil, config) and additionalJumpStepExt(mid, node, config) and fromArg = false and - ap = node.(AccessPathNilNode).getAp() and + ap = TNil(node.getErasedNodeTypeBound()) and apf = ap.(AccessPathNil).getFront() ) or @@ -1982,13 +1954,13 @@ private predicate flow0(NodeExt node, boolean toReturn, AccessPath ap, Configura ap instanceof AccessPathNil or exists(NodeExt mid | - localFlowBigStepExt(node, mid, true, config) and + localFlowBigStepExt(node, mid, true, _, config) and flow(mid, toReturn, ap, config) ) or exists(NodeExt mid, AccessPathNil nil | flowFwd(node, _, _, ap, config) and - localFlowBigStepExt(node, mid, false, config) and + localFlowBigStepExt(node, mid, false, _, config) and flow(mid, toReturn, nil, config) and ap instanceof AccessPathNil ) @@ -2164,7 +2136,7 @@ private newtype TPathNode = config.isSource(node) and cc instanceof CallContextAny and sc instanceof SummaryCtxNone and - ap = any(AccessPathNilNode nil | nil.getNode() = node).getAp() + ap = TNil(getErasedNodeTypeBound(node)) or // ... or a step from an existing PathNode to another node. exists(PathNodeMid mid | @@ -2357,12 +2329,11 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt pathIntoLocalStep(mid, midnode, cc, enclosing, sc, ap0, conf) and localCC = getLocalCallContext(cc, enclosing) | - localFlowBigStep(midnode, node, true, conf, localCC) and + localFlowBigStep(midnode, node, true, _, conf, localCC) and ap = ap0 or - localFlowBigStep(midnode, node, false, conf, localCC) and - ap0 instanceof AccessPathNil and - ap = any(AccessPathNilNode nil | nil.getNode() = node).getAp() + localFlowBigStep(midnode, node, false, ap.(AccessPathNil).getType(), conf, localCC) and + ap0 instanceof AccessPathNil ) or jumpStep(mid.getNode(), node, mid.getConfiguration()) and @@ -2374,7 +2345,7 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt cc instanceof CallContextAny and sc instanceof SummaryCtxNone and mid.getAp() instanceof AccessPathNil and - ap = any(AccessPathNilNode nil | nil.getNode() = node).getAp() + ap = TNil(getErasedNodeTypeBound(node)) or exists(Content f, AccessPath ap0 | pathReadStep(mid, node, ap0, f, cc) and ap = pop(f, ap0)) and sc = mid.getSummaryCtx() @@ -2397,7 +2368,7 @@ private predicate pathIntoLocalStep( midnode = mid.getNode() and cc = mid.getCallContext() and conf = mid.getConfiguration() and - localFlowBigStep(midnode, _, _, conf, _) and + localFlowBigStep(midnode, _, _, _, conf, _) and enclosing = midnode.getEnclosingCallable() and sc = mid.getSummaryCtx() and ap0 = mid.getAp() @@ -2949,7 +2920,7 @@ private module FlowExploration { config = mid.getConfiguration() } - pragma[noinline] + pragma[nomagic] private predicate partialPathOutOfCallable1( PartialPathNodePriv mid, DataFlowCall call, ReturnKindExt kind, CallContext cc, PartialAccessPath ap, Configuration config 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 5eb98aefe4e..e3f1da355ef 100644 --- a/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl4.qll +++ b/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl4.qll @@ -1307,15 +1307,20 @@ private predicate localFlowExit(Node node, Configuration config) { */ pragma[nomagic] private predicate localFlowStepPlus( - Node node1, Node node2, boolean preservesValue, Configuration config, LocalCallContext cc + Node node1, Node node2, boolean preservesValue, DataFlowType t, Configuration config, + LocalCallContext cc ) { not isUnreachableInCall(node2, cc.(LocalCallContextSpecificCall).getCall()) and ( localFlowEntry(node1, config) and ( - localFlowStep(node1, node2, config) and preservesValue = true + localFlowStep(node1, node2, config) and + preservesValue = true and + t = getErasedNodeTypeBound(node1) or - additionalLocalFlowStep(node1, node2, config) and preservesValue = false + additionalLocalFlowStep(node1, node2, config) and + preservesValue = false and + t = getErasedNodeTypeBound(node2) ) and node1 != node2 and cc.relevantFor(node1.getEnclosingCallable()) and @@ -1323,17 +1328,18 @@ private predicate localFlowStepPlus( nodeCand(TNormalNode(node2), unbind(config)) or exists(Node mid | - localFlowStepPlus(node1, mid, preservesValue, config, cc) and + localFlowStepPlus(node1, mid, preservesValue, t, config, cc) and localFlowStep(mid, node2, config) and not mid instanceof CastNode and nodeCand(TNormalNode(node2), unbind(config)) ) or exists(Node mid | - localFlowStepPlus(node1, mid, _, config, cc) and + localFlowStepPlus(node1, mid, _, _, config, cc) and additionalLocalFlowStep(mid, node2, config) and not mid instanceof CastNode and preservesValue = false and + t = getErasedNodeTypeBound(node2) and nodeCand(TNormalNode(node2), unbind(config)) ) ) @@ -1345,17 +1351,18 @@ private predicate localFlowStepPlus( */ pragma[nomagic] private predicate localFlowBigStep( - Node node1, Node node2, boolean preservesValue, Configuration config, LocalCallContext callContext + Node node1, Node node2, boolean preservesValue, DataFlowType t, Configuration config, + LocalCallContext callContext ) { - localFlowStepPlus(node1, node2, preservesValue, config, callContext) and + localFlowStepPlus(node1, node2, preservesValue, t, config, callContext) and localFlowExit(node2, config) } pragma[nomagic] private predicate localFlowBigStepExt( - NodeExt node1, NodeExt node2, boolean preservesValue, Configuration config + NodeExt node1, NodeExt node2, boolean preservesValue, AccessPathFrontNil apf, Configuration config ) { - localFlowBigStep(node1.getNode(), node2.getNode(), preservesValue, config, _) + localFlowBigStep(node1.getNode(), node2.getNode(), preservesValue, apf.getType(), config, _) } private newtype TAccessPathFront = @@ -1395,46 +1402,24 @@ private predicate flowCandFwd( else any() } -/** - * A node that requires an empty access path and should have its tracked type - * (re-)computed. This is either a source or a node reached through an - * additional step. - */ -private class AccessPathFrontNilNode extends NormalNodeExt { - AccessPathFrontNilNode() { - nodeCand(this, _) and - ( - any(Configuration c).isSource(this.getNode()) - or - localFlowBigStepExt(_, this, false, _) - or - additionalJumpStepExt(_, this, _) - ) - } - - /** Gets the `nil` path front for this node. */ - AccessPathFrontNil getApf() { result = TFrontNil(this.getErasedNodeTypeBound()) } -} - private predicate flowCandFwd0( NodeExt node, boolean fromArg, AccessPathFront apf, Configuration config ) { nodeCand2(node, _, false, config) and config.isSource(node.getNode()) and fromArg = false and - apf = node.(AccessPathFrontNilNode).getApf() + apf = TFrontNil(node.getErasedNodeTypeBound()) or nodeCand(node, unbind(config)) and ( exists(NodeExt mid | flowCandFwd(mid, fromArg, apf, config) and - localFlowBigStepExt(mid, node, true, config) + localFlowBigStepExt(mid, node, true, _, config) ) or exists(NodeExt mid, AccessPathFrontNil nil | flowCandFwd(mid, fromArg, nil, config) and - localFlowBigStepExt(mid, node, false, config) and - apf = node.(AccessPathFrontNilNode).getApf() + localFlowBigStepExt(mid, node, false, apf, config) ) or exists(NodeExt mid | @@ -1447,7 +1432,7 @@ private predicate flowCandFwd0( flowCandFwd(mid, _, nil, config) and additionalJumpStepExt(mid, node, config) and fromArg = false and - apf = node.(AccessPathFrontNilNode).getApf() + apf = TFrontNil(node.getErasedNodeTypeBound()) ) or exists(NodeExt mid, boolean allowsFieldFlow | @@ -1589,13 +1574,13 @@ private predicate flowCand0( apf instanceof AccessPathFrontNil or exists(NodeExt mid | - localFlowBigStepExt(node, mid, true, config) and + localFlowBigStepExt(node, mid, true, _, config) and flowCand(mid, toReturn, apf, config) ) or exists(NodeExt mid, AccessPathFrontNil nil | flowCandFwd(node, _, apf, config) and - localFlowBigStepExt(node, mid, false, config) and + localFlowBigStepExt(node, mid, false, _, config) and flowCand(mid, toReturn, nil, config) and apf instanceof AccessPathFrontNil ) @@ -1810,18 +1795,6 @@ private predicate popWithFront(AccessPath ap0, Content f, AccessPathFront apf, A /** Gets the access path obtained by pushing `f` onto `ap`. */ private AccessPath push(Content f, AccessPath ap) { ap = pop(f, result) } -/** - * A node that requires an empty access path and should have its tracked type - * (re-)computed. This is either a source or a node reached through an - * additional step. - */ -private class AccessPathNilNode extends NormalNodeExt { - AccessPathNilNode() { flowCand(this.(AccessPathFrontNilNode), _, _, _) } - - /** Gets the `nil` path for this node. */ - AccessPathNil getAp() { result = TNil(this.getErasedNodeTypeBound()) } -} - /** * Holds if data can flow from a source to `node` with the given `ap`. */ @@ -1838,20 +1811,19 @@ private predicate flowFwd0( flowCand(node, _, _, config) and config.isSource(node.getNode()) and fromArg = false and - ap = node.(AccessPathNilNode).getAp() and + ap = TNil(node.getErasedNodeTypeBound()) and apf = ap.(AccessPathNil).getFront() or flowCand(node, _, _, unbind(config)) and ( exists(NodeExt mid | flowFwd(mid, fromArg, apf, ap, config) and - localFlowBigStepExt(mid, node, true, config) + localFlowBigStepExt(mid, node, true, _, config) ) or exists(NodeExt mid, AccessPathNil nil | flowFwd(mid, fromArg, _, nil, config) and - localFlowBigStepExt(mid, node, false, config) and - ap = node.(AccessPathNilNode).getAp() and + localFlowBigStepExt(mid, node, false, apf, config) and apf = ap.(AccessPathNil).getFront() ) or @@ -1865,7 +1837,7 @@ private predicate flowFwd0( flowFwd(mid, _, _, nil, config) and additionalJumpStepExt(mid, node, config) and fromArg = false and - ap = node.(AccessPathNilNode).getAp() and + ap = TNil(node.getErasedNodeTypeBound()) and apf = ap.(AccessPathNil).getFront() ) or @@ -1982,13 +1954,13 @@ private predicate flow0(NodeExt node, boolean toReturn, AccessPath ap, Configura ap instanceof AccessPathNil or exists(NodeExt mid | - localFlowBigStepExt(node, mid, true, config) and + localFlowBigStepExt(node, mid, true, _, config) and flow(mid, toReturn, ap, config) ) or exists(NodeExt mid, AccessPathNil nil | flowFwd(node, _, _, ap, config) and - localFlowBigStepExt(node, mid, false, config) and + localFlowBigStepExt(node, mid, false, _, config) and flow(mid, toReturn, nil, config) and ap instanceof AccessPathNil ) @@ -2164,7 +2136,7 @@ private newtype TPathNode = config.isSource(node) and cc instanceof CallContextAny and sc instanceof SummaryCtxNone and - ap = any(AccessPathNilNode nil | nil.getNode() = node).getAp() + ap = TNil(getErasedNodeTypeBound(node)) or // ... or a step from an existing PathNode to another node. exists(PathNodeMid mid | @@ -2357,12 +2329,11 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt pathIntoLocalStep(mid, midnode, cc, enclosing, sc, ap0, conf) and localCC = getLocalCallContext(cc, enclosing) | - localFlowBigStep(midnode, node, true, conf, localCC) and + localFlowBigStep(midnode, node, true, _, conf, localCC) and ap = ap0 or - localFlowBigStep(midnode, node, false, conf, localCC) and - ap0 instanceof AccessPathNil and - ap = any(AccessPathNilNode nil | nil.getNode() = node).getAp() + localFlowBigStep(midnode, node, false, ap.(AccessPathNil).getType(), conf, localCC) and + ap0 instanceof AccessPathNil ) or jumpStep(mid.getNode(), node, mid.getConfiguration()) and @@ -2374,7 +2345,7 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt cc instanceof CallContextAny and sc instanceof SummaryCtxNone and mid.getAp() instanceof AccessPathNil and - ap = any(AccessPathNilNode nil | nil.getNode() = node).getAp() + ap = TNil(getErasedNodeTypeBound(node)) or exists(Content f, AccessPath ap0 | pathReadStep(mid, node, ap0, f, cc) and ap = pop(f, ap0)) and sc = mid.getSummaryCtx() @@ -2397,7 +2368,7 @@ private predicate pathIntoLocalStep( midnode = mid.getNode() and cc = mid.getCallContext() and conf = mid.getConfiguration() and - localFlowBigStep(midnode, _, _, conf, _) and + localFlowBigStep(midnode, _, _, _, conf, _) and enclosing = midnode.getEnclosingCallable() and sc = mid.getSummaryCtx() and ap0 = mid.getAp() @@ -2949,7 +2920,7 @@ private module FlowExploration { config = mid.getConfiguration() } - pragma[noinline] + pragma[nomagic] private predicate partialPathOutOfCallable1( PartialPathNodePriv mid, DataFlowCall call, ReturnKindExt kind, CallContext cc, PartialAccessPath ap, Configuration config 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 5eb98aefe4e..e3f1da355ef 100644 --- a/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImplLocal.qll +++ b/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImplLocal.qll @@ -1307,15 +1307,20 @@ private predicate localFlowExit(Node node, Configuration config) { */ pragma[nomagic] private predicate localFlowStepPlus( - Node node1, Node node2, boolean preservesValue, Configuration config, LocalCallContext cc + Node node1, Node node2, boolean preservesValue, DataFlowType t, Configuration config, + LocalCallContext cc ) { not isUnreachableInCall(node2, cc.(LocalCallContextSpecificCall).getCall()) and ( localFlowEntry(node1, config) and ( - localFlowStep(node1, node2, config) and preservesValue = true + localFlowStep(node1, node2, config) and + preservesValue = true and + t = getErasedNodeTypeBound(node1) or - additionalLocalFlowStep(node1, node2, config) and preservesValue = false + additionalLocalFlowStep(node1, node2, config) and + preservesValue = false and + t = getErasedNodeTypeBound(node2) ) and node1 != node2 and cc.relevantFor(node1.getEnclosingCallable()) and @@ -1323,17 +1328,18 @@ private predicate localFlowStepPlus( nodeCand(TNormalNode(node2), unbind(config)) or exists(Node mid | - localFlowStepPlus(node1, mid, preservesValue, config, cc) and + localFlowStepPlus(node1, mid, preservesValue, t, config, cc) and localFlowStep(mid, node2, config) and not mid instanceof CastNode and nodeCand(TNormalNode(node2), unbind(config)) ) or exists(Node mid | - localFlowStepPlus(node1, mid, _, config, cc) and + localFlowStepPlus(node1, mid, _, _, config, cc) and additionalLocalFlowStep(mid, node2, config) and not mid instanceof CastNode and preservesValue = false and + t = getErasedNodeTypeBound(node2) and nodeCand(TNormalNode(node2), unbind(config)) ) ) @@ -1345,17 +1351,18 @@ private predicate localFlowStepPlus( */ pragma[nomagic] private predicate localFlowBigStep( - Node node1, Node node2, boolean preservesValue, Configuration config, LocalCallContext callContext + Node node1, Node node2, boolean preservesValue, DataFlowType t, Configuration config, + LocalCallContext callContext ) { - localFlowStepPlus(node1, node2, preservesValue, config, callContext) and + localFlowStepPlus(node1, node2, preservesValue, t, config, callContext) and localFlowExit(node2, config) } pragma[nomagic] private predicate localFlowBigStepExt( - NodeExt node1, NodeExt node2, boolean preservesValue, Configuration config + NodeExt node1, NodeExt node2, boolean preservesValue, AccessPathFrontNil apf, Configuration config ) { - localFlowBigStep(node1.getNode(), node2.getNode(), preservesValue, config, _) + localFlowBigStep(node1.getNode(), node2.getNode(), preservesValue, apf.getType(), config, _) } private newtype TAccessPathFront = @@ -1395,46 +1402,24 @@ private predicate flowCandFwd( else any() } -/** - * A node that requires an empty access path and should have its tracked type - * (re-)computed. This is either a source or a node reached through an - * additional step. - */ -private class AccessPathFrontNilNode extends NormalNodeExt { - AccessPathFrontNilNode() { - nodeCand(this, _) and - ( - any(Configuration c).isSource(this.getNode()) - or - localFlowBigStepExt(_, this, false, _) - or - additionalJumpStepExt(_, this, _) - ) - } - - /** Gets the `nil` path front for this node. */ - AccessPathFrontNil getApf() { result = TFrontNil(this.getErasedNodeTypeBound()) } -} - private predicate flowCandFwd0( NodeExt node, boolean fromArg, AccessPathFront apf, Configuration config ) { nodeCand2(node, _, false, config) and config.isSource(node.getNode()) and fromArg = false and - apf = node.(AccessPathFrontNilNode).getApf() + apf = TFrontNil(node.getErasedNodeTypeBound()) or nodeCand(node, unbind(config)) and ( exists(NodeExt mid | flowCandFwd(mid, fromArg, apf, config) and - localFlowBigStepExt(mid, node, true, config) + localFlowBigStepExt(mid, node, true, _, config) ) or exists(NodeExt mid, AccessPathFrontNil nil | flowCandFwd(mid, fromArg, nil, config) and - localFlowBigStepExt(mid, node, false, config) and - apf = node.(AccessPathFrontNilNode).getApf() + localFlowBigStepExt(mid, node, false, apf, config) ) or exists(NodeExt mid | @@ -1447,7 +1432,7 @@ private predicate flowCandFwd0( flowCandFwd(mid, _, nil, config) and additionalJumpStepExt(mid, node, config) and fromArg = false and - apf = node.(AccessPathFrontNilNode).getApf() + apf = TFrontNil(node.getErasedNodeTypeBound()) ) or exists(NodeExt mid, boolean allowsFieldFlow | @@ -1589,13 +1574,13 @@ private predicate flowCand0( apf instanceof AccessPathFrontNil or exists(NodeExt mid | - localFlowBigStepExt(node, mid, true, config) and + localFlowBigStepExt(node, mid, true, _, config) and flowCand(mid, toReturn, apf, config) ) or exists(NodeExt mid, AccessPathFrontNil nil | flowCandFwd(node, _, apf, config) and - localFlowBigStepExt(node, mid, false, config) and + localFlowBigStepExt(node, mid, false, _, config) and flowCand(mid, toReturn, nil, config) and apf instanceof AccessPathFrontNil ) @@ -1810,18 +1795,6 @@ private predicate popWithFront(AccessPath ap0, Content f, AccessPathFront apf, A /** Gets the access path obtained by pushing `f` onto `ap`. */ private AccessPath push(Content f, AccessPath ap) { ap = pop(f, result) } -/** - * A node that requires an empty access path and should have its tracked type - * (re-)computed. This is either a source or a node reached through an - * additional step. - */ -private class AccessPathNilNode extends NormalNodeExt { - AccessPathNilNode() { flowCand(this.(AccessPathFrontNilNode), _, _, _) } - - /** Gets the `nil` path for this node. */ - AccessPathNil getAp() { result = TNil(this.getErasedNodeTypeBound()) } -} - /** * Holds if data can flow from a source to `node` with the given `ap`. */ @@ -1838,20 +1811,19 @@ private predicate flowFwd0( flowCand(node, _, _, config) and config.isSource(node.getNode()) and fromArg = false and - ap = node.(AccessPathNilNode).getAp() and + ap = TNil(node.getErasedNodeTypeBound()) and apf = ap.(AccessPathNil).getFront() or flowCand(node, _, _, unbind(config)) and ( exists(NodeExt mid | flowFwd(mid, fromArg, apf, ap, config) and - localFlowBigStepExt(mid, node, true, config) + localFlowBigStepExt(mid, node, true, _, config) ) or exists(NodeExt mid, AccessPathNil nil | flowFwd(mid, fromArg, _, nil, config) and - localFlowBigStepExt(mid, node, false, config) and - ap = node.(AccessPathNilNode).getAp() and + localFlowBigStepExt(mid, node, false, apf, config) and apf = ap.(AccessPathNil).getFront() ) or @@ -1865,7 +1837,7 @@ private predicate flowFwd0( flowFwd(mid, _, _, nil, config) and additionalJumpStepExt(mid, node, config) and fromArg = false and - ap = node.(AccessPathNilNode).getAp() and + ap = TNil(node.getErasedNodeTypeBound()) and apf = ap.(AccessPathNil).getFront() ) or @@ -1982,13 +1954,13 @@ private predicate flow0(NodeExt node, boolean toReturn, AccessPath ap, Configura ap instanceof AccessPathNil or exists(NodeExt mid | - localFlowBigStepExt(node, mid, true, config) and + localFlowBigStepExt(node, mid, true, _, config) and flow(mid, toReturn, ap, config) ) or exists(NodeExt mid, AccessPathNil nil | flowFwd(node, _, _, ap, config) and - localFlowBigStepExt(node, mid, false, config) and + localFlowBigStepExt(node, mid, false, _, config) and flow(mid, toReturn, nil, config) and ap instanceof AccessPathNil ) @@ -2164,7 +2136,7 @@ private newtype TPathNode = config.isSource(node) and cc instanceof CallContextAny and sc instanceof SummaryCtxNone and - ap = any(AccessPathNilNode nil | nil.getNode() = node).getAp() + ap = TNil(getErasedNodeTypeBound(node)) or // ... or a step from an existing PathNode to another node. exists(PathNodeMid mid | @@ -2357,12 +2329,11 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt pathIntoLocalStep(mid, midnode, cc, enclosing, sc, ap0, conf) and localCC = getLocalCallContext(cc, enclosing) | - localFlowBigStep(midnode, node, true, conf, localCC) and + localFlowBigStep(midnode, node, true, _, conf, localCC) and ap = ap0 or - localFlowBigStep(midnode, node, false, conf, localCC) and - ap0 instanceof AccessPathNil and - ap = any(AccessPathNilNode nil | nil.getNode() = node).getAp() + localFlowBigStep(midnode, node, false, ap.(AccessPathNil).getType(), conf, localCC) and + ap0 instanceof AccessPathNil ) or jumpStep(mid.getNode(), node, mid.getConfiguration()) and @@ -2374,7 +2345,7 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt cc instanceof CallContextAny and sc instanceof SummaryCtxNone and mid.getAp() instanceof AccessPathNil and - ap = any(AccessPathNilNode nil | nil.getNode() = node).getAp() + ap = TNil(getErasedNodeTypeBound(node)) or exists(Content f, AccessPath ap0 | pathReadStep(mid, node, ap0, f, cc) and ap = pop(f, ap0)) and sc = mid.getSummaryCtx() @@ -2397,7 +2368,7 @@ private predicate pathIntoLocalStep( midnode = mid.getNode() and cc = mid.getCallContext() and conf = mid.getConfiguration() and - localFlowBigStep(midnode, _, _, conf, _) and + localFlowBigStep(midnode, _, _, _, conf, _) and enclosing = midnode.getEnclosingCallable() and sc = mid.getSummaryCtx() and ap0 = mid.getAp() @@ -2949,7 +2920,7 @@ private module FlowExploration { config = mid.getConfiguration() } - pragma[noinline] + pragma[nomagic] private predicate partialPathOutOfCallable1( PartialPathNodePriv mid, DataFlowCall call, ReturnKindExt kind, CallContext cc, PartialAccessPath ap, Configuration config diff --git a/cpp/ql/src/semmle/code/cpp/exprs/Expr.qll b/cpp/ql/src/semmle/code/cpp/exprs/Expr.qll index c71403e84a2..079b031c530 100644 --- a/cpp/ql/src/semmle/code/cpp/exprs/Expr.qll +++ b/cpp/ql/src/semmle/code/cpp/exprs/Expr.qll @@ -442,6 +442,20 @@ class Expr extends StmtParent, @expr { else result = this } + /** + * Gets the unique non-`Conversion` expression `e` for which + * `this = e.getConversion*()`. + * + * For example, if called on the expression `(int)(char)x`, this predicate + * gets the expression `x`. + */ + Expr getUnconverted() { + not this instanceof Conversion and + result = this + or + result = this.(Conversion).getExpr().getUnconverted() + } + /** * Gets the type of this expression, after any implicit conversions and explicit casts, and after resolving typedefs. * 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 7e2107508a5..5672074aa51 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/dataflow/DefaultTaintTracking.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/dataflow/DefaultTaintTracking.qll @@ -269,7 +269,7 @@ private predicate modelTaintToParameter(Function f, int parameterIn, int paramet /** * Holds if `chi` is on the chain of chi-instructions for all aliased memory. - * Taint shoud not pass through these instructions since they tend to mix up + * Taint should not pass through these instructions since they tend to mix up * unrelated objects. */ private predicate isChiForAllAliasedMemory(Instruction instr) { @@ -277,7 +277,7 @@ private predicate isChiForAllAliasedMemory(Instruction instr) { or isChiForAllAliasedMemory(instr.(ChiInstruction).getTotal()) or - isChiForAllAliasedMemory(instr.(PhiInstruction).getAnInput()) + isChiForAllAliasedMemory(instr.(PhiInstruction).getAnInputOperand().getAnyDef()) } private predicate modelTaintToReturnValue(Function f, int parameterIn) { 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 f1d8c21595e..abb62b9a021 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 @@ -135,6 +135,12 @@ private module VirtualDispatch { exists(FunctionInstruction fi | this.flowsFrom(DataFlow::instructionNode(fi), _) and result = fi.getFunctionSymbol() + ) and + ( + this.getNumberOfArguments() <= result.getEffectiveNumberOfParameters() and + this.getNumberOfArguments() >= result.getEffectiveNumberOfParameters() + or + result.isVarargs() ) } } 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 5eb98aefe4e..e3f1da355ef 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 @@ -1307,15 +1307,20 @@ private predicate localFlowExit(Node node, Configuration config) { */ pragma[nomagic] private predicate localFlowStepPlus( - Node node1, Node node2, boolean preservesValue, Configuration config, LocalCallContext cc + Node node1, Node node2, boolean preservesValue, DataFlowType t, Configuration config, + LocalCallContext cc ) { not isUnreachableInCall(node2, cc.(LocalCallContextSpecificCall).getCall()) and ( localFlowEntry(node1, config) and ( - localFlowStep(node1, node2, config) and preservesValue = true + localFlowStep(node1, node2, config) and + preservesValue = true and + t = getErasedNodeTypeBound(node1) or - additionalLocalFlowStep(node1, node2, config) and preservesValue = false + additionalLocalFlowStep(node1, node2, config) and + preservesValue = false and + t = getErasedNodeTypeBound(node2) ) and node1 != node2 and cc.relevantFor(node1.getEnclosingCallable()) and @@ -1323,17 +1328,18 @@ private predicate localFlowStepPlus( nodeCand(TNormalNode(node2), unbind(config)) or exists(Node mid | - localFlowStepPlus(node1, mid, preservesValue, config, cc) and + localFlowStepPlus(node1, mid, preservesValue, t, config, cc) and localFlowStep(mid, node2, config) and not mid instanceof CastNode and nodeCand(TNormalNode(node2), unbind(config)) ) or exists(Node mid | - localFlowStepPlus(node1, mid, _, config, cc) and + localFlowStepPlus(node1, mid, _, _, config, cc) and additionalLocalFlowStep(mid, node2, config) and not mid instanceof CastNode and preservesValue = false and + t = getErasedNodeTypeBound(node2) and nodeCand(TNormalNode(node2), unbind(config)) ) ) @@ -1345,17 +1351,18 @@ private predicate localFlowStepPlus( */ pragma[nomagic] private predicate localFlowBigStep( - Node node1, Node node2, boolean preservesValue, Configuration config, LocalCallContext callContext + Node node1, Node node2, boolean preservesValue, DataFlowType t, Configuration config, + LocalCallContext callContext ) { - localFlowStepPlus(node1, node2, preservesValue, config, callContext) and + localFlowStepPlus(node1, node2, preservesValue, t, config, callContext) and localFlowExit(node2, config) } pragma[nomagic] private predicate localFlowBigStepExt( - NodeExt node1, NodeExt node2, boolean preservesValue, Configuration config + NodeExt node1, NodeExt node2, boolean preservesValue, AccessPathFrontNil apf, Configuration config ) { - localFlowBigStep(node1.getNode(), node2.getNode(), preservesValue, config, _) + localFlowBigStep(node1.getNode(), node2.getNode(), preservesValue, apf.getType(), config, _) } private newtype TAccessPathFront = @@ -1395,46 +1402,24 @@ private predicate flowCandFwd( else any() } -/** - * A node that requires an empty access path and should have its tracked type - * (re-)computed. This is either a source or a node reached through an - * additional step. - */ -private class AccessPathFrontNilNode extends NormalNodeExt { - AccessPathFrontNilNode() { - nodeCand(this, _) and - ( - any(Configuration c).isSource(this.getNode()) - or - localFlowBigStepExt(_, this, false, _) - or - additionalJumpStepExt(_, this, _) - ) - } - - /** Gets the `nil` path front for this node. */ - AccessPathFrontNil getApf() { result = TFrontNil(this.getErasedNodeTypeBound()) } -} - private predicate flowCandFwd0( NodeExt node, boolean fromArg, AccessPathFront apf, Configuration config ) { nodeCand2(node, _, false, config) and config.isSource(node.getNode()) and fromArg = false and - apf = node.(AccessPathFrontNilNode).getApf() + apf = TFrontNil(node.getErasedNodeTypeBound()) or nodeCand(node, unbind(config)) and ( exists(NodeExt mid | flowCandFwd(mid, fromArg, apf, config) and - localFlowBigStepExt(mid, node, true, config) + localFlowBigStepExt(mid, node, true, _, config) ) or exists(NodeExt mid, AccessPathFrontNil nil | flowCandFwd(mid, fromArg, nil, config) and - localFlowBigStepExt(mid, node, false, config) and - apf = node.(AccessPathFrontNilNode).getApf() + localFlowBigStepExt(mid, node, false, apf, config) ) or exists(NodeExt mid | @@ -1447,7 +1432,7 @@ private predicate flowCandFwd0( flowCandFwd(mid, _, nil, config) and additionalJumpStepExt(mid, node, config) and fromArg = false and - apf = node.(AccessPathFrontNilNode).getApf() + apf = TFrontNil(node.getErasedNodeTypeBound()) ) or exists(NodeExt mid, boolean allowsFieldFlow | @@ -1589,13 +1574,13 @@ private predicate flowCand0( apf instanceof AccessPathFrontNil or exists(NodeExt mid | - localFlowBigStepExt(node, mid, true, config) and + localFlowBigStepExt(node, mid, true, _, config) and flowCand(mid, toReturn, apf, config) ) or exists(NodeExt mid, AccessPathFrontNil nil | flowCandFwd(node, _, apf, config) and - localFlowBigStepExt(node, mid, false, config) and + localFlowBigStepExt(node, mid, false, _, config) and flowCand(mid, toReturn, nil, config) and apf instanceof AccessPathFrontNil ) @@ -1810,18 +1795,6 @@ private predicate popWithFront(AccessPath ap0, Content f, AccessPathFront apf, A /** Gets the access path obtained by pushing `f` onto `ap`. */ private AccessPath push(Content f, AccessPath ap) { ap = pop(f, result) } -/** - * A node that requires an empty access path and should have its tracked type - * (re-)computed. This is either a source or a node reached through an - * additional step. - */ -private class AccessPathNilNode extends NormalNodeExt { - AccessPathNilNode() { flowCand(this.(AccessPathFrontNilNode), _, _, _) } - - /** Gets the `nil` path for this node. */ - AccessPathNil getAp() { result = TNil(this.getErasedNodeTypeBound()) } -} - /** * Holds if data can flow from a source to `node` with the given `ap`. */ @@ -1838,20 +1811,19 @@ private predicate flowFwd0( flowCand(node, _, _, config) and config.isSource(node.getNode()) and fromArg = false and - ap = node.(AccessPathNilNode).getAp() and + ap = TNil(node.getErasedNodeTypeBound()) and apf = ap.(AccessPathNil).getFront() or flowCand(node, _, _, unbind(config)) and ( exists(NodeExt mid | flowFwd(mid, fromArg, apf, ap, config) and - localFlowBigStepExt(mid, node, true, config) + localFlowBigStepExt(mid, node, true, _, config) ) or exists(NodeExt mid, AccessPathNil nil | flowFwd(mid, fromArg, _, nil, config) and - localFlowBigStepExt(mid, node, false, config) and - ap = node.(AccessPathNilNode).getAp() and + localFlowBigStepExt(mid, node, false, apf, config) and apf = ap.(AccessPathNil).getFront() ) or @@ -1865,7 +1837,7 @@ private predicate flowFwd0( flowFwd(mid, _, _, nil, config) and additionalJumpStepExt(mid, node, config) and fromArg = false and - ap = node.(AccessPathNilNode).getAp() and + ap = TNil(node.getErasedNodeTypeBound()) and apf = ap.(AccessPathNil).getFront() ) or @@ -1982,13 +1954,13 @@ private predicate flow0(NodeExt node, boolean toReturn, AccessPath ap, Configura ap instanceof AccessPathNil or exists(NodeExt mid | - localFlowBigStepExt(node, mid, true, config) and + localFlowBigStepExt(node, mid, true, _, config) and flow(mid, toReturn, ap, config) ) or exists(NodeExt mid, AccessPathNil nil | flowFwd(node, _, _, ap, config) and - localFlowBigStepExt(node, mid, false, config) and + localFlowBigStepExt(node, mid, false, _, config) and flow(mid, toReturn, nil, config) and ap instanceof AccessPathNil ) @@ -2164,7 +2136,7 @@ private newtype TPathNode = config.isSource(node) and cc instanceof CallContextAny and sc instanceof SummaryCtxNone and - ap = any(AccessPathNilNode nil | nil.getNode() = node).getAp() + ap = TNil(getErasedNodeTypeBound(node)) or // ... or a step from an existing PathNode to another node. exists(PathNodeMid mid | @@ -2357,12 +2329,11 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt pathIntoLocalStep(mid, midnode, cc, enclosing, sc, ap0, conf) and localCC = getLocalCallContext(cc, enclosing) | - localFlowBigStep(midnode, node, true, conf, localCC) and + localFlowBigStep(midnode, node, true, _, conf, localCC) and ap = ap0 or - localFlowBigStep(midnode, node, false, conf, localCC) and - ap0 instanceof AccessPathNil and - ap = any(AccessPathNilNode nil | nil.getNode() = node).getAp() + localFlowBigStep(midnode, node, false, ap.(AccessPathNil).getType(), conf, localCC) and + ap0 instanceof AccessPathNil ) or jumpStep(mid.getNode(), node, mid.getConfiguration()) and @@ -2374,7 +2345,7 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt cc instanceof CallContextAny and sc instanceof SummaryCtxNone and mid.getAp() instanceof AccessPathNil and - ap = any(AccessPathNilNode nil | nil.getNode() = node).getAp() + ap = TNil(getErasedNodeTypeBound(node)) or exists(Content f, AccessPath ap0 | pathReadStep(mid, node, ap0, f, cc) and ap = pop(f, ap0)) and sc = mid.getSummaryCtx() @@ -2397,7 +2368,7 @@ private predicate pathIntoLocalStep( midnode = mid.getNode() and cc = mid.getCallContext() and conf = mid.getConfiguration() and - localFlowBigStep(midnode, _, _, conf, _) and + localFlowBigStep(midnode, _, _, _, conf, _) and enclosing = midnode.getEnclosingCallable() and sc = mid.getSummaryCtx() and ap0 = mid.getAp() @@ -2949,7 +2920,7 @@ private module FlowExploration { config = mid.getConfiguration() } - pragma[noinline] + pragma[nomagic] private predicate partialPathOutOfCallable1( PartialPathNodePriv mid, DataFlowCall call, ReturnKindExt kind, CallContext cc, PartialAccessPath ap, Configuration config 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 5eb98aefe4e..e3f1da355ef 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 @@ -1307,15 +1307,20 @@ private predicate localFlowExit(Node node, Configuration config) { */ pragma[nomagic] private predicate localFlowStepPlus( - Node node1, Node node2, boolean preservesValue, Configuration config, LocalCallContext cc + Node node1, Node node2, boolean preservesValue, DataFlowType t, Configuration config, + LocalCallContext cc ) { not isUnreachableInCall(node2, cc.(LocalCallContextSpecificCall).getCall()) and ( localFlowEntry(node1, config) and ( - localFlowStep(node1, node2, config) and preservesValue = true + localFlowStep(node1, node2, config) and + preservesValue = true and + t = getErasedNodeTypeBound(node1) or - additionalLocalFlowStep(node1, node2, config) and preservesValue = false + additionalLocalFlowStep(node1, node2, config) and + preservesValue = false and + t = getErasedNodeTypeBound(node2) ) and node1 != node2 and cc.relevantFor(node1.getEnclosingCallable()) and @@ -1323,17 +1328,18 @@ private predicate localFlowStepPlus( nodeCand(TNormalNode(node2), unbind(config)) or exists(Node mid | - localFlowStepPlus(node1, mid, preservesValue, config, cc) and + localFlowStepPlus(node1, mid, preservesValue, t, config, cc) and localFlowStep(mid, node2, config) and not mid instanceof CastNode and nodeCand(TNormalNode(node2), unbind(config)) ) or exists(Node mid | - localFlowStepPlus(node1, mid, _, config, cc) and + localFlowStepPlus(node1, mid, _, _, config, cc) and additionalLocalFlowStep(mid, node2, config) and not mid instanceof CastNode and preservesValue = false and + t = getErasedNodeTypeBound(node2) and nodeCand(TNormalNode(node2), unbind(config)) ) ) @@ -1345,17 +1351,18 @@ private predicate localFlowStepPlus( */ pragma[nomagic] private predicate localFlowBigStep( - Node node1, Node node2, boolean preservesValue, Configuration config, LocalCallContext callContext + Node node1, Node node2, boolean preservesValue, DataFlowType t, Configuration config, + LocalCallContext callContext ) { - localFlowStepPlus(node1, node2, preservesValue, config, callContext) and + localFlowStepPlus(node1, node2, preservesValue, t, config, callContext) and localFlowExit(node2, config) } pragma[nomagic] private predicate localFlowBigStepExt( - NodeExt node1, NodeExt node2, boolean preservesValue, Configuration config + NodeExt node1, NodeExt node2, boolean preservesValue, AccessPathFrontNil apf, Configuration config ) { - localFlowBigStep(node1.getNode(), node2.getNode(), preservesValue, config, _) + localFlowBigStep(node1.getNode(), node2.getNode(), preservesValue, apf.getType(), config, _) } private newtype TAccessPathFront = @@ -1395,46 +1402,24 @@ private predicate flowCandFwd( else any() } -/** - * A node that requires an empty access path and should have its tracked type - * (re-)computed. This is either a source or a node reached through an - * additional step. - */ -private class AccessPathFrontNilNode extends NormalNodeExt { - AccessPathFrontNilNode() { - nodeCand(this, _) and - ( - any(Configuration c).isSource(this.getNode()) - or - localFlowBigStepExt(_, this, false, _) - or - additionalJumpStepExt(_, this, _) - ) - } - - /** Gets the `nil` path front for this node. */ - AccessPathFrontNil getApf() { result = TFrontNil(this.getErasedNodeTypeBound()) } -} - private predicate flowCandFwd0( NodeExt node, boolean fromArg, AccessPathFront apf, Configuration config ) { nodeCand2(node, _, false, config) and config.isSource(node.getNode()) and fromArg = false and - apf = node.(AccessPathFrontNilNode).getApf() + apf = TFrontNil(node.getErasedNodeTypeBound()) or nodeCand(node, unbind(config)) and ( exists(NodeExt mid | flowCandFwd(mid, fromArg, apf, config) and - localFlowBigStepExt(mid, node, true, config) + localFlowBigStepExt(mid, node, true, _, config) ) or exists(NodeExt mid, AccessPathFrontNil nil | flowCandFwd(mid, fromArg, nil, config) and - localFlowBigStepExt(mid, node, false, config) and - apf = node.(AccessPathFrontNilNode).getApf() + localFlowBigStepExt(mid, node, false, apf, config) ) or exists(NodeExt mid | @@ -1447,7 +1432,7 @@ private predicate flowCandFwd0( flowCandFwd(mid, _, nil, config) and additionalJumpStepExt(mid, node, config) and fromArg = false and - apf = node.(AccessPathFrontNilNode).getApf() + apf = TFrontNil(node.getErasedNodeTypeBound()) ) or exists(NodeExt mid, boolean allowsFieldFlow | @@ -1589,13 +1574,13 @@ private predicate flowCand0( apf instanceof AccessPathFrontNil or exists(NodeExt mid | - localFlowBigStepExt(node, mid, true, config) and + localFlowBigStepExt(node, mid, true, _, config) and flowCand(mid, toReturn, apf, config) ) or exists(NodeExt mid, AccessPathFrontNil nil | flowCandFwd(node, _, apf, config) and - localFlowBigStepExt(node, mid, false, config) and + localFlowBigStepExt(node, mid, false, _, config) and flowCand(mid, toReturn, nil, config) and apf instanceof AccessPathFrontNil ) @@ -1810,18 +1795,6 @@ private predicate popWithFront(AccessPath ap0, Content f, AccessPathFront apf, A /** Gets the access path obtained by pushing `f` onto `ap`. */ private AccessPath push(Content f, AccessPath ap) { ap = pop(f, result) } -/** - * A node that requires an empty access path and should have its tracked type - * (re-)computed. This is either a source or a node reached through an - * additional step. - */ -private class AccessPathNilNode extends NormalNodeExt { - AccessPathNilNode() { flowCand(this.(AccessPathFrontNilNode), _, _, _) } - - /** Gets the `nil` path for this node. */ - AccessPathNil getAp() { result = TNil(this.getErasedNodeTypeBound()) } -} - /** * Holds if data can flow from a source to `node` with the given `ap`. */ @@ -1838,20 +1811,19 @@ private predicate flowFwd0( flowCand(node, _, _, config) and config.isSource(node.getNode()) and fromArg = false and - ap = node.(AccessPathNilNode).getAp() and + ap = TNil(node.getErasedNodeTypeBound()) and apf = ap.(AccessPathNil).getFront() or flowCand(node, _, _, unbind(config)) and ( exists(NodeExt mid | flowFwd(mid, fromArg, apf, ap, config) and - localFlowBigStepExt(mid, node, true, config) + localFlowBigStepExt(mid, node, true, _, config) ) or exists(NodeExt mid, AccessPathNil nil | flowFwd(mid, fromArg, _, nil, config) and - localFlowBigStepExt(mid, node, false, config) and - ap = node.(AccessPathNilNode).getAp() and + localFlowBigStepExt(mid, node, false, apf, config) and apf = ap.(AccessPathNil).getFront() ) or @@ -1865,7 +1837,7 @@ private predicate flowFwd0( flowFwd(mid, _, _, nil, config) and additionalJumpStepExt(mid, node, config) and fromArg = false and - ap = node.(AccessPathNilNode).getAp() and + ap = TNil(node.getErasedNodeTypeBound()) and apf = ap.(AccessPathNil).getFront() ) or @@ -1982,13 +1954,13 @@ private predicate flow0(NodeExt node, boolean toReturn, AccessPath ap, Configura ap instanceof AccessPathNil or exists(NodeExt mid | - localFlowBigStepExt(node, mid, true, config) and + localFlowBigStepExt(node, mid, true, _, config) and flow(mid, toReturn, ap, config) ) or exists(NodeExt mid, AccessPathNil nil | flowFwd(node, _, _, ap, config) and - localFlowBigStepExt(node, mid, false, config) and + localFlowBigStepExt(node, mid, false, _, config) and flow(mid, toReturn, nil, config) and ap instanceof AccessPathNil ) @@ -2164,7 +2136,7 @@ private newtype TPathNode = config.isSource(node) and cc instanceof CallContextAny and sc instanceof SummaryCtxNone and - ap = any(AccessPathNilNode nil | nil.getNode() = node).getAp() + ap = TNil(getErasedNodeTypeBound(node)) or // ... or a step from an existing PathNode to another node. exists(PathNodeMid mid | @@ -2357,12 +2329,11 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt pathIntoLocalStep(mid, midnode, cc, enclosing, sc, ap0, conf) and localCC = getLocalCallContext(cc, enclosing) | - localFlowBigStep(midnode, node, true, conf, localCC) and + localFlowBigStep(midnode, node, true, _, conf, localCC) and ap = ap0 or - localFlowBigStep(midnode, node, false, conf, localCC) and - ap0 instanceof AccessPathNil and - ap = any(AccessPathNilNode nil | nil.getNode() = node).getAp() + localFlowBigStep(midnode, node, false, ap.(AccessPathNil).getType(), conf, localCC) and + ap0 instanceof AccessPathNil ) or jumpStep(mid.getNode(), node, mid.getConfiguration()) and @@ -2374,7 +2345,7 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt cc instanceof CallContextAny and sc instanceof SummaryCtxNone and mid.getAp() instanceof AccessPathNil and - ap = any(AccessPathNilNode nil | nil.getNode() = node).getAp() + ap = TNil(getErasedNodeTypeBound(node)) or exists(Content f, AccessPath ap0 | pathReadStep(mid, node, ap0, f, cc) and ap = pop(f, ap0)) and sc = mid.getSummaryCtx() @@ -2397,7 +2368,7 @@ private predicate pathIntoLocalStep( midnode = mid.getNode() and cc = mid.getCallContext() and conf = mid.getConfiguration() and - localFlowBigStep(midnode, _, _, conf, _) and + localFlowBigStep(midnode, _, _, _, conf, _) and enclosing = midnode.getEnclosingCallable() and sc = mid.getSummaryCtx() and ap0 = mid.getAp() @@ -2949,7 +2920,7 @@ private module FlowExploration { config = mid.getConfiguration() } - pragma[noinline] + pragma[nomagic] private predicate partialPathOutOfCallable1( PartialPathNodePriv mid, DataFlowCall call, ReturnKindExt kind, CallContext cc, PartialAccessPath ap, Configuration config 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 5eb98aefe4e..e3f1da355ef 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 @@ -1307,15 +1307,20 @@ private predicate localFlowExit(Node node, Configuration config) { */ pragma[nomagic] private predicate localFlowStepPlus( - Node node1, Node node2, boolean preservesValue, Configuration config, LocalCallContext cc + Node node1, Node node2, boolean preservesValue, DataFlowType t, Configuration config, + LocalCallContext cc ) { not isUnreachableInCall(node2, cc.(LocalCallContextSpecificCall).getCall()) and ( localFlowEntry(node1, config) and ( - localFlowStep(node1, node2, config) and preservesValue = true + localFlowStep(node1, node2, config) and + preservesValue = true and + t = getErasedNodeTypeBound(node1) or - additionalLocalFlowStep(node1, node2, config) and preservesValue = false + additionalLocalFlowStep(node1, node2, config) and + preservesValue = false and + t = getErasedNodeTypeBound(node2) ) and node1 != node2 and cc.relevantFor(node1.getEnclosingCallable()) and @@ -1323,17 +1328,18 @@ private predicate localFlowStepPlus( nodeCand(TNormalNode(node2), unbind(config)) or exists(Node mid | - localFlowStepPlus(node1, mid, preservesValue, config, cc) and + localFlowStepPlus(node1, mid, preservesValue, t, config, cc) and localFlowStep(mid, node2, config) and not mid instanceof CastNode and nodeCand(TNormalNode(node2), unbind(config)) ) or exists(Node mid | - localFlowStepPlus(node1, mid, _, config, cc) and + localFlowStepPlus(node1, mid, _, _, config, cc) and additionalLocalFlowStep(mid, node2, config) and not mid instanceof CastNode and preservesValue = false and + t = getErasedNodeTypeBound(node2) and nodeCand(TNormalNode(node2), unbind(config)) ) ) @@ -1345,17 +1351,18 @@ private predicate localFlowStepPlus( */ pragma[nomagic] private predicate localFlowBigStep( - Node node1, Node node2, boolean preservesValue, Configuration config, LocalCallContext callContext + Node node1, Node node2, boolean preservesValue, DataFlowType t, Configuration config, + LocalCallContext callContext ) { - localFlowStepPlus(node1, node2, preservesValue, config, callContext) and + localFlowStepPlus(node1, node2, preservesValue, t, config, callContext) and localFlowExit(node2, config) } pragma[nomagic] private predicate localFlowBigStepExt( - NodeExt node1, NodeExt node2, boolean preservesValue, Configuration config + NodeExt node1, NodeExt node2, boolean preservesValue, AccessPathFrontNil apf, Configuration config ) { - localFlowBigStep(node1.getNode(), node2.getNode(), preservesValue, config, _) + localFlowBigStep(node1.getNode(), node2.getNode(), preservesValue, apf.getType(), config, _) } private newtype TAccessPathFront = @@ -1395,46 +1402,24 @@ private predicate flowCandFwd( else any() } -/** - * A node that requires an empty access path and should have its tracked type - * (re-)computed. This is either a source or a node reached through an - * additional step. - */ -private class AccessPathFrontNilNode extends NormalNodeExt { - AccessPathFrontNilNode() { - nodeCand(this, _) and - ( - any(Configuration c).isSource(this.getNode()) - or - localFlowBigStepExt(_, this, false, _) - or - additionalJumpStepExt(_, this, _) - ) - } - - /** Gets the `nil` path front for this node. */ - AccessPathFrontNil getApf() { result = TFrontNil(this.getErasedNodeTypeBound()) } -} - private predicate flowCandFwd0( NodeExt node, boolean fromArg, AccessPathFront apf, Configuration config ) { nodeCand2(node, _, false, config) and config.isSource(node.getNode()) and fromArg = false and - apf = node.(AccessPathFrontNilNode).getApf() + apf = TFrontNil(node.getErasedNodeTypeBound()) or nodeCand(node, unbind(config)) and ( exists(NodeExt mid | flowCandFwd(mid, fromArg, apf, config) and - localFlowBigStepExt(mid, node, true, config) + localFlowBigStepExt(mid, node, true, _, config) ) or exists(NodeExt mid, AccessPathFrontNil nil | flowCandFwd(mid, fromArg, nil, config) and - localFlowBigStepExt(mid, node, false, config) and - apf = node.(AccessPathFrontNilNode).getApf() + localFlowBigStepExt(mid, node, false, apf, config) ) or exists(NodeExt mid | @@ -1447,7 +1432,7 @@ private predicate flowCandFwd0( flowCandFwd(mid, _, nil, config) and additionalJumpStepExt(mid, node, config) and fromArg = false and - apf = node.(AccessPathFrontNilNode).getApf() + apf = TFrontNil(node.getErasedNodeTypeBound()) ) or exists(NodeExt mid, boolean allowsFieldFlow | @@ -1589,13 +1574,13 @@ private predicate flowCand0( apf instanceof AccessPathFrontNil or exists(NodeExt mid | - localFlowBigStepExt(node, mid, true, config) and + localFlowBigStepExt(node, mid, true, _, config) and flowCand(mid, toReturn, apf, config) ) or exists(NodeExt mid, AccessPathFrontNil nil | flowCandFwd(node, _, apf, config) and - localFlowBigStepExt(node, mid, false, config) and + localFlowBigStepExt(node, mid, false, _, config) and flowCand(mid, toReturn, nil, config) and apf instanceof AccessPathFrontNil ) @@ -1810,18 +1795,6 @@ private predicate popWithFront(AccessPath ap0, Content f, AccessPathFront apf, A /** Gets the access path obtained by pushing `f` onto `ap`. */ private AccessPath push(Content f, AccessPath ap) { ap = pop(f, result) } -/** - * A node that requires an empty access path and should have its tracked type - * (re-)computed. This is either a source or a node reached through an - * additional step. - */ -private class AccessPathNilNode extends NormalNodeExt { - AccessPathNilNode() { flowCand(this.(AccessPathFrontNilNode), _, _, _) } - - /** Gets the `nil` path for this node. */ - AccessPathNil getAp() { result = TNil(this.getErasedNodeTypeBound()) } -} - /** * Holds if data can flow from a source to `node` with the given `ap`. */ @@ -1838,20 +1811,19 @@ private predicate flowFwd0( flowCand(node, _, _, config) and config.isSource(node.getNode()) and fromArg = false and - ap = node.(AccessPathNilNode).getAp() and + ap = TNil(node.getErasedNodeTypeBound()) and apf = ap.(AccessPathNil).getFront() or flowCand(node, _, _, unbind(config)) and ( exists(NodeExt mid | flowFwd(mid, fromArg, apf, ap, config) and - localFlowBigStepExt(mid, node, true, config) + localFlowBigStepExt(mid, node, true, _, config) ) or exists(NodeExt mid, AccessPathNil nil | flowFwd(mid, fromArg, _, nil, config) and - localFlowBigStepExt(mid, node, false, config) and - ap = node.(AccessPathNilNode).getAp() and + localFlowBigStepExt(mid, node, false, apf, config) and apf = ap.(AccessPathNil).getFront() ) or @@ -1865,7 +1837,7 @@ private predicate flowFwd0( flowFwd(mid, _, _, nil, config) and additionalJumpStepExt(mid, node, config) and fromArg = false and - ap = node.(AccessPathNilNode).getAp() and + ap = TNil(node.getErasedNodeTypeBound()) and apf = ap.(AccessPathNil).getFront() ) or @@ -1982,13 +1954,13 @@ private predicate flow0(NodeExt node, boolean toReturn, AccessPath ap, Configura ap instanceof AccessPathNil or exists(NodeExt mid | - localFlowBigStepExt(node, mid, true, config) and + localFlowBigStepExt(node, mid, true, _, config) and flow(mid, toReturn, ap, config) ) or exists(NodeExt mid, AccessPathNil nil | flowFwd(node, _, _, ap, config) and - localFlowBigStepExt(node, mid, false, config) and + localFlowBigStepExt(node, mid, false, _, config) and flow(mid, toReturn, nil, config) and ap instanceof AccessPathNil ) @@ -2164,7 +2136,7 @@ private newtype TPathNode = config.isSource(node) and cc instanceof CallContextAny and sc instanceof SummaryCtxNone and - ap = any(AccessPathNilNode nil | nil.getNode() = node).getAp() + ap = TNil(getErasedNodeTypeBound(node)) or // ... or a step from an existing PathNode to another node. exists(PathNodeMid mid | @@ -2357,12 +2329,11 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt pathIntoLocalStep(mid, midnode, cc, enclosing, sc, ap0, conf) and localCC = getLocalCallContext(cc, enclosing) | - localFlowBigStep(midnode, node, true, conf, localCC) and + localFlowBigStep(midnode, node, true, _, conf, localCC) and ap = ap0 or - localFlowBigStep(midnode, node, false, conf, localCC) and - ap0 instanceof AccessPathNil and - ap = any(AccessPathNilNode nil | nil.getNode() = node).getAp() + localFlowBigStep(midnode, node, false, ap.(AccessPathNil).getType(), conf, localCC) and + ap0 instanceof AccessPathNil ) or jumpStep(mid.getNode(), node, mid.getConfiguration()) and @@ -2374,7 +2345,7 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt cc instanceof CallContextAny and sc instanceof SummaryCtxNone and mid.getAp() instanceof AccessPathNil and - ap = any(AccessPathNilNode nil | nil.getNode() = node).getAp() + ap = TNil(getErasedNodeTypeBound(node)) or exists(Content f, AccessPath ap0 | pathReadStep(mid, node, ap0, f, cc) and ap = pop(f, ap0)) and sc = mid.getSummaryCtx() @@ -2397,7 +2368,7 @@ private predicate pathIntoLocalStep( midnode = mid.getNode() and cc = mid.getCallContext() and conf = mid.getConfiguration() and - localFlowBigStep(midnode, _, _, conf, _) and + localFlowBigStep(midnode, _, _, _, conf, _) and enclosing = midnode.getEnclosingCallable() and sc = mid.getSummaryCtx() and ap0 = mid.getAp() @@ -2949,7 +2920,7 @@ private module FlowExploration { config = mid.getConfiguration() } - pragma[noinline] + pragma[nomagic] private predicate partialPathOutOfCallable1( PartialPathNodePriv mid, DataFlowCall call, ReturnKindExt kind, CallContext cc, PartialAccessPath ap, Configuration config 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 5eb98aefe4e..e3f1da355ef 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 @@ -1307,15 +1307,20 @@ private predicate localFlowExit(Node node, Configuration config) { */ pragma[nomagic] private predicate localFlowStepPlus( - Node node1, Node node2, boolean preservesValue, Configuration config, LocalCallContext cc + Node node1, Node node2, boolean preservesValue, DataFlowType t, Configuration config, + LocalCallContext cc ) { not isUnreachableInCall(node2, cc.(LocalCallContextSpecificCall).getCall()) and ( localFlowEntry(node1, config) and ( - localFlowStep(node1, node2, config) and preservesValue = true + localFlowStep(node1, node2, config) and + preservesValue = true and + t = getErasedNodeTypeBound(node1) or - additionalLocalFlowStep(node1, node2, config) and preservesValue = false + additionalLocalFlowStep(node1, node2, config) and + preservesValue = false and + t = getErasedNodeTypeBound(node2) ) and node1 != node2 and cc.relevantFor(node1.getEnclosingCallable()) and @@ -1323,17 +1328,18 @@ private predicate localFlowStepPlus( nodeCand(TNormalNode(node2), unbind(config)) or exists(Node mid | - localFlowStepPlus(node1, mid, preservesValue, config, cc) and + localFlowStepPlus(node1, mid, preservesValue, t, config, cc) and localFlowStep(mid, node2, config) and not mid instanceof CastNode and nodeCand(TNormalNode(node2), unbind(config)) ) or exists(Node mid | - localFlowStepPlus(node1, mid, _, config, cc) and + localFlowStepPlus(node1, mid, _, _, config, cc) and additionalLocalFlowStep(mid, node2, config) and not mid instanceof CastNode and preservesValue = false and + t = getErasedNodeTypeBound(node2) and nodeCand(TNormalNode(node2), unbind(config)) ) ) @@ -1345,17 +1351,18 @@ private predicate localFlowStepPlus( */ pragma[nomagic] private predicate localFlowBigStep( - Node node1, Node node2, boolean preservesValue, Configuration config, LocalCallContext callContext + Node node1, Node node2, boolean preservesValue, DataFlowType t, Configuration config, + LocalCallContext callContext ) { - localFlowStepPlus(node1, node2, preservesValue, config, callContext) and + localFlowStepPlus(node1, node2, preservesValue, t, config, callContext) and localFlowExit(node2, config) } pragma[nomagic] private predicate localFlowBigStepExt( - NodeExt node1, NodeExt node2, boolean preservesValue, Configuration config + NodeExt node1, NodeExt node2, boolean preservesValue, AccessPathFrontNil apf, Configuration config ) { - localFlowBigStep(node1.getNode(), node2.getNode(), preservesValue, config, _) + localFlowBigStep(node1.getNode(), node2.getNode(), preservesValue, apf.getType(), config, _) } private newtype TAccessPathFront = @@ -1395,46 +1402,24 @@ private predicate flowCandFwd( else any() } -/** - * A node that requires an empty access path and should have its tracked type - * (re-)computed. This is either a source or a node reached through an - * additional step. - */ -private class AccessPathFrontNilNode extends NormalNodeExt { - AccessPathFrontNilNode() { - nodeCand(this, _) and - ( - any(Configuration c).isSource(this.getNode()) - or - localFlowBigStepExt(_, this, false, _) - or - additionalJumpStepExt(_, this, _) - ) - } - - /** Gets the `nil` path front for this node. */ - AccessPathFrontNil getApf() { result = TFrontNil(this.getErasedNodeTypeBound()) } -} - private predicate flowCandFwd0( NodeExt node, boolean fromArg, AccessPathFront apf, Configuration config ) { nodeCand2(node, _, false, config) and config.isSource(node.getNode()) and fromArg = false and - apf = node.(AccessPathFrontNilNode).getApf() + apf = TFrontNil(node.getErasedNodeTypeBound()) or nodeCand(node, unbind(config)) and ( exists(NodeExt mid | flowCandFwd(mid, fromArg, apf, config) and - localFlowBigStepExt(mid, node, true, config) + localFlowBigStepExt(mid, node, true, _, config) ) or exists(NodeExt mid, AccessPathFrontNil nil | flowCandFwd(mid, fromArg, nil, config) and - localFlowBigStepExt(mid, node, false, config) and - apf = node.(AccessPathFrontNilNode).getApf() + localFlowBigStepExt(mid, node, false, apf, config) ) or exists(NodeExt mid | @@ -1447,7 +1432,7 @@ private predicate flowCandFwd0( flowCandFwd(mid, _, nil, config) and additionalJumpStepExt(mid, node, config) and fromArg = false and - apf = node.(AccessPathFrontNilNode).getApf() + apf = TFrontNil(node.getErasedNodeTypeBound()) ) or exists(NodeExt mid, boolean allowsFieldFlow | @@ -1589,13 +1574,13 @@ private predicate flowCand0( apf instanceof AccessPathFrontNil or exists(NodeExt mid | - localFlowBigStepExt(node, mid, true, config) and + localFlowBigStepExt(node, mid, true, _, config) and flowCand(mid, toReturn, apf, config) ) or exists(NodeExt mid, AccessPathFrontNil nil | flowCandFwd(node, _, apf, config) and - localFlowBigStepExt(node, mid, false, config) and + localFlowBigStepExt(node, mid, false, _, config) and flowCand(mid, toReturn, nil, config) and apf instanceof AccessPathFrontNil ) @@ -1810,18 +1795,6 @@ private predicate popWithFront(AccessPath ap0, Content f, AccessPathFront apf, A /** Gets the access path obtained by pushing `f` onto `ap`. */ private AccessPath push(Content f, AccessPath ap) { ap = pop(f, result) } -/** - * A node that requires an empty access path and should have its tracked type - * (re-)computed. This is either a source or a node reached through an - * additional step. - */ -private class AccessPathNilNode extends NormalNodeExt { - AccessPathNilNode() { flowCand(this.(AccessPathFrontNilNode), _, _, _) } - - /** Gets the `nil` path for this node. */ - AccessPathNil getAp() { result = TNil(this.getErasedNodeTypeBound()) } -} - /** * Holds if data can flow from a source to `node` with the given `ap`. */ @@ -1838,20 +1811,19 @@ private predicate flowFwd0( flowCand(node, _, _, config) and config.isSource(node.getNode()) and fromArg = false and - ap = node.(AccessPathNilNode).getAp() and + ap = TNil(node.getErasedNodeTypeBound()) and apf = ap.(AccessPathNil).getFront() or flowCand(node, _, _, unbind(config)) and ( exists(NodeExt mid | flowFwd(mid, fromArg, apf, ap, config) and - localFlowBigStepExt(mid, node, true, config) + localFlowBigStepExt(mid, node, true, _, config) ) or exists(NodeExt mid, AccessPathNil nil | flowFwd(mid, fromArg, _, nil, config) and - localFlowBigStepExt(mid, node, false, config) and - ap = node.(AccessPathNilNode).getAp() and + localFlowBigStepExt(mid, node, false, apf, config) and apf = ap.(AccessPathNil).getFront() ) or @@ -1865,7 +1837,7 @@ private predicate flowFwd0( flowFwd(mid, _, _, nil, config) and additionalJumpStepExt(mid, node, config) and fromArg = false and - ap = node.(AccessPathNilNode).getAp() and + ap = TNil(node.getErasedNodeTypeBound()) and apf = ap.(AccessPathNil).getFront() ) or @@ -1982,13 +1954,13 @@ private predicate flow0(NodeExt node, boolean toReturn, AccessPath ap, Configura ap instanceof AccessPathNil or exists(NodeExt mid | - localFlowBigStepExt(node, mid, true, config) and + localFlowBigStepExt(node, mid, true, _, config) and flow(mid, toReturn, ap, config) ) or exists(NodeExt mid, AccessPathNil nil | flowFwd(node, _, _, ap, config) and - localFlowBigStepExt(node, mid, false, config) and + localFlowBigStepExt(node, mid, false, _, config) and flow(mid, toReturn, nil, config) and ap instanceof AccessPathNil ) @@ -2164,7 +2136,7 @@ private newtype TPathNode = config.isSource(node) and cc instanceof CallContextAny and sc instanceof SummaryCtxNone and - ap = any(AccessPathNilNode nil | nil.getNode() = node).getAp() + ap = TNil(getErasedNodeTypeBound(node)) or // ... or a step from an existing PathNode to another node. exists(PathNodeMid mid | @@ -2357,12 +2329,11 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt pathIntoLocalStep(mid, midnode, cc, enclosing, sc, ap0, conf) and localCC = getLocalCallContext(cc, enclosing) | - localFlowBigStep(midnode, node, true, conf, localCC) and + localFlowBigStep(midnode, node, true, _, conf, localCC) and ap = ap0 or - localFlowBigStep(midnode, node, false, conf, localCC) and - ap0 instanceof AccessPathNil and - ap = any(AccessPathNilNode nil | nil.getNode() = node).getAp() + localFlowBigStep(midnode, node, false, ap.(AccessPathNil).getType(), conf, localCC) and + ap0 instanceof AccessPathNil ) or jumpStep(mid.getNode(), node, mid.getConfiguration()) and @@ -2374,7 +2345,7 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt cc instanceof CallContextAny and sc instanceof SummaryCtxNone and mid.getAp() instanceof AccessPathNil and - ap = any(AccessPathNilNode nil | nil.getNode() = node).getAp() + ap = TNil(getErasedNodeTypeBound(node)) or exists(Content f, AccessPath ap0 | pathReadStep(mid, node, ap0, f, cc) and ap = pop(f, ap0)) and sc = mid.getSummaryCtx() @@ -2397,7 +2368,7 @@ private predicate pathIntoLocalStep( midnode = mid.getNode() and cc = mid.getCallContext() and conf = mid.getConfiguration() and - localFlowBigStep(midnode, _, _, conf, _) and + localFlowBigStep(midnode, _, _, _, conf, _) and enclosing = midnode.getEnclosingCallable() and sc = mid.getSummaryCtx() and ap0 = mid.getAp() @@ -2949,7 +2920,7 @@ private module FlowExploration { config = mid.getConfiguration() } - pragma[noinline] + pragma[nomagic] private predicate partialPathOutOfCallable1( PartialPathNodePriv mid, DataFlowCall call, ReturnKindExt kind, CallContext cc, PartialAccessPath ap, Configuration config diff --git a/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowUtil.qll b/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowUtil.qll index b10997f3f18..e19912a63ee 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowUtil.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowUtil.qll @@ -131,10 +131,7 @@ class ExprNode extends InstructionNode { * `Conversion`, then the result is that `Conversion`'s non-`Conversion` base * expression. */ - Expr getExpr() { - result.getConversion*() = instr.getConvertedResultExpression() and - not result instanceof Conversion - } + Expr getExpr() { result = instr.getUnconvertedResultExpression() } /** * Gets the expression corresponding to this node, if any. The returned diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/Opcode.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/Opcode.qll index 886183c32ba..83c9ac1215c 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/implementation/Opcode.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/Opcode.qll @@ -82,6 +82,7 @@ private newtype TOpcode = TSizedBufferReadSideEffect() or TSizedBufferMustWriteSideEffect() or TSizedBufferMayWriteSideEffect() or + TInitializeDynamicAllocation() or TChi() or TInlineAsm() or TUnreached() or @@ -213,23 +214,28 @@ abstract class IndirectReadOpcode extends IndirectMemoryAccessOpcode { } /** - * An opcode that accesses a memory buffer of unknown size. + * An opcode that accesses a memory buffer. */ abstract class BufferAccessOpcode extends Opcode { final override predicate hasAddressOperand() { any() } } +/** + * An opcode that accesses a memory buffer of unknown size. + */ +abstract class UnsizedBufferAccessOpcode extends BufferAccessOpcode { } + /** * An opcode that writes to a memory buffer of unknown size. */ -abstract class BufferWriteOpcode extends BufferAccessOpcode { +abstract class UnsizedBufferWriteOpcode extends UnsizedBufferAccessOpcode { final override MemoryAccessKind getWriteMemoryAccess() { result instanceof BufferMemoryAccess } } /** * An opcode that reads from a memory buffer of unknown size. */ -abstract class BufferReadOpcode extends BufferAccessOpcode { +abstract class UnsizedBufferReadOpcode extends UnsizedBufferAccessOpcode { final override MemoryAccessKind getReadMemoryAccess() { result instanceof BufferMemoryAccess } } @@ -261,9 +267,7 @@ abstract class EntireAllocationReadOpcode extends EntireAllocationAccessOpcode { /** * An opcode that accesses a memory buffer whose size is determined by a `BufferSizeOperand`. */ -abstract class SizedBufferAccessOpcode extends Opcode { - final override predicate hasAddressOperand() { any() } - +abstract class SizedBufferAccessOpcode extends BufferAccessOpcode { final override predicate hasBufferSizeOperand() { any() } } @@ -666,17 +670,18 @@ module Opcode { final override string toString() { result = "IndirectMayWriteSideEffect" } } - class BufferReadSideEffect extends ReadSideEffectOpcode, BufferReadOpcode, TBufferReadSideEffect { + class BufferReadSideEffect extends ReadSideEffectOpcode, UnsizedBufferReadOpcode, + TBufferReadSideEffect { final override string toString() { result = "BufferReadSideEffect" } } - class BufferMustWriteSideEffect extends WriteSideEffectOpcode, BufferWriteOpcode, + class BufferMustWriteSideEffect extends WriteSideEffectOpcode, UnsizedBufferWriteOpcode, TBufferMustWriteSideEffect { final override string toString() { result = "BufferMustWriteSideEffect" } } - class BufferMayWriteSideEffect extends WriteSideEffectOpcode, BufferWriteOpcode, MayWriteOpcode, - TBufferMayWriteSideEffect { + class BufferMayWriteSideEffect extends WriteSideEffectOpcode, UnsizedBufferWriteOpcode, + MayWriteOpcode, TBufferMayWriteSideEffect { final override string toString() { result = "BufferMayWriteSideEffect" } } @@ -695,6 +700,11 @@ module Opcode { final override string toString() { result = "SizedBufferMayWriteSideEffect" } } + class InitializeDynamicAllocation extends SideEffectOpcode, EntireAllocationWriteOpcode, + TInitializeDynamicAllocation { + final override string toString() { result = "InitializeDynamicAllocation" } + } + class Chi extends Opcode, TChi { final override string toString() { result = "Chi" } diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/IRSanity.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/IRSanity.qll index de66a3c99dc..d07a99eab37 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/IRSanity.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/IRSanity.qll @@ -1,3 +1,275 @@ private import IR -import InstructionSanity -import IRTypeSanity +import InstructionSanity // module is below +import IRTypeSanity // module is in IRType.qll + +module InstructionSanity { + private import internal.InstructionImports as Imports + private import Imports::OperandTag + private import internal.IRInternal + + /** + * Holds if instruction `instr` is missing an expected operand with tag `tag`. + */ + query predicate missingOperand(Instruction instr, string message, IRFunction func, string funcText) { + exists(OperandTag tag | + instr.getOpcode().hasOperand(tag) and + not exists(NonPhiOperand operand | + operand = instr.getAnOperand() and + operand.getOperandTag() = tag + ) and + message = + "Instruction '" + instr.getOpcode().toString() + + "' is missing an expected operand with tag '" + tag.toString() + "' in function '$@'." and + func = instr.getEnclosingIRFunction() and + funcText = Language::getIdentityString(func.getFunction()) + ) + } + + /** + * Holds if instruction `instr` has an unexpected operand with tag `tag`. + */ + query predicate unexpectedOperand(Instruction instr, OperandTag tag) { + exists(NonPhiOperand operand | + operand = instr.getAnOperand() and + operand.getOperandTag() = tag + ) and + not instr.getOpcode().hasOperand(tag) and + not (instr instanceof CallInstruction and tag instanceof ArgumentOperandTag) and + not ( + instr instanceof BuiltInOperationInstruction and tag instanceof PositionalArgumentOperandTag + ) and + not (instr instanceof InlineAsmInstruction and tag instanceof AsmOperandTag) + } + + /** + * Holds if instruction `instr` has multiple operands with tag `tag`. + */ + query predicate duplicateOperand( + Instruction instr, string message, IRFunction func, string funcText + ) { + exists(OperandTag tag, int operandCount | + operandCount = + strictcount(NonPhiOperand operand | + operand = instr.getAnOperand() and + operand.getOperandTag() = tag + ) and + operandCount > 1 and + not tag instanceof UnmodeledUseOperandTag and + message = + "Instruction has " + operandCount + " operands with tag '" + tag.toString() + "'" + + " in function '$@'." and + func = instr.getEnclosingIRFunction() and + funcText = Language::getIdentityString(func.getFunction()) + ) + } + + /** + * Holds if `Phi` instruction `instr` is missing an operand corresponding to + * the predecessor block `pred`. + */ + query predicate missingPhiOperand(PhiInstruction instr, IRBlock pred) { + pred = instr.getBlock().getAPredecessor() and + not exists(PhiInputOperand operand | + operand = instr.getAnOperand() and + operand.getPredecessorBlock() = pred + ) + } + + query predicate missingOperandType(Operand operand, string message) { + exists(Language::Function func, Instruction use | + not exists(operand.getType()) and + use = operand.getUse() and + func = use.getEnclosingFunction() and + message = + "Operand '" + operand.toString() + "' of instruction '" + use.getOpcode().toString() + + "' missing type in function '" + Language::getIdentityString(func) + "'." + ) + } + + query predicate duplicateChiOperand( + ChiInstruction chi, string message, IRFunction func, string funcText + ) { + chi.getTotal() = chi.getPartial() and + message = + "Chi instruction for " + chi.getPartial().toString() + + " has duplicate operands in function $@" and + func = chi.getEnclosingIRFunction() and + funcText = Language::getIdentityString(func.getFunction()) + } + + query predicate sideEffectWithoutPrimary( + SideEffectInstruction instr, string message, IRFunction func, string funcText + ) { + not exists(instr.getPrimaryInstruction()) and + message = "Side effect instruction missing primary instruction in function $@" and + func = instr.getEnclosingIRFunction() and + funcText = Language::getIdentityString(func.getFunction()) + } + + /** + * Holds if an instruction, other than `ExitFunction`, has no successors. + */ + query predicate instructionWithoutSuccessor(Instruction instr) { + not exists(instr.getASuccessor()) and + not instr instanceof ExitFunctionInstruction and + // Phi instructions aren't linked into the instruction-level flow graph. + not instr instanceof PhiInstruction and + not instr instanceof UnreachedInstruction + } + + /** + * Holds if there are multiple (`n`) edges of kind `kind` from `source`, + * where `target` is among the targets of those edges. + */ + query predicate ambiguousSuccessors(Instruction source, EdgeKind kind, int n, Instruction target) { + n = strictcount(Instruction t | source.getSuccessor(kind) = t) and + n > 1 and + source.getSuccessor(kind) = target + } + + /** + * Holds if `instr` in `f` is part of a loop even though the AST of `f` + * contains no element that can cause loops. + */ + query predicate unexplainedLoop(Language::Function f, Instruction instr) { + exists(IRBlock block | + instr.getBlock() = block and + block.getEnclosingFunction() = f and + block.getASuccessor+() = block + ) and + not Language::hasPotentialLoop(f) + } + + /** + * Holds if a `Phi` instruction is present in a block with fewer than two + * predecessors. + */ + query predicate unnecessaryPhiInstruction(PhiInstruction instr) { + count(instr.getBlock().getAPredecessor()) < 2 + } + + /** + * Holds if operand `operand` consumes a value that was defined in + * a different function. + */ + query predicate operandAcrossFunctions(Operand operand, Instruction instr, Instruction defInstr) { + operand.getUse() = instr and + operand.getAnyDef() = defInstr and + instr.getEnclosingIRFunction() != defInstr.getEnclosingIRFunction() + } + + /** + * Holds if instruction `instr` is not in exactly one block. + */ + query predicate instructionWithoutUniqueBlock(Instruction instr, int blockCount) { + blockCount = count(instr.getBlock()) and + blockCount != 1 + } + + private predicate forwardEdge(IRBlock b1, IRBlock b2) { + b1.getASuccessor() = b2 and + not b1.getBackEdgeSuccessor(_) = b2 + } + + /** + * Holds if `f` contains a loop in which no edge is a back edge. + * + * This check ensures we don't have too _few_ back edges. + */ + query predicate containsLoopOfForwardEdges(IRFunction f) { + exists(IRBlock block | + forwardEdge+(block, block) and + block.getEnclosingIRFunction() = f + ) + } + + /** + * Holds if `block` is reachable from its function entry point but would not + * be reachable by traversing only forward edges. This check is skipped for + * functions containing `goto` statements as the property does not generally + * hold there. + * + * This check ensures we don't have too _many_ back edges. + */ + query predicate lostReachability(IRBlock block) { + exists(IRFunction f, IRBlock entry | + entry = f.getEntryBlock() and + entry.getASuccessor+() = block and + not forwardEdge+(entry, block) and + not Language::hasGoto(f.getFunction()) + ) + } + + /** + * Holds if the number of back edges differs between the `Instruction` graph + * and the `IRBlock` graph. + */ + query predicate backEdgeCountMismatch(Language::Function f, int fromInstr, int fromBlock) { + fromInstr = + count(Instruction i1, Instruction i2 | + i1.getEnclosingFunction() = f and i1.getBackEdgeSuccessor(_) = i2 + ) and + fromBlock = + count(IRBlock b1, IRBlock b2 | + b1.getEnclosingFunction() = f and b1.getBackEdgeSuccessor(_) = b2 + ) and + fromInstr != fromBlock + } + + /** + * Gets the point in the function at which the specified operand is evaluated. For most operands, + * this is at the instruction that consumes the use. For a `PhiInputOperand`, the effective point + * of evaluation is at the end of the corresponding predecessor block. + */ + private predicate pointOfEvaluation(Operand operand, IRBlock block, int index) { + block = operand.(PhiInputOperand).getPredecessorBlock() and + index = block.getInstructionCount() + or + exists(Instruction use | + use = operand.(NonPhiOperand).getUse() and + block.getInstruction(index) = use + ) + } + + /** + * Holds if `useOperand` has a definition that does not dominate the use. + */ + query predicate useNotDominatedByDefinition( + Operand useOperand, string message, IRFunction func, string funcText + ) { + exists(IRBlock useBlock, int useIndex, Instruction defInstr, IRBlock defBlock, int defIndex | + not useOperand.getUse() instanceof UnmodeledUseInstruction and + not defInstr instanceof UnmodeledDefinitionInstruction and + pointOfEvaluation(useOperand, useBlock, useIndex) and + defInstr = useOperand.getAnyDef() and + ( + defInstr instanceof PhiInstruction and + defBlock = defInstr.getBlock() and + defIndex = -1 + or + defBlock.getInstruction(defIndex) = defInstr + ) and + not ( + defBlock.strictlyDominates(useBlock) + or + defBlock = useBlock and + defIndex < useIndex + ) and + message = + "Operand '" + useOperand.toString() + + "' is not dominated by its definition in function '$@'." and + func = useOperand.getEnclosingIRFunction() and + 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/IRVariable.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/IRVariable.qll index ee7aebc4749..ae585ec2c7e 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/IRVariable.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/IRVariable.qll @@ -176,7 +176,7 @@ IRTempVariable getIRTempVariable(Language::AST ast, TempVariableTag tag) { /** * A temporary variable introduced by IR construction. The most common examples are the variable - * generated to hold the return value of afunction, or the variable generated to hold the result of + * generated to hold the return value of a function, or the variable generated to hold the result of * a condition operator (`a ? b : c`). */ class IRTempVariable extends IRGeneratedVariable, IRAutomaticVariable, TIRTempVariable { 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 52cf46a5f0e..472f1673f0d 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 @@ -10,274 +10,6 @@ import Imports::MemoryAccessKind import Imports::Opcode private import Imports::OperandTag -module InstructionSanity { - /** - * Holds if instruction `instr` is missing an expected operand with tag `tag`. - */ - query predicate missingOperand(Instruction instr, string message, IRFunction func, string funcText) { - exists(OperandTag tag | - instr.getOpcode().hasOperand(tag) and - not exists(NonPhiOperand operand | - operand = instr.getAnOperand() and - operand.getOperandTag() = tag - ) and - message = - "Instruction '" + instr.getOpcode().toString() + - "' is missing an expected operand with tag '" + tag.toString() + "' in function '$@'." and - func = instr.getEnclosingIRFunction() and - funcText = Language::getIdentityString(func.getFunction()) - ) - } - - /** - * Holds if instruction `instr` has an unexpected operand with tag `tag`. - */ - query predicate unexpectedOperand(Instruction instr, OperandTag tag) { - exists(NonPhiOperand operand | - operand = instr.getAnOperand() and - operand.getOperandTag() = tag - ) and - not instr.getOpcode().hasOperand(tag) and - not (instr instanceof CallInstruction and tag instanceof ArgumentOperandTag) and - not ( - instr instanceof BuiltInOperationInstruction and tag instanceof PositionalArgumentOperandTag - ) and - not (instr instanceof InlineAsmInstruction and tag instanceof AsmOperandTag) - } - - /** - * Holds if instruction `instr` has multiple operands with tag `tag`. - */ - query predicate duplicateOperand( - Instruction instr, string message, IRFunction func, string funcText - ) { - exists(OperandTag tag, int operandCount | - operandCount = - strictcount(NonPhiOperand operand | - operand = instr.getAnOperand() and - operand.getOperandTag() = tag - ) and - operandCount > 1 and - not tag instanceof UnmodeledUseOperandTag and - message = - "Instruction has " + operandCount + " operands with tag '" + tag.toString() + "'" + - " in function '$@'." and - func = instr.getEnclosingIRFunction() and - funcText = Language::getIdentityString(func.getFunction()) - ) - } - - /** - * Holds if `Phi` instruction `instr` is missing an operand corresponding to - * the predecessor block `pred`. - */ - query predicate missingPhiOperand(PhiInstruction instr, IRBlock pred) { - pred = instr.getBlock().getAPredecessor() and - not exists(PhiInputOperand operand | - operand = instr.getAnOperand() and - operand.getPredecessorBlock() = pred - ) - } - - query predicate missingOperandType(Operand operand, string message) { - exists(Language::Function func, Instruction use | - not exists(operand.getType()) and - use = operand.getUse() and - func = use.getEnclosingFunction() and - message = - "Operand '" + operand.toString() + "' of instruction '" + use.getOpcode().toString() + - "' missing type in function '" + Language::getIdentityString(func) + "'." - ) - } - - query predicate duplicateChiOperand( - ChiInstruction chi, string message, IRFunction func, string funcText - ) { - chi.getTotal() = chi.getPartial() and - message = - "Chi instruction for " + chi.getPartial().toString() + - " has duplicate operands in function $@" and - func = chi.getEnclosingIRFunction() and - funcText = Language::getIdentityString(func.getFunction()) - } - - query predicate sideEffectWithoutPrimary( - SideEffectInstruction instr, string message, IRFunction func, string funcText - ) { - not exists(instr.getPrimaryInstruction()) and - message = "Side effect instruction missing primary instruction in function $@" and - func = instr.getEnclosingIRFunction() and - funcText = Language::getIdentityString(func.getFunction()) - } - - /** - * Holds if an instruction, other than `ExitFunction`, has no successors. - */ - query predicate instructionWithoutSuccessor(Instruction instr) { - not exists(instr.getASuccessor()) and - not instr instanceof ExitFunctionInstruction and - // Phi instructions aren't linked into the instruction-level flow graph. - not instr instanceof PhiInstruction and - not instr instanceof UnreachedInstruction - } - - /** - * Holds if there are multiple (`n`) edges of kind `kind` from `source`, - * where `target` is among the targets of those edges. - */ - query predicate ambiguousSuccessors(Instruction source, EdgeKind kind, int n, Instruction target) { - n = strictcount(Instruction t | source.getSuccessor(kind) = t) and - n > 1 and - source.getSuccessor(kind) = target - } - - /** - * Holds if `instr` in `f` is part of a loop even though the AST of `f` - * contains no element that can cause loops. - */ - query predicate unexplainedLoop(Language::Function f, Instruction instr) { - exists(IRBlock block | - instr.getBlock() = block and - block.getEnclosingFunction() = f and - block.getASuccessor+() = block - ) and - not Language::hasPotentialLoop(f) - } - - /** - * Holds if a `Phi` instruction is present in a block with fewer than two - * predecessors. - */ - query predicate unnecessaryPhiInstruction(PhiInstruction instr) { - count(instr.getBlock().getAPredecessor()) < 2 - } - - /** - * Holds if operand `operand` consumes a value that was defined in - * a different function. - */ - query predicate operandAcrossFunctions(Operand operand, Instruction instr, Instruction defInstr) { - operand.getUse() = instr and - operand.getAnyDef() = defInstr and - instr.getEnclosingIRFunction() != defInstr.getEnclosingIRFunction() - } - - /** - * Holds if instruction `instr` is not in exactly one block. - */ - query predicate instructionWithoutUniqueBlock(Instruction instr, int blockCount) { - blockCount = count(instr.getBlock()) and - blockCount != 1 - } - - private predicate forwardEdge(IRBlock b1, IRBlock b2) { - b1.getASuccessor() = b2 and - not b1.getBackEdgeSuccessor(_) = b2 - } - - /** - * Holds if `f` contains a loop in which no edge is a back edge. - * - * This check ensures we don't have too _few_ back edges. - */ - query predicate containsLoopOfForwardEdges(IRFunction f) { - exists(IRBlock block | - forwardEdge+(block, block) and - block.getEnclosingIRFunction() = f - ) - } - - /** - * Holds if `block` is reachable from its function entry point but would not - * be reachable by traversing only forward edges. This check is skipped for - * functions containing `goto` statements as the property does not generally - * hold there. - * - * This check ensures we don't have too _many_ back edges. - */ - query predicate lostReachability(IRBlock block) { - exists(IRFunction f, IRBlock entry | - entry = f.getEntryBlock() and - entry.getASuccessor+() = block and - not forwardEdge+(entry, block) and - not Language::hasGoto(f.getFunction()) - ) - } - - /** - * Holds if the number of back edges differs between the `Instruction` graph - * and the `IRBlock` graph. - */ - query predicate backEdgeCountMismatch(Language::Function f, int fromInstr, int fromBlock) { - fromInstr = - count(Instruction i1, Instruction i2 | - i1.getEnclosingFunction() = f and i1.getBackEdgeSuccessor(_) = i2 - ) and - fromBlock = - count(IRBlock b1, IRBlock b2 | - b1.getEnclosingFunction() = f and b1.getBackEdgeSuccessor(_) = b2 - ) and - fromInstr != fromBlock - } - - /** - * Gets the point in the function at which the specified operand is evaluated. For most operands, - * this is at the instruction that consumes the use. For a `PhiInputOperand`, the effective point - * of evaluation is at the end of the corresponding predecessor block. - */ - private predicate pointOfEvaluation(Operand operand, IRBlock block, int index) { - block = operand.(PhiInputOperand).getPredecessorBlock() and - index = block.getInstructionCount() - or - exists(Instruction use | - use = operand.(NonPhiOperand).getUse() and - block.getInstruction(index) = use - ) - } - - /** - * Holds if `useOperand` has a definition that does not dominate the use. - */ - query predicate useNotDominatedByDefinition( - Operand useOperand, string message, IRFunction func, string funcText - ) { - exists(IRBlock useBlock, int useIndex, Instruction defInstr, IRBlock defBlock, int defIndex | - not useOperand.getUse() instanceof UnmodeledUseInstruction and - not defInstr instanceof UnmodeledDefinitionInstruction and - pointOfEvaluation(useOperand, useBlock, useIndex) and - defInstr = useOperand.getAnyDef() and - ( - defInstr instanceof PhiInstruction and - defBlock = defInstr.getBlock() and - defIndex = -1 - or - defBlock.getInstruction(defIndex) = defInstr - ) and - not ( - defBlock.strictlyDominates(useBlock) - or - defBlock = useBlock and - defIndex < useIndex - ) and - message = - "Operand '" + useOperand.toString() + - "' is not dominated by its definition in function '$@'." and - func = useOperand.getEnclosingIRFunction() and - 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()) - } -} - /** * Gets an `Instruction` that is contained in `IRFunction`, and has a location with the specified * `File` and line number. Used for assigning register names when printing IR. @@ -1204,6 +936,11 @@ class CallInstruction extends Instruction { final Instruction getPositionalArgument(int index) { result = getPositionalArgumentOperand(index).getDef() } + + /** + * Gets the number of arguments of the call, including the `this` pointer, if any. + */ + final int getNumberOfArguments() { result = count(this.getAnArgumentOperand()) } } /** @@ -1352,6 +1089,26 @@ class SizedBufferMayWriteSideEffectInstruction extends WriteSideEffectInstructio Instruction getSizeDef() { result = getAnOperand().(BufferSizeOperand).getDef() } } +/** + * An instruction representing the initial value of newly allocated memory, e.g. the result of a + * call to `malloc`. + */ +class InitializeDynamicAllocationInstruction extends SideEffectInstruction { + InitializeDynamicAllocationInstruction() { + getOpcode() instanceof Opcode::InitializeDynamicAllocation + } + + /** + * Gets the address of the allocation this instruction is initializing. + */ + final AddressOperand getAllocationAddressOperand() { result = getAnOperand() } + + /** + * Gets the operand for the allocation this instruction is initializing. + */ + final Instruction getAllocationAddress() { result = getAllocationAddressOperand().getDef() } +} + /** * An instruction representing a GNU or MSVC inline assembly statement. */ diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/AliasConfiguration.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/AliasConfiguration.qll index d9937294d70..e95086c89fc 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/AliasConfiguration.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/AliasConfiguration.qll @@ -7,6 +7,9 @@ private newtype TAllocation = TVariableAllocation(IRVariable var) or TIndirectParameterAllocation(IRAutomaticUserVariable var) { exists(InitializeIndirectionInstruction instr | instr.getIRVariable() = var) + } or + TDynamicAllocation(CallInstruction call) { + exists(InitializeDynamicAllocationInstruction instr | instr.getPrimaryInstruction() = call) } /** @@ -95,3 +98,29 @@ class IndirectParameterAllocation extends Allocation, TIndirectParameterAllocati final override predicate alwaysEscapes() { none() } } + +class DynamicAllocation extends Allocation, TDynamicAllocation { + CallInstruction call; + + DynamicAllocation() { this = TDynamicAllocation(call) } + + final override string toString() { + result = call.toString() + " at " + call.getLocation() // This isn't performant, but it's only used in test/dump code right now. + } + + final override CallInstruction getABaseInstruction() { result = call } + + final override IRFunction getEnclosingIRFunction() { result = call.getEnclosingIRFunction() } + + final override Language::Location getLocation() { result = call.getLocation() } + + final override string getUniqueId() { result = call.getUniqueId() } + + final override IRType getIRType() { result instanceof IRUnknownType } + + final override predicate isReadOnly() { none() } + + final override predicate isAlwaysAllocatedOnStack() { none() } + + final override predicate alwaysEscapes() { none() } +} diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/AliasedSSA.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/AliasedSSA.qll index c65570c5999..e754e8d3849 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/AliasedSSA.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/AliasedSSA.qll @@ -26,7 +26,7 @@ private predicate hasResultMemoryAccess( type = languageType.getIRType() and isIndirectOrBufferMemoryAccess(instr.getResultMemoryAccess()) and (if instr.hasResultMayMemoryAccess() then isMayAccess = true else isMayAccess = false) and - if type.getByteSize() > 0 + if exists(type.getByteSize()) then endBitOffset = Ints::add(startBitOffset, Ints::mul(type.getByteSize(), 8)) else endBitOffset = Ints::unknown() ) @@ -43,7 +43,7 @@ private predicate hasOperandMemoryAccess( type = languageType.getIRType() and isIndirectOrBufferMemoryAccess(operand.getMemoryAccess()) and (if operand.hasMayReadMemoryAccess() then isMayAccess = true else isMayAccess = false) and - if type.getByteSize() > 0 + if exists(type.getByteSize()) then endBitOffset = Ints::add(startBitOffset, Ints::mul(type.getByteSize(), 8)) else endBitOffset = Ints::unknown() ) @@ -68,8 +68,12 @@ private newtype TMemoryLocation = ) and languageType = type.getCanonicalLanguageType() } or - TEntireAllocationMemoryLocation(IndirectParameterAllocation var, boolean isMayAccess) { - isMayAccess = false or isMayAccess = true + TEntireAllocationMemoryLocation(Allocation var, boolean isMayAccess) { + ( + var instanceof IndirectParameterAllocation or + var instanceof DynamicAllocation + ) and + (isMayAccess = false or isMayAccess = true) } or TUnknownMemoryLocation(IRFunction irFunc, boolean isMayAccess) { isMayAccess = false or isMayAccess = true diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/SSAConstruction.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/SSAConstruction.qll index 62753e298cd..6578968b1ff 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/SSAConstruction.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/SSAConstruction.qll @@ -665,17 +665,18 @@ module DefUse { private predicate definitionReachesRank( Alias::MemoryLocation useLocation, OldBlock block, int defRank, int reachesRank ) { + // The def always reaches the next use, even if there is also a def on the + // use instruction. hasDefinitionAtRank(useLocation, _, block, defRank, _) and - reachesRank <= exitRank(useLocation, block) and // Without this, the predicate would be infinite. - ( - // The def always reaches the next use, even if there is also a def on the - // use instruction. - reachesRank = defRank + 1 - or - // If the def reached the previous rank, it also reaches the current rank, - // unless there was another def at the previous rank. - definitionReachesRank(useLocation, block, defRank, reachesRank - 1) and - not hasDefinitionAtRank(useLocation, _, block, reachesRank - 1, _) + reachesRank = defRank + 1 + or + // If the def reached the previous rank, it also reaches the current rank, + // unless there was another def at the previous rank. + exists(int prevRank | + reachesRank = prevRank + 1 and + definitionReachesRank(useLocation, block, defRank, prevRank) and + not prevRank = exitRank(useLocation, block) and + not hasDefinitionAtRank(useLocation, _, block, prevRank, _) ) } diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/IRSanity.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/IRSanity.qll index de66a3c99dc..d07a99eab37 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/IRSanity.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/IRSanity.qll @@ -1,3 +1,275 @@ private import IR -import InstructionSanity -import IRTypeSanity +import InstructionSanity // module is below +import IRTypeSanity // module is in IRType.qll + +module InstructionSanity { + private import internal.InstructionImports as Imports + private import Imports::OperandTag + private import internal.IRInternal + + /** + * Holds if instruction `instr` is missing an expected operand with tag `tag`. + */ + query predicate missingOperand(Instruction instr, string message, IRFunction func, string funcText) { + exists(OperandTag tag | + instr.getOpcode().hasOperand(tag) and + not exists(NonPhiOperand operand | + operand = instr.getAnOperand() and + operand.getOperandTag() = tag + ) and + message = + "Instruction '" + instr.getOpcode().toString() + + "' is missing an expected operand with tag '" + tag.toString() + "' in function '$@'." and + func = instr.getEnclosingIRFunction() and + funcText = Language::getIdentityString(func.getFunction()) + ) + } + + /** + * Holds if instruction `instr` has an unexpected operand with tag `tag`. + */ + query predicate unexpectedOperand(Instruction instr, OperandTag tag) { + exists(NonPhiOperand operand | + operand = instr.getAnOperand() and + operand.getOperandTag() = tag + ) and + not instr.getOpcode().hasOperand(tag) and + not (instr instanceof CallInstruction and tag instanceof ArgumentOperandTag) and + not ( + instr instanceof BuiltInOperationInstruction and tag instanceof PositionalArgumentOperandTag + ) and + not (instr instanceof InlineAsmInstruction and tag instanceof AsmOperandTag) + } + + /** + * Holds if instruction `instr` has multiple operands with tag `tag`. + */ + query predicate duplicateOperand( + Instruction instr, string message, IRFunction func, string funcText + ) { + exists(OperandTag tag, int operandCount | + operandCount = + strictcount(NonPhiOperand operand | + operand = instr.getAnOperand() and + operand.getOperandTag() = tag + ) and + operandCount > 1 and + not tag instanceof UnmodeledUseOperandTag and + message = + "Instruction has " + operandCount + " operands with tag '" + tag.toString() + "'" + + " in function '$@'." and + func = instr.getEnclosingIRFunction() and + funcText = Language::getIdentityString(func.getFunction()) + ) + } + + /** + * Holds if `Phi` instruction `instr` is missing an operand corresponding to + * the predecessor block `pred`. + */ + query predicate missingPhiOperand(PhiInstruction instr, IRBlock pred) { + pred = instr.getBlock().getAPredecessor() and + not exists(PhiInputOperand operand | + operand = instr.getAnOperand() and + operand.getPredecessorBlock() = pred + ) + } + + query predicate missingOperandType(Operand operand, string message) { + exists(Language::Function func, Instruction use | + not exists(operand.getType()) and + use = operand.getUse() and + func = use.getEnclosingFunction() and + message = + "Operand '" + operand.toString() + "' of instruction '" + use.getOpcode().toString() + + "' missing type in function '" + Language::getIdentityString(func) + "'." + ) + } + + query predicate duplicateChiOperand( + ChiInstruction chi, string message, IRFunction func, string funcText + ) { + chi.getTotal() = chi.getPartial() and + message = + "Chi instruction for " + chi.getPartial().toString() + + " has duplicate operands in function $@" and + func = chi.getEnclosingIRFunction() and + funcText = Language::getIdentityString(func.getFunction()) + } + + query predicate sideEffectWithoutPrimary( + SideEffectInstruction instr, string message, IRFunction func, string funcText + ) { + not exists(instr.getPrimaryInstruction()) and + message = "Side effect instruction missing primary instruction in function $@" and + func = instr.getEnclosingIRFunction() and + funcText = Language::getIdentityString(func.getFunction()) + } + + /** + * Holds if an instruction, other than `ExitFunction`, has no successors. + */ + query predicate instructionWithoutSuccessor(Instruction instr) { + not exists(instr.getASuccessor()) and + not instr instanceof ExitFunctionInstruction and + // Phi instructions aren't linked into the instruction-level flow graph. + not instr instanceof PhiInstruction and + not instr instanceof UnreachedInstruction + } + + /** + * Holds if there are multiple (`n`) edges of kind `kind` from `source`, + * where `target` is among the targets of those edges. + */ + query predicate ambiguousSuccessors(Instruction source, EdgeKind kind, int n, Instruction target) { + n = strictcount(Instruction t | source.getSuccessor(kind) = t) and + n > 1 and + source.getSuccessor(kind) = target + } + + /** + * Holds if `instr` in `f` is part of a loop even though the AST of `f` + * contains no element that can cause loops. + */ + query predicate unexplainedLoop(Language::Function f, Instruction instr) { + exists(IRBlock block | + instr.getBlock() = block and + block.getEnclosingFunction() = f and + block.getASuccessor+() = block + ) and + not Language::hasPotentialLoop(f) + } + + /** + * Holds if a `Phi` instruction is present in a block with fewer than two + * predecessors. + */ + query predicate unnecessaryPhiInstruction(PhiInstruction instr) { + count(instr.getBlock().getAPredecessor()) < 2 + } + + /** + * Holds if operand `operand` consumes a value that was defined in + * a different function. + */ + query predicate operandAcrossFunctions(Operand operand, Instruction instr, Instruction defInstr) { + operand.getUse() = instr and + operand.getAnyDef() = defInstr and + instr.getEnclosingIRFunction() != defInstr.getEnclosingIRFunction() + } + + /** + * Holds if instruction `instr` is not in exactly one block. + */ + query predicate instructionWithoutUniqueBlock(Instruction instr, int blockCount) { + blockCount = count(instr.getBlock()) and + blockCount != 1 + } + + private predicate forwardEdge(IRBlock b1, IRBlock b2) { + b1.getASuccessor() = b2 and + not b1.getBackEdgeSuccessor(_) = b2 + } + + /** + * Holds if `f` contains a loop in which no edge is a back edge. + * + * This check ensures we don't have too _few_ back edges. + */ + query predicate containsLoopOfForwardEdges(IRFunction f) { + exists(IRBlock block | + forwardEdge+(block, block) and + block.getEnclosingIRFunction() = f + ) + } + + /** + * Holds if `block` is reachable from its function entry point but would not + * be reachable by traversing only forward edges. This check is skipped for + * functions containing `goto` statements as the property does not generally + * hold there. + * + * This check ensures we don't have too _many_ back edges. + */ + query predicate lostReachability(IRBlock block) { + exists(IRFunction f, IRBlock entry | + entry = f.getEntryBlock() and + entry.getASuccessor+() = block and + not forwardEdge+(entry, block) and + not Language::hasGoto(f.getFunction()) + ) + } + + /** + * Holds if the number of back edges differs between the `Instruction` graph + * and the `IRBlock` graph. + */ + query predicate backEdgeCountMismatch(Language::Function f, int fromInstr, int fromBlock) { + fromInstr = + count(Instruction i1, Instruction i2 | + i1.getEnclosingFunction() = f and i1.getBackEdgeSuccessor(_) = i2 + ) and + fromBlock = + count(IRBlock b1, IRBlock b2 | + b1.getEnclosingFunction() = f and b1.getBackEdgeSuccessor(_) = b2 + ) and + fromInstr != fromBlock + } + + /** + * Gets the point in the function at which the specified operand is evaluated. For most operands, + * this is at the instruction that consumes the use. For a `PhiInputOperand`, the effective point + * of evaluation is at the end of the corresponding predecessor block. + */ + private predicate pointOfEvaluation(Operand operand, IRBlock block, int index) { + block = operand.(PhiInputOperand).getPredecessorBlock() and + index = block.getInstructionCount() + or + exists(Instruction use | + use = operand.(NonPhiOperand).getUse() and + block.getInstruction(index) = use + ) + } + + /** + * Holds if `useOperand` has a definition that does not dominate the use. + */ + query predicate useNotDominatedByDefinition( + Operand useOperand, string message, IRFunction func, string funcText + ) { + exists(IRBlock useBlock, int useIndex, Instruction defInstr, IRBlock defBlock, int defIndex | + not useOperand.getUse() instanceof UnmodeledUseInstruction and + not defInstr instanceof UnmodeledDefinitionInstruction and + pointOfEvaluation(useOperand, useBlock, useIndex) and + defInstr = useOperand.getAnyDef() and + ( + defInstr instanceof PhiInstruction and + defBlock = defInstr.getBlock() and + defIndex = -1 + or + defBlock.getInstruction(defIndex) = defInstr + ) and + not ( + defBlock.strictlyDominates(useBlock) + or + defBlock = useBlock and + defIndex < useIndex + ) and + message = + "Operand '" + useOperand.toString() + + "' is not dominated by its definition in function '$@'." and + func = useOperand.getEnclosingIRFunction() and + 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/IRVariable.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/IRVariable.qll index ee7aebc4749..ae585ec2c7e 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/IRVariable.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/IRVariable.qll @@ -176,7 +176,7 @@ IRTempVariable getIRTempVariable(Language::AST ast, TempVariableTag tag) { /** * A temporary variable introduced by IR construction. The most common examples are the variable - * generated to hold the return value of afunction, or the variable generated to hold the result of + * generated to hold the return value of a function, or the variable generated to hold the result of * a condition operator (`a ? b : c`). */ class IRTempVariable extends IRGeneratedVariable, IRAutomaticVariable, TIRTempVariable { 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 52cf46a5f0e..472f1673f0d 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 @@ -10,274 +10,6 @@ import Imports::MemoryAccessKind import Imports::Opcode private import Imports::OperandTag -module InstructionSanity { - /** - * Holds if instruction `instr` is missing an expected operand with tag `tag`. - */ - query predicate missingOperand(Instruction instr, string message, IRFunction func, string funcText) { - exists(OperandTag tag | - instr.getOpcode().hasOperand(tag) and - not exists(NonPhiOperand operand | - operand = instr.getAnOperand() and - operand.getOperandTag() = tag - ) and - message = - "Instruction '" + instr.getOpcode().toString() + - "' is missing an expected operand with tag '" + tag.toString() + "' in function '$@'." and - func = instr.getEnclosingIRFunction() and - funcText = Language::getIdentityString(func.getFunction()) - ) - } - - /** - * Holds if instruction `instr` has an unexpected operand with tag `tag`. - */ - query predicate unexpectedOperand(Instruction instr, OperandTag tag) { - exists(NonPhiOperand operand | - operand = instr.getAnOperand() and - operand.getOperandTag() = tag - ) and - not instr.getOpcode().hasOperand(tag) and - not (instr instanceof CallInstruction and tag instanceof ArgumentOperandTag) and - not ( - instr instanceof BuiltInOperationInstruction and tag instanceof PositionalArgumentOperandTag - ) and - not (instr instanceof InlineAsmInstruction and tag instanceof AsmOperandTag) - } - - /** - * Holds if instruction `instr` has multiple operands with tag `tag`. - */ - query predicate duplicateOperand( - Instruction instr, string message, IRFunction func, string funcText - ) { - exists(OperandTag tag, int operandCount | - operandCount = - strictcount(NonPhiOperand operand | - operand = instr.getAnOperand() and - operand.getOperandTag() = tag - ) and - operandCount > 1 and - not tag instanceof UnmodeledUseOperandTag and - message = - "Instruction has " + operandCount + " operands with tag '" + tag.toString() + "'" + - " in function '$@'." and - func = instr.getEnclosingIRFunction() and - funcText = Language::getIdentityString(func.getFunction()) - ) - } - - /** - * Holds if `Phi` instruction `instr` is missing an operand corresponding to - * the predecessor block `pred`. - */ - query predicate missingPhiOperand(PhiInstruction instr, IRBlock pred) { - pred = instr.getBlock().getAPredecessor() and - not exists(PhiInputOperand operand | - operand = instr.getAnOperand() and - operand.getPredecessorBlock() = pred - ) - } - - query predicate missingOperandType(Operand operand, string message) { - exists(Language::Function func, Instruction use | - not exists(operand.getType()) and - use = operand.getUse() and - func = use.getEnclosingFunction() and - message = - "Operand '" + operand.toString() + "' of instruction '" + use.getOpcode().toString() + - "' missing type in function '" + Language::getIdentityString(func) + "'." - ) - } - - query predicate duplicateChiOperand( - ChiInstruction chi, string message, IRFunction func, string funcText - ) { - chi.getTotal() = chi.getPartial() and - message = - "Chi instruction for " + chi.getPartial().toString() + - " has duplicate operands in function $@" and - func = chi.getEnclosingIRFunction() and - funcText = Language::getIdentityString(func.getFunction()) - } - - query predicate sideEffectWithoutPrimary( - SideEffectInstruction instr, string message, IRFunction func, string funcText - ) { - not exists(instr.getPrimaryInstruction()) and - message = "Side effect instruction missing primary instruction in function $@" and - func = instr.getEnclosingIRFunction() and - funcText = Language::getIdentityString(func.getFunction()) - } - - /** - * Holds if an instruction, other than `ExitFunction`, has no successors. - */ - query predicate instructionWithoutSuccessor(Instruction instr) { - not exists(instr.getASuccessor()) and - not instr instanceof ExitFunctionInstruction and - // Phi instructions aren't linked into the instruction-level flow graph. - not instr instanceof PhiInstruction and - not instr instanceof UnreachedInstruction - } - - /** - * Holds if there are multiple (`n`) edges of kind `kind` from `source`, - * where `target` is among the targets of those edges. - */ - query predicate ambiguousSuccessors(Instruction source, EdgeKind kind, int n, Instruction target) { - n = strictcount(Instruction t | source.getSuccessor(kind) = t) and - n > 1 and - source.getSuccessor(kind) = target - } - - /** - * Holds if `instr` in `f` is part of a loop even though the AST of `f` - * contains no element that can cause loops. - */ - query predicate unexplainedLoop(Language::Function f, Instruction instr) { - exists(IRBlock block | - instr.getBlock() = block and - block.getEnclosingFunction() = f and - block.getASuccessor+() = block - ) and - not Language::hasPotentialLoop(f) - } - - /** - * Holds if a `Phi` instruction is present in a block with fewer than two - * predecessors. - */ - query predicate unnecessaryPhiInstruction(PhiInstruction instr) { - count(instr.getBlock().getAPredecessor()) < 2 - } - - /** - * Holds if operand `operand` consumes a value that was defined in - * a different function. - */ - query predicate operandAcrossFunctions(Operand operand, Instruction instr, Instruction defInstr) { - operand.getUse() = instr and - operand.getAnyDef() = defInstr and - instr.getEnclosingIRFunction() != defInstr.getEnclosingIRFunction() - } - - /** - * Holds if instruction `instr` is not in exactly one block. - */ - query predicate instructionWithoutUniqueBlock(Instruction instr, int blockCount) { - blockCount = count(instr.getBlock()) and - blockCount != 1 - } - - private predicate forwardEdge(IRBlock b1, IRBlock b2) { - b1.getASuccessor() = b2 and - not b1.getBackEdgeSuccessor(_) = b2 - } - - /** - * Holds if `f` contains a loop in which no edge is a back edge. - * - * This check ensures we don't have too _few_ back edges. - */ - query predicate containsLoopOfForwardEdges(IRFunction f) { - exists(IRBlock block | - forwardEdge+(block, block) and - block.getEnclosingIRFunction() = f - ) - } - - /** - * Holds if `block` is reachable from its function entry point but would not - * be reachable by traversing only forward edges. This check is skipped for - * functions containing `goto` statements as the property does not generally - * hold there. - * - * This check ensures we don't have too _many_ back edges. - */ - query predicate lostReachability(IRBlock block) { - exists(IRFunction f, IRBlock entry | - entry = f.getEntryBlock() and - entry.getASuccessor+() = block and - not forwardEdge+(entry, block) and - not Language::hasGoto(f.getFunction()) - ) - } - - /** - * Holds if the number of back edges differs between the `Instruction` graph - * and the `IRBlock` graph. - */ - query predicate backEdgeCountMismatch(Language::Function f, int fromInstr, int fromBlock) { - fromInstr = - count(Instruction i1, Instruction i2 | - i1.getEnclosingFunction() = f and i1.getBackEdgeSuccessor(_) = i2 - ) and - fromBlock = - count(IRBlock b1, IRBlock b2 | - b1.getEnclosingFunction() = f and b1.getBackEdgeSuccessor(_) = b2 - ) and - fromInstr != fromBlock - } - - /** - * Gets the point in the function at which the specified operand is evaluated. For most operands, - * this is at the instruction that consumes the use. For a `PhiInputOperand`, the effective point - * of evaluation is at the end of the corresponding predecessor block. - */ - private predicate pointOfEvaluation(Operand operand, IRBlock block, int index) { - block = operand.(PhiInputOperand).getPredecessorBlock() and - index = block.getInstructionCount() - or - exists(Instruction use | - use = operand.(NonPhiOperand).getUse() and - block.getInstruction(index) = use - ) - } - - /** - * Holds if `useOperand` has a definition that does not dominate the use. - */ - query predicate useNotDominatedByDefinition( - Operand useOperand, string message, IRFunction func, string funcText - ) { - exists(IRBlock useBlock, int useIndex, Instruction defInstr, IRBlock defBlock, int defIndex | - not useOperand.getUse() instanceof UnmodeledUseInstruction and - not defInstr instanceof UnmodeledDefinitionInstruction and - pointOfEvaluation(useOperand, useBlock, useIndex) and - defInstr = useOperand.getAnyDef() and - ( - defInstr instanceof PhiInstruction and - defBlock = defInstr.getBlock() and - defIndex = -1 - or - defBlock.getInstruction(defIndex) = defInstr - ) and - not ( - defBlock.strictlyDominates(useBlock) - or - defBlock = useBlock and - defIndex < useIndex - ) and - message = - "Operand '" + useOperand.toString() + - "' is not dominated by its definition in function '$@'." and - func = useOperand.getEnclosingIRFunction() and - 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()) - } -} - /** * Gets an `Instruction` that is contained in `IRFunction`, and has a location with the specified * `File` and line number. Used for assigning register names when printing IR. @@ -1204,6 +936,11 @@ class CallInstruction extends Instruction { final Instruction getPositionalArgument(int index) { result = getPositionalArgumentOperand(index).getDef() } + + /** + * Gets the number of arguments of the call, including the `this` pointer, if any. + */ + final int getNumberOfArguments() { result = count(this.getAnArgumentOperand()) } } /** @@ -1352,6 +1089,26 @@ class SizedBufferMayWriteSideEffectInstruction extends WriteSideEffectInstructio Instruction getSizeDef() { result = getAnOperand().(BufferSizeOperand).getDef() } } +/** + * An instruction representing the initial value of newly allocated memory, e.g. the result of a + * call to `malloc`. + */ +class InitializeDynamicAllocationInstruction extends SideEffectInstruction { + InitializeDynamicAllocationInstruction() { + getOpcode() instanceof Opcode::InitializeDynamicAllocation + } + + /** + * Gets the address of the allocation this instruction is initializing. + */ + final AddressOperand getAllocationAddressOperand() { result = getAnOperand() } + + /** + * Gets the operand for the allocation this instruction is initializing. + */ + final Instruction getAllocationAddress() { result = getAllocationAddressOperand().getDef() } +} + /** * An instruction representing a GNU or MSVC inline assembly statement. */ 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 96ae480405a..dbb27cd1bca 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 @@ -68,14 +68,7 @@ private module Cached { cached Expr getInstructionUnconvertedResultExpression(Instruction instruction) { - exists(Expr converted | - result = converted.(Conversion).getExpr+() - or - result = converted - | - not result instanceof Conversion and - converted = getInstructionConvertedResultExpression(instruction) - ) + result = getInstructionConvertedResultExpression(instruction).getUnconverted() } cached diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/internal/TranslatedCall.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/internal/TranslatedCall.qll index 93e5c43bf3b..fb7dc173812 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/internal/TranslatedCall.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/internal/TranslatedCall.qll @@ -341,16 +341,32 @@ class TranslatedSideEffects extends TranslatedElement, TTranslatedSideEffects { ) } - override predicate hasInstruction(Opcode opcode, InstructionTag tag, CppType type) { none() } + override predicate hasInstruction(Opcode opcode, InstructionTag tag, CppType type) { + expr.getTarget() instanceof AllocationFunction and + opcode instanceof Opcode::InitializeDynamicAllocation and + tag = OnlyInstructionTag() and + type = getUnknownType() + } - override Instruction getFirstInstruction() { result = getChild(0).getFirstInstruction() } + override Instruction getFirstInstruction() { + if expr.getTarget() instanceof AllocationFunction + then result = getInstruction(OnlyInstructionTag()) + else result = getChild(0).getFirstInstruction() + } - override Instruction getInstructionSuccessor(InstructionTag tag, EdgeKind kind) { none() } + override Instruction getInstructionSuccessor(InstructionTag tag, EdgeKind kind) { + tag = OnlyInstructionTag() and + kind = gotoEdge() and + expr.getTarget() instanceof AllocationFunction and + if exists(getChild(0)) + then result = getChild(0).getFirstInstruction() + else result = getParent().getChildSuccessor(this) + } - override Instruction getInstructionOperand(InstructionTag tag, OperandTag operandTag) { none() } - - override CppType getInstructionOperandType(InstructionTag tag, TypedOperandTag operandTag) { - none() + override Instruction getInstructionOperand(InstructionTag tag, OperandTag operandTag) { + tag = OnlyInstructionTag() and + operandTag = addressOperand() and + result = getPrimaryInstructionForSideEffect(OnlyInstructionTag()) } override Instruction getPrimaryInstructionForSideEffect(InstructionTag tag) { @@ -487,7 +503,7 @@ class TranslatedSideEffect extends TranslatedElement, TTranslatedArgumentSideEff } override CppType getInstructionOperandType(InstructionTag tag, TypedOperandTag operandTag) { - if hasSpecificReadSideEffect(any(Opcode::BufferReadSideEffect op)) + if hasSpecificReadSideEffect(any(BufferAccessOpcode op)) then result = getUnknownType() and tag instanceof OnlyInstructionTag and diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/internal/TranslatedElement.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/internal/TranslatedElement.qll index 64dc7e869c2..81abc58495d 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/internal/TranslatedElement.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/internal/TranslatedElement.qll @@ -411,7 +411,9 @@ newtype TTranslatedElement = TTranslatedConditionDecl(ConditionDeclExpr expr) { not ignoreExpr(expr) } or // The side effects of a `Call` TTranslatedSideEffects(Call expr) { - exists(TTranslatedArgumentSideEffect(expr, _, _, _)) or expr instanceof ConstructorCall + exists(TTranslatedArgumentSideEffect(expr, _, _, _)) or + expr instanceof ConstructorCall or + expr.getTarget() instanceof AllocationFunction } or // A precise side effect of an argument to a `Call` TTranslatedArgumentSideEffect(Call call, Expr expr, int n, boolean isWrite) { ( diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/IRSanity.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/IRSanity.qll index de66a3c99dc..d07a99eab37 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/IRSanity.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/IRSanity.qll @@ -1,3 +1,275 @@ private import IR -import InstructionSanity -import IRTypeSanity +import InstructionSanity // module is below +import IRTypeSanity // module is in IRType.qll + +module InstructionSanity { + private import internal.InstructionImports as Imports + private import Imports::OperandTag + private import internal.IRInternal + + /** + * Holds if instruction `instr` is missing an expected operand with tag `tag`. + */ + query predicate missingOperand(Instruction instr, string message, IRFunction func, string funcText) { + exists(OperandTag tag | + instr.getOpcode().hasOperand(tag) and + not exists(NonPhiOperand operand | + operand = instr.getAnOperand() and + operand.getOperandTag() = tag + ) and + message = + "Instruction '" + instr.getOpcode().toString() + + "' is missing an expected operand with tag '" + tag.toString() + "' in function '$@'." and + func = instr.getEnclosingIRFunction() and + funcText = Language::getIdentityString(func.getFunction()) + ) + } + + /** + * Holds if instruction `instr` has an unexpected operand with tag `tag`. + */ + query predicate unexpectedOperand(Instruction instr, OperandTag tag) { + exists(NonPhiOperand operand | + operand = instr.getAnOperand() and + operand.getOperandTag() = tag + ) and + not instr.getOpcode().hasOperand(tag) and + not (instr instanceof CallInstruction and tag instanceof ArgumentOperandTag) and + not ( + instr instanceof BuiltInOperationInstruction and tag instanceof PositionalArgumentOperandTag + ) and + not (instr instanceof InlineAsmInstruction and tag instanceof AsmOperandTag) + } + + /** + * Holds if instruction `instr` has multiple operands with tag `tag`. + */ + query predicate duplicateOperand( + Instruction instr, string message, IRFunction func, string funcText + ) { + exists(OperandTag tag, int operandCount | + operandCount = + strictcount(NonPhiOperand operand | + operand = instr.getAnOperand() and + operand.getOperandTag() = tag + ) and + operandCount > 1 and + not tag instanceof UnmodeledUseOperandTag and + message = + "Instruction has " + operandCount + " operands with tag '" + tag.toString() + "'" + + " in function '$@'." and + func = instr.getEnclosingIRFunction() and + funcText = Language::getIdentityString(func.getFunction()) + ) + } + + /** + * Holds if `Phi` instruction `instr` is missing an operand corresponding to + * the predecessor block `pred`. + */ + query predicate missingPhiOperand(PhiInstruction instr, IRBlock pred) { + pred = instr.getBlock().getAPredecessor() and + not exists(PhiInputOperand operand | + operand = instr.getAnOperand() and + operand.getPredecessorBlock() = pred + ) + } + + query predicate missingOperandType(Operand operand, string message) { + exists(Language::Function func, Instruction use | + not exists(operand.getType()) and + use = operand.getUse() and + func = use.getEnclosingFunction() and + message = + "Operand '" + operand.toString() + "' of instruction '" + use.getOpcode().toString() + + "' missing type in function '" + Language::getIdentityString(func) + "'." + ) + } + + query predicate duplicateChiOperand( + ChiInstruction chi, string message, IRFunction func, string funcText + ) { + chi.getTotal() = chi.getPartial() and + message = + "Chi instruction for " + chi.getPartial().toString() + + " has duplicate operands in function $@" and + func = chi.getEnclosingIRFunction() and + funcText = Language::getIdentityString(func.getFunction()) + } + + query predicate sideEffectWithoutPrimary( + SideEffectInstruction instr, string message, IRFunction func, string funcText + ) { + not exists(instr.getPrimaryInstruction()) and + message = "Side effect instruction missing primary instruction in function $@" and + func = instr.getEnclosingIRFunction() and + funcText = Language::getIdentityString(func.getFunction()) + } + + /** + * Holds if an instruction, other than `ExitFunction`, has no successors. + */ + query predicate instructionWithoutSuccessor(Instruction instr) { + not exists(instr.getASuccessor()) and + not instr instanceof ExitFunctionInstruction and + // Phi instructions aren't linked into the instruction-level flow graph. + not instr instanceof PhiInstruction and + not instr instanceof UnreachedInstruction + } + + /** + * Holds if there are multiple (`n`) edges of kind `kind` from `source`, + * where `target` is among the targets of those edges. + */ + query predicate ambiguousSuccessors(Instruction source, EdgeKind kind, int n, Instruction target) { + n = strictcount(Instruction t | source.getSuccessor(kind) = t) and + n > 1 and + source.getSuccessor(kind) = target + } + + /** + * Holds if `instr` in `f` is part of a loop even though the AST of `f` + * contains no element that can cause loops. + */ + query predicate unexplainedLoop(Language::Function f, Instruction instr) { + exists(IRBlock block | + instr.getBlock() = block and + block.getEnclosingFunction() = f and + block.getASuccessor+() = block + ) and + not Language::hasPotentialLoop(f) + } + + /** + * Holds if a `Phi` instruction is present in a block with fewer than two + * predecessors. + */ + query predicate unnecessaryPhiInstruction(PhiInstruction instr) { + count(instr.getBlock().getAPredecessor()) < 2 + } + + /** + * Holds if operand `operand` consumes a value that was defined in + * a different function. + */ + query predicate operandAcrossFunctions(Operand operand, Instruction instr, Instruction defInstr) { + operand.getUse() = instr and + operand.getAnyDef() = defInstr and + instr.getEnclosingIRFunction() != defInstr.getEnclosingIRFunction() + } + + /** + * Holds if instruction `instr` is not in exactly one block. + */ + query predicate instructionWithoutUniqueBlock(Instruction instr, int blockCount) { + blockCount = count(instr.getBlock()) and + blockCount != 1 + } + + private predicate forwardEdge(IRBlock b1, IRBlock b2) { + b1.getASuccessor() = b2 and + not b1.getBackEdgeSuccessor(_) = b2 + } + + /** + * Holds if `f` contains a loop in which no edge is a back edge. + * + * This check ensures we don't have too _few_ back edges. + */ + query predicate containsLoopOfForwardEdges(IRFunction f) { + exists(IRBlock block | + forwardEdge+(block, block) and + block.getEnclosingIRFunction() = f + ) + } + + /** + * Holds if `block` is reachable from its function entry point but would not + * be reachable by traversing only forward edges. This check is skipped for + * functions containing `goto` statements as the property does not generally + * hold there. + * + * This check ensures we don't have too _many_ back edges. + */ + query predicate lostReachability(IRBlock block) { + exists(IRFunction f, IRBlock entry | + entry = f.getEntryBlock() and + entry.getASuccessor+() = block and + not forwardEdge+(entry, block) and + not Language::hasGoto(f.getFunction()) + ) + } + + /** + * Holds if the number of back edges differs between the `Instruction` graph + * and the `IRBlock` graph. + */ + query predicate backEdgeCountMismatch(Language::Function f, int fromInstr, int fromBlock) { + fromInstr = + count(Instruction i1, Instruction i2 | + i1.getEnclosingFunction() = f and i1.getBackEdgeSuccessor(_) = i2 + ) and + fromBlock = + count(IRBlock b1, IRBlock b2 | + b1.getEnclosingFunction() = f and b1.getBackEdgeSuccessor(_) = b2 + ) and + fromInstr != fromBlock + } + + /** + * Gets the point in the function at which the specified operand is evaluated. For most operands, + * this is at the instruction that consumes the use. For a `PhiInputOperand`, the effective point + * of evaluation is at the end of the corresponding predecessor block. + */ + private predicate pointOfEvaluation(Operand operand, IRBlock block, int index) { + block = operand.(PhiInputOperand).getPredecessorBlock() and + index = block.getInstructionCount() + or + exists(Instruction use | + use = operand.(NonPhiOperand).getUse() and + block.getInstruction(index) = use + ) + } + + /** + * Holds if `useOperand` has a definition that does not dominate the use. + */ + query predicate useNotDominatedByDefinition( + Operand useOperand, string message, IRFunction func, string funcText + ) { + exists(IRBlock useBlock, int useIndex, Instruction defInstr, IRBlock defBlock, int defIndex | + not useOperand.getUse() instanceof UnmodeledUseInstruction and + not defInstr instanceof UnmodeledDefinitionInstruction and + pointOfEvaluation(useOperand, useBlock, useIndex) and + defInstr = useOperand.getAnyDef() and + ( + defInstr instanceof PhiInstruction and + defBlock = defInstr.getBlock() and + defIndex = -1 + or + defBlock.getInstruction(defIndex) = defInstr + ) and + not ( + defBlock.strictlyDominates(useBlock) + or + defBlock = useBlock and + defIndex < useIndex + ) and + message = + "Operand '" + useOperand.toString() + + "' is not dominated by its definition in function '$@'." and + func = useOperand.getEnclosingIRFunction() and + 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/IRVariable.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/IRVariable.qll index ee7aebc4749..ae585ec2c7e 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/IRVariable.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/IRVariable.qll @@ -176,7 +176,7 @@ IRTempVariable getIRTempVariable(Language::AST ast, TempVariableTag tag) { /** * A temporary variable introduced by IR construction. The most common examples are the variable - * generated to hold the return value of afunction, or the variable generated to hold the result of + * generated to hold the return value of a function, or the variable generated to hold the result of * a condition operator (`a ? b : c`). */ class IRTempVariable extends IRGeneratedVariable, IRAutomaticVariable, TIRTempVariable { 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 52cf46a5f0e..472f1673f0d 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 @@ -10,274 +10,6 @@ import Imports::MemoryAccessKind import Imports::Opcode private import Imports::OperandTag -module InstructionSanity { - /** - * Holds if instruction `instr` is missing an expected operand with tag `tag`. - */ - query predicate missingOperand(Instruction instr, string message, IRFunction func, string funcText) { - exists(OperandTag tag | - instr.getOpcode().hasOperand(tag) and - not exists(NonPhiOperand operand | - operand = instr.getAnOperand() and - operand.getOperandTag() = tag - ) and - message = - "Instruction '" + instr.getOpcode().toString() + - "' is missing an expected operand with tag '" + tag.toString() + "' in function '$@'." and - func = instr.getEnclosingIRFunction() and - funcText = Language::getIdentityString(func.getFunction()) - ) - } - - /** - * Holds if instruction `instr` has an unexpected operand with tag `tag`. - */ - query predicate unexpectedOperand(Instruction instr, OperandTag tag) { - exists(NonPhiOperand operand | - operand = instr.getAnOperand() and - operand.getOperandTag() = tag - ) and - not instr.getOpcode().hasOperand(tag) and - not (instr instanceof CallInstruction and tag instanceof ArgumentOperandTag) and - not ( - instr instanceof BuiltInOperationInstruction and tag instanceof PositionalArgumentOperandTag - ) and - not (instr instanceof InlineAsmInstruction and tag instanceof AsmOperandTag) - } - - /** - * Holds if instruction `instr` has multiple operands with tag `tag`. - */ - query predicate duplicateOperand( - Instruction instr, string message, IRFunction func, string funcText - ) { - exists(OperandTag tag, int operandCount | - operandCount = - strictcount(NonPhiOperand operand | - operand = instr.getAnOperand() and - operand.getOperandTag() = tag - ) and - operandCount > 1 and - not tag instanceof UnmodeledUseOperandTag and - message = - "Instruction has " + operandCount + " operands with tag '" + tag.toString() + "'" + - " in function '$@'." and - func = instr.getEnclosingIRFunction() and - funcText = Language::getIdentityString(func.getFunction()) - ) - } - - /** - * Holds if `Phi` instruction `instr` is missing an operand corresponding to - * the predecessor block `pred`. - */ - query predicate missingPhiOperand(PhiInstruction instr, IRBlock pred) { - pred = instr.getBlock().getAPredecessor() and - not exists(PhiInputOperand operand | - operand = instr.getAnOperand() and - operand.getPredecessorBlock() = pred - ) - } - - query predicate missingOperandType(Operand operand, string message) { - exists(Language::Function func, Instruction use | - not exists(operand.getType()) and - use = operand.getUse() and - func = use.getEnclosingFunction() and - message = - "Operand '" + operand.toString() + "' of instruction '" + use.getOpcode().toString() + - "' missing type in function '" + Language::getIdentityString(func) + "'." - ) - } - - query predicate duplicateChiOperand( - ChiInstruction chi, string message, IRFunction func, string funcText - ) { - chi.getTotal() = chi.getPartial() and - message = - "Chi instruction for " + chi.getPartial().toString() + - " has duplicate operands in function $@" and - func = chi.getEnclosingIRFunction() and - funcText = Language::getIdentityString(func.getFunction()) - } - - query predicate sideEffectWithoutPrimary( - SideEffectInstruction instr, string message, IRFunction func, string funcText - ) { - not exists(instr.getPrimaryInstruction()) and - message = "Side effect instruction missing primary instruction in function $@" and - func = instr.getEnclosingIRFunction() and - funcText = Language::getIdentityString(func.getFunction()) - } - - /** - * Holds if an instruction, other than `ExitFunction`, has no successors. - */ - query predicate instructionWithoutSuccessor(Instruction instr) { - not exists(instr.getASuccessor()) and - not instr instanceof ExitFunctionInstruction and - // Phi instructions aren't linked into the instruction-level flow graph. - not instr instanceof PhiInstruction and - not instr instanceof UnreachedInstruction - } - - /** - * Holds if there are multiple (`n`) edges of kind `kind` from `source`, - * where `target` is among the targets of those edges. - */ - query predicate ambiguousSuccessors(Instruction source, EdgeKind kind, int n, Instruction target) { - n = strictcount(Instruction t | source.getSuccessor(kind) = t) and - n > 1 and - source.getSuccessor(kind) = target - } - - /** - * Holds if `instr` in `f` is part of a loop even though the AST of `f` - * contains no element that can cause loops. - */ - query predicate unexplainedLoop(Language::Function f, Instruction instr) { - exists(IRBlock block | - instr.getBlock() = block and - block.getEnclosingFunction() = f and - block.getASuccessor+() = block - ) and - not Language::hasPotentialLoop(f) - } - - /** - * Holds if a `Phi` instruction is present in a block with fewer than two - * predecessors. - */ - query predicate unnecessaryPhiInstruction(PhiInstruction instr) { - count(instr.getBlock().getAPredecessor()) < 2 - } - - /** - * Holds if operand `operand` consumes a value that was defined in - * a different function. - */ - query predicate operandAcrossFunctions(Operand operand, Instruction instr, Instruction defInstr) { - operand.getUse() = instr and - operand.getAnyDef() = defInstr and - instr.getEnclosingIRFunction() != defInstr.getEnclosingIRFunction() - } - - /** - * Holds if instruction `instr` is not in exactly one block. - */ - query predicate instructionWithoutUniqueBlock(Instruction instr, int blockCount) { - blockCount = count(instr.getBlock()) and - blockCount != 1 - } - - private predicate forwardEdge(IRBlock b1, IRBlock b2) { - b1.getASuccessor() = b2 and - not b1.getBackEdgeSuccessor(_) = b2 - } - - /** - * Holds if `f` contains a loop in which no edge is a back edge. - * - * This check ensures we don't have too _few_ back edges. - */ - query predicate containsLoopOfForwardEdges(IRFunction f) { - exists(IRBlock block | - forwardEdge+(block, block) and - block.getEnclosingIRFunction() = f - ) - } - - /** - * Holds if `block` is reachable from its function entry point but would not - * be reachable by traversing only forward edges. This check is skipped for - * functions containing `goto` statements as the property does not generally - * hold there. - * - * This check ensures we don't have too _many_ back edges. - */ - query predicate lostReachability(IRBlock block) { - exists(IRFunction f, IRBlock entry | - entry = f.getEntryBlock() and - entry.getASuccessor+() = block and - not forwardEdge+(entry, block) and - not Language::hasGoto(f.getFunction()) - ) - } - - /** - * Holds if the number of back edges differs between the `Instruction` graph - * and the `IRBlock` graph. - */ - query predicate backEdgeCountMismatch(Language::Function f, int fromInstr, int fromBlock) { - fromInstr = - count(Instruction i1, Instruction i2 | - i1.getEnclosingFunction() = f and i1.getBackEdgeSuccessor(_) = i2 - ) and - fromBlock = - count(IRBlock b1, IRBlock b2 | - b1.getEnclosingFunction() = f and b1.getBackEdgeSuccessor(_) = b2 - ) and - fromInstr != fromBlock - } - - /** - * Gets the point in the function at which the specified operand is evaluated. For most operands, - * this is at the instruction that consumes the use. For a `PhiInputOperand`, the effective point - * of evaluation is at the end of the corresponding predecessor block. - */ - private predicate pointOfEvaluation(Operand operand, IRBlock block, int index) { - block = operand.(PhiInputOperand).getPredecessorBlock() and - index = block.getInstructionCount() - or - exists(Instruction use | - use = operand.(NonPhiOperand).getUse() and - block.getInstruction(index) = use - ) - } - - /** - * Holds if `useOperand` has a definition that does not dominate the use. - */ - query predicate useNotDominatedByDefinition( - Operand useOperand, string message, IRFunction func, string funcText - ) { - exists(IRBlock useBlock, int useIndex, Instruction defInstr, IRBlock defBlock, int defIndex | - not useOperand.getUse() instanceof UnmodeledUseInstruction and - not defInstr instanceof UnmodeledDefinitionInstruction and - pointOfEvaluation(useOperand, useBlock, useIndex) and - defInstr = useOperand.getAnyDef() and - ( - defInstr instanceof PhiInstruction and - defBlock = defInstr.getBlock() and - defIndex = -1 - or - defBlock.getInstruction(defIndex) = defInstr - ) and - not ( - defBlock.strictlyDominates(useBlock) - or - defBlock = useBlock and - defIndex < useIndex - ) and - message = - "Operand '" + useOperand.toString() + - "' is not dominated by its definition in function '$@'." and - func = useOperand.getEnclosingIRFunction() and - 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()) - } -} - /** * Gets an `Instruction` that is contained in `IRFunction`, and has a location with the specified * `File` and line number. Used for assigning register names when printing IR. @@ -1204,6 +936,11 @@ class CallInstruction extends Instruction { final Instruction getPositionalArgument(int index) { result = getPositionalArgumentOperand(index).getDef() } + + /** + * Gets the number of arguments of the call, including the `this` pointer, if any. + */ + final int getNumberOfArguments() { result = count(this.getAnArgumentOperand()) } } /** @@ -1352,6 +1089,26 @@ class SizedBufferMayWriteSideEffectInstruction extends WriteSideEffectInstructio Instruction getSizeDef() { result = getAnOperand().(BufferSizeOperand).getDef() } } +/** + * An instruction representing the initial value of newly allocated memory, e.g. the result of a + * call to `malloc`. + */ +class InitializeDynamicAllocationInstruction extends SideEffectInstruction { + InitializeDynamicAllocationInstruction() { + getOpcode() instanceof Opcode::InitializeDynamicAllocation + } + + /** + * Gets the address of the allocation this instruction is initializing. + */ + final AddressOperand getAllocationAddressOperand() { result = getAnOperand() } + + /** + * Gets the operand for the allocation this instruction is initializing. + */ + final Instruction getAllocationAddress() { result = getAllocationAddressOperand().getDef() } +} + /** * An instruction representing a GNU or MSVC inline assembly statement. */ diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/SSAConstruction.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/SSAConstruction.qll index 62753e298cd..6578968b1ff 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/SSAConstruction.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/SSAConstruction.qll @@ -665,17 +665,18 @@ module DefUse { private predicate definitionReachesRank( Alias::MemoryLocation useLocation, OldBlock block, int defRank, int reachesRank ) { + // The def always reaches the next use, even if there is also a def on the + // use instruction. hasDefinitionAtRank(useLocation, _, block, defRank, _) and - reachesRank <= exitRank(useLocation, block) and // Without this, the predicate would be infinite. - ( - // The def always reaches the next use, even if there is also a def on the - // use instruction. - reachesRank = defRank + 1 - or - // If the def reached the previous rank, it also reaches the current rank, - // unless there was another def at the previous rank. - definitionReachesRank(useLocation, block, defRank, reachesRank - 1) and - not hasDefinitionAtRank(useLocation, _, block, reachesRank - 1, _) + reachesRank = defRank + 1 + or + // If the def reached the previous rank, it also reaches the current rank, + // unless there was another def at the previous rank. + exists(int prevRank | + reachesRank = prevRank + 1 and + definitionReachesRank(useLocation, block, defRank, prevRank) and + not prevRank = exitRank(useLocation, block) and + not hasDefinitionAtRank(useLocation, _, block, prevRank, _) ) } diff --git a/cpp/ql/src/semmle/code/cpp/ir/internal/ASTValueNumbering.qll b/cpp/ql/src/semmle/code/cpp/ir/internal/ASTValueNumbering.qll index 2bb8315990a..90e9b4ef920 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/internal/ASTValueNumbering.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/internal/ASTValueNumbering.qll @@ -51,6 +51,7 @@ private import semmle.code.cpp.ir.IR * methods. */ class GVN extends TValueNumber { + pragma[noinline] GVN() { exists(Instruction instr | this = tvalueNumber(instr) and exists(instr.getUnconvertedResultExpression()) diff --git a/cpp/ql/src/semmle/code/cpp/rangeanalysis/SimpleRangeAnalysis.qll b/cpp/ql/src/semmle/code/cpp/rangeanalysis/SimpleRangeAnalysis.qll index 4a8f39d79f2..fe89d8ebbe5 100644 --- a/cpp/ql/src/semmle/code/cpp/rangeanalysis/SimpleRangeAnalysis.qll +++ b/cpp/ql/src/semmle/code/cpp/rangeanalysis/SimpleRangeAnalysis.qll @@ -91,13 +91,30 @@ private float wideningUpperBounds(ArithmeticType t) { result = 1.0 / 0.0 // +Inf } +/** + * Gets the value of the expression `e`, if it is a constant. + * This predicate also handles the case of constant variables initialized in compilation units, + * which doesn't necessarily have a getValue() result from the extractor. + */ +private string getValue(Expr e) { + if exists(e.getValue()) + then result = e.getValue() + else + exists(VariableAccess access, Variable v | + e = access and + v = access.getTarget() and + v.getUnderlyingType().isConst() and + result = getValue(v.getAnAssignedValue()) + ) +} + /** Set of expressions which we know how to analyze. */ private predicate analyzableExpr(Expr e) { // The type of the expression must be arithmetic. We reuse the logic in // `exprMinVal` to check this. exists(exprMinVal(e)) and ( - exists(e.getValue().toFloat()) or + exists(getValue(e).toFloat()) or e instanceof UnaryPlusExpr or e instanceof UnaryMinusExpr or e instanceof MinExpr or @@ -365,8 +382,8 @@ private float getTruncatedLowerBounds(Expr expr) { then // If the expression evaluates to a constant, then there is no // need to call getLowerBoundsImpl. - if exists(expr.getValue().toFloat()) - then result = expr.getValue().toFloat() + if exists(getValue(expr).toFloat()) + then result = getValue(expr).toFloat() else ( // Some of the bounds computed by getLowerBoundsImpl might // overflow, so we replace invalid bounds with exprMinVal. @@ -418,8 +435,8 @@ private float getTruncatedUpperBounds(Expr expr) { then // If the expression evaluates to a constant, then there is no // need to call getUpperBoundsImpl. - if exists(expr.getValue().toFloat()) - then result = expr.getValue().toFloat() + if exists(getValue(expr).toFloat()) + then result = getValue(expr).toFloat() else ( // Some of the bounds computed by `getUpperBoundsImpl` // might overflow, so we replace invalid bounds with diff --git a/cpp/ql/src/semmle/code/cpp/security/Overflow.qll b/cpp/ql/src/semmle/code/cpp/security/Overflow.qll index ba98f42d096..59993954cc8 100644 --- a/cpp/ql/src/semmle/code/cpp/security/Overflow.qll +++ b/cpp/ql/src/semmle/code/cpp/security/Overflow.qll @@ -13,19 +13,36 @@ predicate guardedAbs(Operation e, Expr use) { ) } +/** This is `BasicBlock.getNode`, restricted to `Stmt` for performance. */ +pragma[noinline] +private int getStmtIndexInBlock(BasicBlock block, Stmt stmt) { block.getNode(result) = stmt } + +pragma[inline] +private predicate stmtDominates(Stmt dominator, Stmt dominated) { + // In same block + exists(BasicBlock block, int dominatorIndex, int dominatedIndex | + dominatorIndex = getStmtIndexInBlock(block, dominator) and + dominatedIndex = getStmtIndexInBlock(block, dominated) and + dominatedIndex >= dominatorIndex + ) + or + // In (possibly) different blocks + bbStrictlyDominates(dominator.getBasicBlock(), dominated.getBasicBlock()) +} + /** is the size of this use guarded to be less than something? */ pragma[nomagic] predicate guardedLesser(Operation e, Expr use) { exists(IfStmt c, RelationalOperation guard | use = guard.getLesserOperand().getAChild*() and guard = c.getControllingExpr().getAChild*() and - iDominates*(c.getThen(), e.getEnclosingStmt()) + stmtDominates(c.getThen(), e.getEnclosingStmt()) ) or exists(Loop c, RelationalOperation guard | use = guard.getLesserOperand().getAChild*() and guard = c.getControllingExpr().getAChild*() and - iDominates*(c.getStmt(), e.getEnclosingStmt()) + stmtDominates(c.getStmt(), e.getEnclosingStmt()) ) or exists(ConditionalExpr c, RelationalOperation guard | @@ -43,13 +60,13 @@ predicate guardedGreater(Operation e, Expr use) { exists(IfStmt c, RelationalOperation guard | use = guard.getGreaterOperand().getAChild*() and guard = c.getControllingExpr().getAChild*() and - iDominates*(c.getThen(), e.getEnclosingStmt()) + stmtDominates(c.getThen(), e.getEnclosingStmt()) ) or exists(Loop c, RelationalOperation guard | use = guard.getGreaterOperand().getAChild*() and guard = c.getControllingExpr().getAChild*() and - iDominates*(c.getStmt(), e.getEnclosingStmt()) + stmtDominates(c.getStmt(), e.getEnclosingStmt()) ) or exists(ConditionalExpr c, RelationalOperation guard | diff --git a/cpp/ql/src/semmle/code/cpp/stmts/Stmt.qll b/cpp/ql/src/semmle/code/cpp/stmts/Stmt.qll index f64b5f2fb31..1640bee0f35 100644 --- a/cpp/ql/src/semmle/code/cpp/stmts/Stmt.qll +++ b/cpp/ql/src/semmle/code/cpp/stmts/Stmt.qll @@ -1604,30 +1604,18 @@ class EnumSwitch extends SwitchStmt { * ``` * there are results `GREEN` and `BLUE`. */ - pragma[noopt] EnumConstant getAMissingCase() { exists(Enum et | - exists(Expr e, Type t | - e = this.getExpr() and - this instanceof EnumSwitch and - t = e.getType() and - et = t.getUnderlyingType() - ) and + et = this.getExpr().getUnderlyingType() and result = et.getAnEnumConstant() and - not exists(string value | - exists(SwitchCase sc, Expr e | - sc = this.getASwitchCase() and - e = sc.getExpr() and - value = e.getValue() - ) and - exists(Initializer init, Expr e | - init = result.getInitializer() and - e = init.getExpr() and - e.getValue() = value - ) - ) + not this.matchesValue(result.getInitializer().getExpr().getValue()) ) } + + pragma[noinline] + private predicate matchesValue(string value) { + value = this.getASwitchCase().getExpr().getValue() + } } /** diff --git a/cpp/ql/test/experimental/README.md b/cpp/ql/test/experimental/README.md new file mode 100644 index 00000000000..2a9db304b9f --- /dev/null +++ b/cpp/ql/test/experimental/README.md @@ -0,0 +1 @@ +This directory contains tests for [experimental](../../../../docs/experimental.md) CodeQL queries and libraries. 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 7359b068d8e..a62b9b3e204 100644 --- a/cpp/ql/test/library-tests/dataflow/security-taint/tainted.expected +++ b/cpp/ql/test/library-tests/dataflow/security-taint/tainted.expected @@ -52,3 +52,18 @@ | test.cpp:75:20:75:25 | call to getenv | test.cpp:75:15:75:18 | call to atoi | | | test.cpp:75:20:75:25 | call to getenv | test.cpp:75:20:75:25 | call to getenv | | | test.cpp:75:20:75:25 | call to getenv | test.cpp:75:20:75:45 | (const char *)... | | +| test.cpp:83:28:83:33 | call to getenv | test.cpp:8:24:8:25 | s1 | | +| test.cpp:83:28:83:33 | call to getenv | test.cpp:11:20:11:21 | s1 | | +| test.cpp:83:28:83:33 | call to getenv | test.cpp:11:36:11:37 | s2 | | +| test.cpp:83:28:83:33 | call to getenv | test.cpp:83:17:83:24 | userName | | +| test.cpp:83:28:83:33 | call to getenv | test.cpp:83:28:83:33 | call to getenv | | +| test.cpp:83:28:83:33 | call to getenv | test.cpp:83:28:83:46 | (const char *)... | | +| test.cpp:83:28:83:33 | call to getenv | test.cpp:85:8:85:11 | copy | | +| test.cpp:83:28:83:33 | call to getenv | test.cpp:86:2:86:7 | call to strcpy | | +| test.cpp:83:28:83:33 | call to getenv | test.cpp:86:9:86:12 | copy | | +| test.cpp:83:28:83:33 | call to getenv | test.cpp:86:15:86:22 | userName | | +| test.cpp:83:28:83:33 | call to getenv | test.cpp:88:6:88:27 | ! ... | | +| test.cpp:83:28:83:33 | call to getenv | test.cpp:88:7:88:12 | call to strcmp | | +| test.cpp:83:28:83:33 | call to getenv | test.cpp:88:7:88:27 | (bool)... | | +| test.cpp:83:28:83:33 | call to getenv | test.cpp:88:14:88:17 | (const char *)... | | +| test.cpp:83:28:83:33 | call to getenv | test.cpp:88:14:88:17 | copy | | diff --git a/cpp/ql/test/library-tests/dataflow/security-taint/tainted_diff.expected b/cpp/ql/test/library-tests/dataflow/security-taint/tainted_diff.expected index ed954b5444a..ca905d71823 100644 --- a/cpp/ql/test/library-tests/dataflow/security-taint/tainted_diff.expected +++ b/cpp/ql/test/library-tests/dataflow/security-taint/tainted_diff.expected @@ -8,3 +8,6 @@ | test.cpp:68:28:68:33 | call to getenv | test.cpp:69:10:69:13 | copy | AST only | | test.cpp:68:28:68:33 | call to getenv | test.cpp:70:12:70:15 | copy | AST only | | test.cpp:68:28:68:33 | call to getenv | test.cpp:71:12:71:15 | copy | AST only | +| test.cpp:83:28:83:33 | call to getenv | test.cpp:11:20:11:21 | s1 | AST only | +| test.cpp:83:28:83:33 | call to getenv | test.cpp:85:8:85:11 | copy | AST only | +| test.cpp:83:28:83:33 | call to getenv | test.cpp:86:9:86:12 | copy | AST only | diff --git a/cpp/ql/test/library-tests/dataflow/security-taint/tainted_ir.expected b/cpp/ql/test/library-tests/dataflow/security-taint/tainted_ir.expected index 114c213ff54..1a7027d399c 100644 --- a/cpp/ql/test/library-tests/dataflow/security-taint/tainted_ir.expected +++ b/cpp/ql/test/library-tests/dataflow/security-taint/tainted_ir.expected @@ -40,3 +40,15 @@ | test.cpp:75:20:75:25 | call to getenv | test.cpp:75:15:75:18 | call to atoi | | | test.cpp:75:20:75:25 | call to getenv | test.cpp:75:20:75:25 | call to getenv | | | test.cpp:75:20:75:25 | call to getenv | test.cpp:75:20:75:45 | (const char *)... | | +| test.cpp:83:28:83:33 | call to getenv | test.cpp:8:24:8:25 | s1 | | +| test.cpp:83:28:83:33 | call to getenv | test.cpp:11:36:11:37 | s2 | | +| test.cpp:83:28:83:33 | call to getenv | test.cpp:83:17:83:24 | userName | | +| test.cpp:83:28:83:33 | call to getenv | test.cpp:83:28:83:33 | call to getenv | | +| test.cpp:83:28:83:33 | call to getenv | test.cpp:83:28:83:46 | (const char *)... | | +| test.cpp:83:28:83:33 | call to getenv | test.cpp:86:2:86:7 | call to strcpy | | +| test.cpp:83:28:83:33 | call to getenv | test.cpp:86:15:86:22 | userName | | +| test.cpp:83:28:83:33 | call to getenv | test.cpp:88:6:88:27 | ! ... | | +| test.cpp:83:28:83:33 | call to getenv | test.cpp:88:7:88:12 | call to strcmp | | +| test.cpp:83:28:83:33 | call to getenv | test.cpp:88:7:88:27 | (bool)... | | +| test.cpp:83:28:83:33 | call to getenv | test.cpp:88:14:88:17 | (const char *)... | | +| test.cpp:83:28:83:33 | call to getenv | test.cpp:88:14:88:17 | copy | | diff --git a/cpp/ql/test/library-tests/dataflow/security-taint/test.cpp b/cpp/ql/test/library-tests/dataflow/security-taint/test.cpp index 5eb7ee79b46..9d28f9680ca 100644 --- a/cpp/ql/test/library-tests/dataflow/security-taint/test.cpp +++ b/cpp/ql/test/library-tests/dataflow/security-taint/test.cpp @@ -76,3 +76,16 @@ void guard() { if (len > 1000) return; char **node = (char **) malloc(len * sizeof(char *)); } + +const char *alias_global; + +void mallocBuffer() { + const char *userName = getenv("USER_NAME"); + char *alias = (char*)malloc(4096); + char *copy = (char*)malloc(4096); + strcpy(copy, userName); + alias_global = alias; // to force a Chi node on all aliased memory + if (!strcmp(copy, "admin")) { // copy should be tainted + isAdmin = true; + } +} \ No newline at end of file diff --git a/cpp/ql/test/library-tests/declarationEntry/declarationEntry/declarationEntry.expected b/cpp/ql/test/library-tests/declarationEntry/declarationEntry/declarationEntry.expected index 912fa5bf1b3..76f564d1f7b 100644 --- a/cpp/ql/test/library-tests/declarationEntry/declarationEntry/declarationEntry.expected +++ b/cpp/ql/test/library-tests/declarationEntry/declarationEntry/declarationEntry.expected @@ -27,3 +27,5 @@ | declarationEntry.cpp:31:4:31:19 | myMemberVariable | declarationEntry.cpp:31:4:31:19 | definition of myMemberVariable | 1 | 1 | | declarationEntry.cpp:34:22:34:28 | mtc_int | declarationEntry.cpp:34:22:34:28 | definition of mtc_int | 1 | 1 | | declarationEntry.cpp:35:24:35:32 | mtc_short | declarationEntry.cpp:35:24:35:32 | definition of mtc_short | 1 | 1 | +| macro.c:2:1:2:3 | foo | macro.c:2:1:2:3 | declaration of foo | 1 | 1 | +| macro.c:4:5:4:8 | main | macro.c:4:5:4:8 | definition of main | 1 | 1 | diff --git a/cpp/ql/test/library-tests/declarationEntry/declarationEntry/fde.expected b/cpp/ql/test/library-tests/declarationEntry/declarationEntry/fde.expected index 6a452473c72..9a544200cae 100644 --- a/cpp/ql/test/library-tests/declarationEntry/declarationEntry/fde.expected +++ b/cpp/ql/test/library-tests/declarationEntry/declarationEntry/fde.expected @@ -1,12 +1,14 @@ -| declarationEntry.c:2:6:2:20 | declaration of myFirstFunction | | -| declarationEntry.c:4:6:4:21 | definition of mySecondFunction | | -| declarationEntry.c:8:6:8:20 | definition of myThirdFunction | | -| declarationEntry.c:13:2:13:2 | declaration of myFourthFunction | isImplicit | -| declarationEntry.c:14:2:14:2 | declaration of myFifthFunction | isImplicit | -| declarationEntry.c:17:6:17:21 | declaration of myFourthFunction | | -| declarationEntry.cpp:9:6:9:15 | declaration of myFunction | | -| declarationEntry.cpp:11:6:11:15 | definition of myFunction | | -| declarationEntry.cpp:28:7:28:7 | declaration of operator= | | -| declarationEntry.cpp:28:7:28:7 | declaration of operator= | | -| declarationEntry.cpp:28:7:28:7 | declaration of operator= | | -| declarationEntry.cpp:28:7:28:7 | declaration of operator= | | +| declarationEntry.c:2:6:2:20 | declaration of myFirstFunction | | 1 | c_linkage | +| declarationEntry.c:4:6:4:21 | definition of mySecondFunction | | 1 | c_linkage | +| declarationEntry.c:8:6:8:20 | definition of myThirdFunction | | 1 | c_linkage | +| declarationEntry.c:13:2:13:2 | declaration of myFourthFunction | isImplicit | 1 | c_linkage | +| declarationEntry.c:14:2:14:2 | declaration of myFifthFunction | isImplicit | 1 | c_linkage | +| declarationEntry.c:17:6:17:21 | declaration of myFourthFunction | | 1 | c_linkage | +| declarationEntry.cpp:9:6:9:15 | declaration of myFunction | | 0 | | +| declarationEntry.cpp:11:6:11:15 | definition of myFunction | | 0 | | +| declarationEntry.cpp:28:7:28:7 | declaration of operator= | | 0 | | +| declarationEntry.cpp:28:7:28:7 | declaration of operator= | | 0 | | +| declarationEntry.cpp:28:7:28:7 | declaration of operator= | | 0 | | +| declarationEntry.cpp:28:7:28:7 | declaration of operator= | | 0 | | +| macro.c:2:1:2:3 | declaration of foo | | 2 | c_linkage, static | +| macro.c:4:5:4:8 | definition of main | | 1 | c_linkage | diff --git a/cpp/ql/test/library-tests/declarationEntry/declarationEntry/fde.ql b/cpp/ql/test/library-tests/declarationEntry/declarationEntry/fde.ql index 7d120121e88..fbc97861f4b 100644 --- a/cpp/ql/test/library-tests/declarationEntry/declarationEntry/fde.ql +++ b/cpp/ql/test/library-tests/declarationEntry/declarationEntry/fde.ql @@ -2,4 +2,4 @@ import cpp from FunctionDeclarationEntry fde, string imp where if fde.isImplicit() then imp = "isImplicit" else imp = "" -select fde, imp +select fde, imp, count(fde.getASpecifier()), concat(fde.getASpecifier().toString(), ", ") diff --git a/cpp/ql/test/library-tests/declarationEntry/declarationEntry/macro.c b/cpp/ql/test/library-tests/declarationEntry/declarationEntry/macro.c new file mode 100644 index 00000000000..590b6a92b95 --- /dev/null +++ b/cpp/ql/test/library-tests/declarationEntry/declarationEntry/macro.c @@ -0,0 +1,7 @@ +#define Foo static void foo() +Foo; + +int main() +{ + return 0; +} 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 4c1bcc9bc13..bcaa0ab3f25 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 @@ -1238,3 +1238,47 @@ ssa.cpp: # 254| v254_10(void) = UnmodeledUse : mu* # 254| v254_11(void) = AliasedUse : ~m262_1 # 254| v254_12(void) = ExitFunction : + +# 268| void* MallocAliasing(void*, int) +# 268| Block 0 +# 268| v268_1(void) = EnterFunction : +# 268| m268_2(unknown) = AliasedDefinition : +# 268| m268_3(unknown) = InitializeNonLocal : +# 268| m268_4(unknown) = Chi : total:m268_2, partial:m268_3 +# 268| mu268_5(unknown) = UnmodeledDefinition : +# 268| r268_6(glval) = VariableAddress[s] : +# 268| m268_7(void *) = InitializeParameter[s] : &:r268_6 +# 268| r268_8(void *) = Load : &:r268_6, m268_7 +# 268| m268_9(unknown) = InitializeIndirection[s] : &:r268_8 +# 268| r268_10(glval) = VariableAddress[size] : +# 268| m268_11(int) = InitializeParameter[size] : &:r268_10 +# 269| r269_1(glval) = VariableAddress[buf] : +# 269| r269_2(glval) = FunctionAddress[malloc] : +# 269| r269_3(glval) = VariableAddress[size] : +# 269| r269_4(int) = Load : &:r269_3, m268_11 +# 269| r269_5(void *) = Call : func:r269_2, 0:r269_4 +# 269| m269_6(unknown) = ^CallSideEffect : ~m268_9 +# 269| m269_7(unknown) = Chi : total:m268_9, partial:m269_6 +# 269| m269_8(unknown) = ^InitializeDynamicAllocation : &:r269_5 +# 269| m269_9(void *) = Store : &:r269_1, r269_5 +# 270| r270_1(glval) = FunctionAddress[memcpy] : +# 270| r270_2(glval) = VariableAddress[buf] : +# 270| r270_3(void *) = Load : &:r270_2, m269_9 +# 270| r270_4(glval) = VariableAddress[s] : +# 270| r270_5(void *) = Load : &:r270_4, m268_7 +# 270| r270_6(glval) = VariableAddress[size] : +# 270| r270_7(int) = Load : &:r270_6, m268_11 +# 270| r270_8(void *) = Call : func:r270_1, 0:r270_3, 1:r270_5, 2:r270_7 +# 270| v270_9(void) = ^SizedBufferReadSideEffect[1] : &:r270_5, r270_7, ~m269_8 +# 270| m270_10(unknown) = ^SizedBufferMustWriteSideEffect[0] : &:r270_3, r270_7 +# 270| m270_11(unknown) = Chi : total:m269_8, partial:m270_10 +# 271| r271_1(glval) = VariableAddress[#return] : +# 271| r271_2(glval) = VariableAddress[buf] : +# 271| r271_3(void *) = Load : &:r271_2, m269_9 +# 271| m271_4(void *) = Store : &:r271_1, r271_3 +# 268| v268_12(void) = ReturnIndirection : &:r268_8, ~m270_11 +# 268| r268_13(glval) = VariableAddress[#return] : +# 268| v268_14(void) = ReturnValue : &:r268_13, m271_4 +# 268| v268_15(void) = UnmodeledUse : mu* +# 268| v268_16(void) = AliasedUse : ~m270_11 +# 268| v268_17(void) = ExitFunction : diff --git a/cpp/ql/test/library-tests/ir/ssa/aliased_ssa_ir_unsound.expected b/cpp/ql/test/library-tests/ir/ssa/aliased_ssa_ir_unsound.expected index 119e28b3ffd..07d0c8ea717 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 @@ -1233,3 +1233,47 @@ ssa.cpp: # 254| v254_10(void) = UnmodeledUse : mu* # 254| v254_11(void) = AliasedUse : ~m262_1 # 254| v254_12(void) = ExitFunction : + +# 268| void* MallocAliasing(void*, int) +# 268| Block 0 +# 268| v268_1(void) = EnterFunction : +# 268| m268_2(unknown) = AliasedDefinition : +# 268| m268_3(unknown) = InitializeNonLocal : +# 268| m268_4(unknown) = Chi : total:m268_2, partial:m268_3 +# 268| mu268_5(unknown) = UnmodeledDefinition : +# 268| r268_6(glval) = VariableAddress[s] : +# 268| m268_7(void *) = InitializeParameter[s] : &:r268_6 +# 268| r268_8(void *) = Load : &:r268_6, m268_7 +# 268| m268_9(unknown) = InitializeIndirection[s] : &:r268_8 +# 268| r268_10(glval) = VariableAddress[size] : +# 268| m268_11(int) = InitializeParameter[size] : &:r268_10 +# 269| r269_1(glval) = VariableAddress[buf] : +# 269| r269_2(glval) = FunctionAddress[malloc] : +# 269| r269_3(glval) = VariableAddress[size] : +# 269| r269_4(int) = Load : &:r269_3, m268_11 +# 269| r269_5(void *) = Call : func:r269_2, 0:r269_4 +# 269| m269_6(unknown) = ^CallSideEffect : ~m268_4 +# 269| m269_7(unknown) = Chi : total:m268_4, partial:m269_6 +# 269| m269_8(unknown) = ^InitializeDynamicAllocation : &:r269_5 +# 269| m269_9(void *) = Store : &:r269_1, r269_5 +# 270| r270_1(glval) = FunctionAddress[memcpy] : +# 270| r270_2(glval) = VariableAddress[buf] : +# 270| r270_3(void *) = Load : &:r270_2, m269_9 +# 270| r270_4(glval) = VariableAddress[s] : +# 270| r270_5(void *) = Load : &:r270_4, m268_7 +# 270| r270_6(glval) = VariableAddress[size] : +# 270| r270_7(int) = Load : &:r270_6, m268_11 +# 270| r270_8(void *) = Call : func:r270_1, 0:r270_3, 1:r270_5, 2:r270_7 +# 270| v270_9(void) = ^SizedBufferReadSideEffect[1] : &:r270_5, r270_7, ~m268_9 +# 270| m270_10(unknown) = ^SizedBufferMustWriteSideEffect[0] : &:r270_3, r270_7 +# 270| m270_11(unknown) = Chi : total:m269_8, partial:m270_10 +# 271| r271_1(glval) = VariableAddress[#return] : +# 271| r271_2(glval) = VariableAddress[buf] : +# 271| r271_3(void *) = Load : &:r271_2, m269_9 +# 271| m271_4(void *) = Store : &:r271_1, r271_3 +# 268| v268_12(void) = ReturnIndirection : &:r268_8, m268_9 +# 268| r268_13(glval) = VariableAddress[#return] : +# 268| v268_14(void) = ReturnValue : &:r268_13, m271_4 +# 268| v268_15(void) = UnmodeledUse : mu* +# 268| v268_16(void) = AliasedUse : ~m269_7 +# 268| v268_17(void) = ExitFunction : diff --git a/cpp/ql/test/library-tests/ir/ssa/ssa.cpp b/cpp/ql/test/library-tests/ir/ssa/ssa.cpp index 5bdfd254e8f..ee75a2f28e4 100644 --- a/cpp/ql/test/library-tests/ir/ssa/ssa.cpp +++ b/cpp/ql/test/library-tests/ir/ssa/ssa.cpp @@ -262,3 +262,11 @@ char StringLiteralAliasing2(bool b) { const char* s = "Literal"; return s[2]; } + +void *malloc(int size); + +void *MallocAliasing(void *s, int size) { + void *buf = malloc(size); + memcpy(buf, s, size); + return buf; +} \ No newline at end of file diff --git a/cpp/ql/test/library-tests/ir/ssa/unaliased_ssa_ir.expected b/cpp/ql/test/library-tests/ir/ssa/unaliased_ssa_ir.expected index a0fd908c93b..824799db812 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 @@ -1147,3 +1147,44 @@ ssa.cpp: # 254| v254_9(void) = UnmodeledUse : mu* # 254| v254_10(void) = AliasedUse : ~mu254_4 # 254| v254_11(void) = ExitFunction : + +# 268| void* MallocAliasing(void*, int) +# 268| Block 0 +# 268| v268_1(void) = EnterFunction : +# 268| mu268_2(unknown) = AliasedDefinition : +# 268| mu268_3(unknown) = InitializeNonLocal : +# 268| mu268_4(unknown) = UnmodeledDefinition : +# 268| r268_5(glval) = VariableAddress[s] : +# 268| m268_6(void *) = InitializeParameter[s] : &:r268_5 +# 268| r268_7(void *) = Load : &:r268_5, m268_6 +# 268| mu268_8(unknown) = InitializeIndirection[s] : &:r268_7 +# 268| r268_9(glval) = VariableAddress[size] : +# 268| m268_10(int) = InitializeParameter[size] : &:r268_9 +# 269| r269_1(glval) = VariableAddress[buf] : +# 269| r269_2(glval) = FunctionAddress[malloc] : +# 269| r269_3(glval) = VariableAddress[size] : +# 269| r269_4(int) = Load : &:r269_3, m268_10 +# 269| r269_5(void *) = Call : func:r269_2, 0:r269_4 +# 269| mu269_6(unknown) = ^CallSideEffect : ~mu268_4 +# 269| mu269_7(unknown) = ^InitializeDynamicAllocation : &:r269_5 +# 269| m269_8(void *) = Store : &:r269_1, r269_5 +# 270| r270_1(glval) = FunctionAddress[memcpy] : +# 270| r270_2(glval) = VariableAddress[buf] : +# 270| r270_3(void *) = Load : &:r270_2, m269_8 +# 270| r270_4(glval) = VariableAddress[s] : +# 270| r270_5(void *) = Load : &:r270_4, m268_6 +# 270| r270_6(glval) = VariableAddress[size] : +# 270| r270_7(int) = Load : &:r270_6, m268_10 +# 270| r270_8(void *) = Call : func:r270_1, 0:r270_3, 1:r270_5, 2:r270_7 +# 270| v270_9(void) = ^SizedBufferReadSideEffect[1] : &:r270_5, r270_7, ~mu268_4 +# 270| mu270_10(unknown) = ^SizedBufferMustWriteSideEffect[0] : &:r270_3, r270_7 +# 271| r271_1(glval) = VariableAddress[#return] : +# 271| r271_2(glval) = VariableAddress[buf] : +# 271| r271_3(void *) = Load : &:r271_2, m269_8 +# 271| m271_4(void *) = Store : &:r271_1, r271_3 +# 268| v268_11(void) = ReturnIndirection : &:r268_7, ~mu268_4 +# 268| r268_12(glval) = VariableAddress[#return] : +# 268| v268_13(void) = ReturnValue : &:r268_12, m271_4 +# 268| v268_14(void) = UnmodeledUse : mu* +# 268| v268_15(void) = AliasedUse : ~mu268_4 +# 268| v268_16(void) = ExitFunction : diff --git a/cpp/ql/test/library-tests/ir/ssa/unaliased_ssa_ir_unsound.expected b/cpp/ql/test/library-tests/ir/ssa/unaliased_ssa_ir_unsound.expected index a0fd908c93b..824799db812 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 @@ -1147,3 +1147,44 @@ ssa.cpp: # 254| v254_9(void) = UnmodeledUse : mu* # 254| v254_10(void) = AliasedUse : ~mu254_4 # 254| v254_11(void) = ExitFunction : + +# 268| void* MallocAliasing(void*, int) +# 268| Block 0 +# 268| v268_1(void) = EnterFunction : +# 268| mu268_2(unknown) = AliasedDefinition : +# 268| mu268_3(unknown) = InitializeNonLocal : +# 268| mu268_4(unknown) = UnmodeledDefinition : +# 268| r268_5(glval) = VariableAddress[s] : +# 268| m268_6(void *) = InitializeParameter[s] : &:r268_5 +# 268| r268_7(void *) = Load : &:r268_5, m268_6 +# 268| mu268_8(unknown) = InitializeIndirection[s] : &:r268_7 +# 268| r268_9(glval) = VariableAddress[size] : +# 268| m268_10(int) = InitializeParameter[size] : &:r268_9 +# 269| r269_1(glval) = VariableAddress[buf] : +# 269| r269_2(glval) = FunctionAddress[malloc] : +# 269| r269_3(glval) = VariableAddress[size] : +# 269| r269_4(int) = Load : &:r269_3, m268_10 +# 269| r269_5(void *) = Call : func:r269_2, 0:r269_4 +# 269| mu269_6(unknown) = ^CallSideEffect : ~mu268_4 +# 269| mu269_7(unknown) = ^InitializeDynamicAllocation : &:r269_5 +# 269| m269_8(void *) = Store : &:r269_1, r269_5 +# 270| r270_1(glval) = FunctionAddress[memcpy] : +# 270| r270_2(glval) = VariableAddress[buf] : +# 270| r270_3(void *) = Load : &:r270_2, m269_8 +# 270| r270_4(glval) = VariableAddress[s] : +# 270| r270_5(void *) = Load : &:r270_4, m268_6 +# 270| r270_6(glval) = VariableAddress[size] : +# 270| r270_7(int) = Load : &:r270_6, m268_10 +# 270| r270_8(void *) = Call : func:r270_1, 0:r270_3, 1:r270_5, 2:r270_7 +# 270| v270_9(void) = ^SizedBufferReadSideEffect[1] : &:r270_5, r270_7, ~mu268_4 +# 270| mu270_10(unknown) = ^SizedBufferMustWriteSideEffect[0] : &:r270_3, r270_7 +# 271| r271_1(glval) = VariableAddress[#return] : +# 271| r271_2(glval) = VariableAddress[buf] : +# 271| r271_3(void *) = Load : &:r271_2, m269_8 +# 271| m271_4(void *) = Store : &:r271_1, r271_3 +# 268| v268_11(void) = ReturnIndirection : &:r268_7, ~mu268_4 +# 268| r268_12(glval) = VariableAddress[#return] : +# 268| v268_13(void) = ReturnValue : &:r268_12, m271_4 +# 268| v268_14(void) = UnmodeledUse : mu* +# 268| v268_15(void) = AliasedUse : ~mu268_4 +# 268| v268_16(void) = ExitFunction : diff --git a/cpp/ql/test/library-tests/lambdas/captures/elements.expected b/cpp/ql/test/library-tests/lambdas/captures/elements.expected index 598dec39fe5..1720fa2ca8e 100644 --- a/cpp/ql/test/library-tests/lambdas/captures/elements.expected +++ b/cpp/ql/test/library-tests/lambdas/captures/elements.expected @@ -213,11 +213,9 @@ | file://:0:0:0:0 | const lambda [] type at line 3, col. 5 | | file://:0:0:0:0 | const lambda [] type at line 3, col. 5 & | | file://:0:0:0:0 | const lambda [] type at line 3, col. 5 * | -| file://:0:0:0:0 | const lambda [] type at line 3, col. 5 *const | | file://:0:0:0:0 | const lambda [] type at line 9, col. 5 | | file://:0:0:0:0 | const lambda [] type at line 9, col. 5 & | | file://:0:0:0:0 | const lambda [] type at line 9, col. 5 * | -| file://:0:0:0:0 | const lambda [] type at line 9, col. 5 *const | | file://:0:0:0:0 | const lambda [] type at line 9, col. 15 | | file://:0:0:0:0 | const lambda [] type at line 9, col. 15 & | | file://:0:0:0:0 | const lambda [] type at line 15, col. 5 | @@ -225,7 +223,6 @@ | file://:0:0:0:0 | const lambda [] type at line 22, col. 19 | | file://:0:0:0:0 | const lambda [] type at line 22, col. 19 & | | file://:0:0:0:0 | const lambda [] type at line 22, col. 19 * | -| file://:0:0:0:0 | const lambda [] type at line 22, col. 19 *const | | file://:0:0:0:0 | declaration of 1st parameter | | file://:0:0:0:0 | declaration of 1st parameter | | file://:0:0:0:0 | declaration of 1st parameter | diff --git a/cpp/ql/test/library-tests/templates/instantiations_functions/elements.expected b/cpp/ql/test/library-tests/templates/instantiations_functions/elements.expected index df99979ff44..52af59bd1f7 100644 --- a/cpp/ql/test/library-tests/templates/instantiations_functions/elements.expected +++ b/cpp/ql/test/library-tests/templates/instantiations_functions/elements.expected @@ -39,17 +39,13 @@ | file://:0:0:0:0 | __va_list_tag && | | file://:0:0:0:0 | abstract | | file://:0:0:0:0 | action * | -| file://:0:0:0:0 | action *const | | file://:0:0:0:0 | action>> & | | file://:0:0:0:0 | action>> && | | file://:0:0:0:0 | action>> * | -| file://:0:0:0:0 | action>> *const | | file://:0:0:0:0 | actor1 * | -| file://:0:0:0:0 | actor1 *const | | file://:0:0:0:0 | actor1> & | | file://:0:0:0:0 | actor1> && | | file://:0:0:0:0 | actor1> * | -| file://:0:0:0:0 | actor1> *const | | file://:0:0:0:0 | atomic | | file://:0:0:0:0 | auto | | file://:0:0:0:0 | auto | diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-190/semmle/TaintedAllocationSize/TaintedAllocationSize.expected b/cpp/ql/test/query-tests/Security/CWE/CWE-190/semmle/TaintedAllocationSize/TaintedAllocationSize.expected index 2463fad2e2b..f77bf73d52d 100644 --- a/cpp/ql/test/query-tests/Security/CWE/CWE-190/semmle/TaintedAllocationSize/TaintedAllocationSize.expected +++ b/cpp/ql/test/query-tests/Security/CWE/CWE-190/semmle/TaintedAllocationSize/TaintedAllocationSize.expected @@ -6,3 +6,4 @@ | test.cpp:49:17:49:30 | new[] | This allocation size is derived from $@ and might overflow | test.cpp:39:21:39:24 | argv | user input (argv) | | test.cpp:52:21:52:27 | call to realloc | This allocation size is derived from $@ and might overflow | test.cpp:39:21:39:24 | argv | user input (argv) | | test.cpp:52:35:52:60 | ... * ... | This allocation size is derived from $@ and might overflow | test.cpp:39:21:39:24 | argv | user input (argv) | +| test.cpp:127:17:127:22 | call to malloc | This allocation size is derived from $@ and might overflow | test.cpp:123:25:123:30 | call to getenv | user input (getenv) | diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-190/semmle/TaintedAllocationSize/test.cpp b/cpp/ql/test/query-tests/Security/CWE/CWE-190/semmle/TaintedAllocationSize/test.cpp index 0722d95cf46..5cd5f0c0246 100644 --- a/cpp/ql/test/query-tests/Security/CWE/CWE-190/semmle/TaintedAllocationSize/test.cpp +++ b/cpp/ql/test/query-tests/Security/CWE/CWE-190/semmle/TaintedAllocationSize/test.cpp @@ -105,3 +105,24 @@ void processFile() fclose(f); } } + +char *getenv(const char *name); + +#define MAX_SIZE 500 + +int bounded(int x, int limit) { + int result = x; + if (x <= 0) + result = 1; + else if (x > limit) + result = limit; + return result; +} + +void open_file_bounded () { + int size = size = atoi(getenv("USER")); + int bounded_size = bounded(size, MAX_SIZE); + + int* a = (int*)malloc(bounded_size); // GOOD + int* b = (int*)malloc(size); // BAD +} \ No newline at end of file diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-190/semmle/wider_type/test.c b/cpp/ql/test/query-tests/Security/CWE/CWE-190/semmle/wider_type/test.c index 88fe22c3b31..5802ca3eb4f 100644 --- a/cpp/ql/test/query-tests/Security/CWE/CWE-190/semmle/wider_type/test.c +++ b/cpp/ql/test/query-tests/Security/CWE/CWE-190/semmle/wider_type/test.c @@ -69,3 +69,10 @@ void test10(int x) { } while (0); } } + +extern const int const256; + +void test11() { + short s; + for(s = 0; s < const256; ++s) {} +} \ No newline at end of file diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-190/semmle/wider_type/test2.c b/cpp/ql/test/query-tests/Security/CWE/CWE-190/semmle/wider_type/test2.c new file mode 100644 index 00000000000..7a9609c9a3b --- /dev/null +++ b/cpp/ql/test/query-tests/Security/CWE/CWE-190/semmle/wider_type/test2.c @@ -0,0 +1 @@ +const int const256 = 256; \ No newline at end of file diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/LocalFunction.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/LocalFunction.cs index f58c027eca5..e570802ab3d 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/LocalFunction.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/LocalFunction.cs @@ -1,5 +1,6 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp.Syntax; +using System; using System.IO; using System.Linq; @@ -13,16 +14,12 @@ namespace Semmle.Extraction.CSharp.Entities public override void WriteId(TextWriter trapFile) { - trapFile.WriteSubId(ContainingType); - trapFile.Write("."); - trapFile.WriteSubId(Location); - if (symbol.IsGenericMethod && !IsSourceDeclaration) - { - trapFile.Write('<'); - trapFile.BuildList(",", symbol.TypeArguments, (ta, tb0) => AddSignatureTypeToId(Context, tb0, symbol, ta)); - trapFile.Write('>'); - } - trapFile.Write(";localfunction"); + throw new InvalidOperationException(); + } + + public override void WriteQuotedId(TextWriter trapFile) + { + trapFile.Write('*'); } public static new LocalFunction Create(Context cx, IMethodSymbol field) => LocalFunctionFactory.Instance.CreateEntity(cx, field); diff --git a/csharp/ql/src/experimental/README.md b/csharp/ql/src/experimental/README.md new file mode 100644 index 00000000000..fc07363b24f --- /dev/null +++ b/csharp/ql/src/experimental/README.md @@ -0,0 +1 @@ +This directory contains [experimental](../../../../docs/experimental.md) CodeQL queries and libraries. 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 5eb98aefe4e..e3f1da355ef 100644 --- a/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl.qll +++ b/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl.qll @@ -1307,15 +1307,20 @@ private predicate localFlowExit(Node node, Configuration config) { */ pragma[nomagic] private predicate localFlowStepPlus( - Node node1, Node node2, boolean preservesValue, Configuration config, LocalCallContext cc + Node node1, Node node2, boolean preservesValue, DataFlowType t, Configuration config, + LocalCallContext cc ) { not isUnreachableInCall(node2, cc.(LocalCallContextSpecificCall).getCall()) and ( localFlowEntry(node1, config) and ( - localFlowStep(node1, node2, config) and preservesValue = true + localFlowStep(node1, node2, config) and + preservesValue = true and + t = getErasedNodeTypeBound(node1) or - additionalLocalFlowStep(node1, node2, config) and preservesValue = false + additionalLocalFlowStep(node1, node2, config) and + preservesValue = false and + t = getErasedNodeTypeBound(node2) ) and node1 != node2 and cc.relevantFor(node1.getEnclosingCallable()) and @@ -1323,17 +1328,18 @@ private predicate localFlowStepPlus( nodeCand(TNormalNode(node2), unbind(config)) or exists(Node mid | - localFlowStepPlus(node1, mid, preservesValue, config, cc) and + localFlowStepPlus(node1, mid, preservesValue, t, config, cc) and localFlowStep(mid, node2, config) and not mid instanceof CastNode and nodeCand(TNormalNode(node2), unbind(config)) ) or exists(Node mid | - localFlowStepPlus(node1, mid, _, config, cc) and + localFlowStepPlus(node1, mid, _, _, config, cc) and additionalLocalFlowStep(mid, node2, config) and not mid instanceof CastNode and preservesValue = false and + t = getErasedNodeTypeBound(node2) and nodeCand(TNormalNode(node2), unbind(config)) ) ) @@ -1345,17 +1351,18 @@ private predicate localFlowStepPlus( */ pragma[nomagic] private predicate localFlowBigStep( - Node node1, Node node2, boolean preservesValue, Configuration config, LocalCallContext callContext + Node node1, Node node2, boolean preservesValue, DataFlowType t, Configuration config, + LocalCallContext callContext ) { - localFlowStepPlus(node1, node2, preservesValue, config, callContext) and + localFlowStepPlus(node1, node2, preservesValue, t, config, callContext) and localFlowExit(node2, config) } pragma[nomagic] private predicate localFlowBigStepExt( - NodeExt node1, NodeExt node2, boolean preservesValue, Configuration config + NodeExt node1, NodeExt node2, boolean preservesValue, AccessPathFrontNil apf, Configuration config ) { - localFlowBigStep(node1.getNode(), node2.getNode(), preservesValue, config, _) + localFlowBigStep(node1.getNode(), node2.getNode(), preservesValue, apf.getType(), config, _) } private newtype TAccessPathFront = @@ -1395,46 +1402,24 @@ private predicate flowCandFwd( else any() } -/** - * A node that requires an empty access path and should have its tracked type - * (re-)computed. This is either a source or a node reached through an - * additional step. - */ -private class AccessPathFrontNilNode extends NormalNodeExt { - AccessPathFrontNilNode() { - nodeCand(this, _) and - ( - any(Configuration c).isSource(this.getNode()) - or - localFlowBigStepExt(_, this, false, _) - or - additionalJumpStepExt(_, this, _) - ) - } - - /** Gets the `nil` path front for this node. */ - AccessPathFrontNil getApf() { result = TFrontNil(this.getErasedNodeTypeBound()) } -} - private predicate flowCandFwd0( NodeExt node, boolean fromArg, AccessPathFront apf, Configuration config ) { nodeCand2(node, _, false, config) and config.isSource(node.getNode()) and fromArg = false and - apf = node.(AccessPathFrontNilNode).getApf() + apf = TFrontNil(node.getErasedNodeTypeBound()) or nodeCand(node, unbind(config)) and ( exists(NodeExt mid | flowCandFwd(mid, fromArg, apf, config) and - localFlowBigStepExt(mid, node, true, config) + localFlowBigStepExt(mid, node, true, _, config) ) or exists(NodeExt mid, AccessPathFrontNil nil | flowCandFwd(mid, fromArg, nil, config) and - localFlowBigStepExt(mid, node, false, config) and - apf = node.(AccessPathFrontNilNode).getApf() + localFlowBigStepExt(mid, node, false, apf, config) ) or exists(NodeExt mid | @@ -1447,7 +1432,7 @@ private predicate flowCandFwd0( flowCandFwd(mid, _, nil, config) and additionalJumpStepExt(mid, node, config) and fromArg = false and - apf = node.(AccessPathFrontNilNode).getApf() + apf = TFrontNil(node.getErasedNodeTypeBound()) ) or exists(NodeExt mid, boolean allowsFieldFlow | @@ -1589,13 +1574,13 @@ private predicate flowCand0( apf instanceof AccessPathFrontNil or exists(NodeExt mid | - localFlowBigStepExt(node, mid, true, config) and + localFlowBigStepExt(node, mid, true, _, config) and flowCand(mid, toReturn, apf, config) ) or exists(NodeExt mid, AccessPathFrontNil nil | flowCandFwd(node, _, apf, config) and - localFlowBigStepExt(node, mid, false, config) and + localFlowBigStepExt(node, mid, false, _, config) and flowCand(mid, toReturn, nil, config) and apf instanceof AccessPathFrontNil ) @@ -1810,18 +1795,6 @@ private predicate popWithFront(AccessPath ap0, Content f, AccessPathFront apf, A /** Gets the access path obtained by pushing `f` onto `ap`. */ private AccessPath push(Content f, AccessPath ap) { ap = pop(f, result) } -/** - * A node that requires an empty access path and should have its tracked type - * (re-)computed. This is either a source or a node reached through an - * additional step. - */ -private class AccessPathNilNode extends NormalNodeExt { - AccessPathNilNode() { flowCand(this.(AccessPathFrontNilNode), _, _, _) } - - /** Gets the `nil` path for this node. */ - AccessPathNil getAp() { result = TNil(this.getErasedNodeTypeBound()) } -} - /** * Holds if data can flow from a source to `node` with the given `ap`. */ @@ -1838,20 +1811,19 @@ private predicate flowFwd0( flowCand(node, _, _, config) and config.isSource(node.getNode()) and fromArg = false and - ap = node.(AccessPathNilNode).getAp() and + ap = TNil(node.getErasedNodeTypeBound()) and apf = ap.(AccessPathNil).getFront() or flowCand(node, _, _, unbind(config)) and ( exists(NodeExt mid | flowFwd(mid, fromArg, apf, ap, config) and - localFlowBigStepExt(mid, node, true, config) + localFlowBigStepExt(mid, node, true, _, config) ) or exists(NodeExt mid, AccessPathNil nil | flowFwd(mid, fromArg, _, nil, config) and - localFlowBigStepExt(mid, node, false, config) and - ap = node.(AccessPathNilNode).getAp() and + localFlowBigStepExt(mid, node, false, apf, config) and apf = ap.(AccessPathNil).getFront() ) or @@ -1865,7 +1837,7 @@ private predicate flowFwd0( flowFwd(mid, _, _, nil, config) and additionalJumpStepExt(mid, node, config) and fromArg = false and - ap = node.(AccessPathNilNode).getAp() and + ap = TNil(node.getErasedNodeTypeBound()) and apf = ap.(AccessPathNil).getFront() ) or @@ -1982,13 +1954,13 @@ private predicate flow0(NodeExt node, boolean toReturn, AccessPath ap, Configura ap instanceof AccessPathNil or exists(NodeExt mid | - localFlowBigStepExt(node, mid, true, config) and + localFlowBigStepExt(node, mid, true, _, config) and flow(mid, toReturn, ap, config) ) or exists(NodeExt mid, AccessPathNil nil | flowFwd(node, _, _, ap, config) and - localFlowBigStepExt(node, mid, false, config) and + localFlowBigStepExt(node, mid, false, _, config) and flow(mid, toReturn, nil, config) and ap instanceof AccessPathNil ) @@ -2164,7 +2136,7 @@ private newtype TPathNode = config.isSource(node) and cc instanceof CallContextAny and sc instanceof SummaryCtxNone and - ap = any(AccessPathNilNode nil | nil.getNode() = node).getAp() + ap = TNil(getErasedNodeTypeBound(node)) or // ... or a step from an existing PathNode to another node. exists(PathNodeMid mid | @@ -2357,12 +2329,11 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt pathIntoLocalStep(mid, midnode, cc, enclosing, sc, ap0, conf) and localCC = getLocalCallContext(cc, enclosing) | - localFlowBigStep(midnode, node, true, conf, localCC) and + localFlowBigStep(midnode, node, true, _, conf, localCC) and ap = ap0 or - localFlowBigStep(midnode, node, false, conf, localCC) and - ap0 instanceof AccessPathNil and - ap = any(AccessPathNilNode nil | nil.getNode() = node).getAp() + localFlowBigStep(midnode, node, false, ap.(AccessPathNil).getType(), conf, localCC) and + ap0 instanceof AccessPathNil ) or jumpStep(mid.getNode(), node, mid.getConfiguration()) and @@ -2374,7 +2345,7 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt cc instanceof CallContextAny and sc instanceof SummaryCtxNone and mid.getAp() instanceof AccessPathNil and - ap = any(AccessPathNilNode nil | nil.getNode() = node).getAp() + ap = TNil(getErasedNodeTypeBound(node)) or exists(Content f, AccessPath ap0 | pathReadStep(mid, node, ap0, f, cc) and ap = pop(f, ap0)) and sc = mid.getSummaryCtx() @@ -2397,7 +2368,7 @@ private predicate pathIntoLocalStep( midnode = mid.getNode() and cc = mid.getCallContext() and conf = mid.getConfiguration() and - localFlowBigStep(midnode, _, _, conf, _) and + localFlowBigStep(midnode, _, _, _, conf, _) and enclosing = midnode.getEnclosingCallable() and sc = mid.getSummaryCtx() and ap0 = mid.getAp() @@ -2949,7 +2920,7 @@ private module FlowExploration { config = mid.getConfiguration() } - pragma[noinline] + pragma[nomagic] private predicate partialPathOutOfCallable1( PartialPathNodePriv mid, DataFlowCall call, ReturnKindExt kind, CallContext cc, PartialAccessPath ap, Configuration config 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 5eb98aefe4e..e3f1da355ef 100644 --- a/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl2.qll +++ b/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl2.qll @@ -1307,15 +1307,20 @@ private predicate localFlowExit(Node node, Configuration config) { */ pragma[nomagic] private predicate localFlowStepPlus( - Node node1, Node node2, boolean preservesValue, Configuration config, LocalCallContext cc + Node node1, Node node2, boolean preservesValue, DataFlowType t, Configuration config, + LocalCallContext cc ) { not isUnreachableInCall(node2, cc.(LocalCallContextSpecificCall).getCall()) and ( localFlowEntry(node1, config) and ( - localFlowStep(node1, node2, config) and preservesValue = true + localFlowStep(node1, node2, config) and + preservesValue = true and + t = getErasedNodeTypeBound(node1) or - additionalLocalFlowStep(node1, node2, config) and preservesValue = false + additionalLocalFlowStep(node1, node2, config) and + preservesValue = false and + t = getErasedNodeTypeBound(node2) ) and node1 != node2 and cc.relevantFor(node1.getEnclosingCallable()) and @@ -1323,17 +1328,18 @@ private predicate localFlowStepPlus( nodeCand(TNormalNode(node2), unbind(config)) or exists(Node mid | - localFlowStepPlus(node1, mid, preservesValue, config, cc) and + localFlowStepPlus(node1, mid, preservesValue, t, config, cc) and localFlowStep(mid, node2, config) and not mid instanceof CastNode and nodeCand(TNormalNode(node2), unbind(config)) ) or exists(Node mid | - localFlowStepPlus(node1, mid, _, config, cc) and + localFlowStepPlus(node1, mid, _, _, config, cc) and additionalLocalFlowStep(mid, node2, config) and not mid instanceof CastNode and preservesValue = false and + t = getErasedNodeTypeBound(node2) and nodeCand(TNormalNode(node2), unbind(config)) ) ) @@ -1345,17 +1351,18 @@ private predicate localFlowStepPlus( */ pragma[nomagic] private predicate localFlowBigStep( - Node node1, Node node2, boolean preservesValue, Configuration config, LocalCallContext callContext + Node node1, Node node2, boolean preservesValue, DataFlowType t, Configuration config, + LocalCallContext callContext ) { - localFlowStepPlus(node1, node2, preservesValue, config, callContext) and + localFlowStepPlus(node1, node2, preservesValue, t, config, callContext) and localFlowExit(node2, config) } pragma[nomagic] private predicate localFlowBigStepExt( - NodeExt node1, NodeExt node2, boolean preservesValue, Configuration config + NodeExt node1, NodeExt node2, boolean preservesValue, AccessPathFrontNil apf, Configuration config ) { - localFlowBigStep(node1.getNode(), node2.getNode(), preservesValue, config, _) + localFlowBigStep(node1.getNode(), node2.getNode(), preservesValue, apf.getType(), config, _) } private newtype TAccessPathFront = @@ -1395,46 +1402,24 @@ private predicate flowCandFwd( else any() } -/** - * A node that requires an empty access path and should have its tracked type - * (re-)computed. This is either a source or a node reached through an - * additional step. - */ -private class AccessPathFrontNilNode extends NormalNodeExt { - AccessPathFrontNilNode() { - nodeCand(this, _) and - ( - any(Configuration c).isSource(this.getNode()) - or - localFlowBigStepExt(_, this, false, _) - or - additionalJumpStepExt(_, this, _) - ) - } - - /** Gets the `nil` path front for this node. */ - AccessPathFrontNil getApf() { result = TFrontNil(this.getErasedNodeTypeBound()) } -} - private predicate flowCandFwd0( NodeExt node, boolean fromArg, AccessPathFront apf, Configuration config ) { nodeCand2(node, _, false, config) and config.isSource(node.getNode()) and fromArg = false and - apf = node.(AccessPathFrontNilNode).getApf() + apf = TFrontNil(node.getErasedNodeTypeBound()) or nodeCand(node, unbind(config)) and ( exists(NodeExt mid | flowCandFwd(mid, fromArg, apf, config) and - localFlowBigStepExt(mid, node, true, config) + localFlowBigStepExt(mid, node, true, _, config) ) or exists(NodeExt mid, AccessPathFrontNil nil | flowCandFwd(mid, fromArg, nil, config) and - localFlowBigStepExt(mid, node, false, config) and - apf = node.(AccessPathFrontNilNode).getApf() + localFlowBigStepExt(mid, node, false, apf, config) ) or exists(NodeExt mid | @@ -1447,7 +1432,7 @@ private predicate flowCandFwd0( flowCandFwd(mid, _, nil, config) and additionalJumpStepExt(mid, node, config) and fromArg = false and - apf = node.(AccessPathFrontNilNode).getApf() + apf = TFrontNil(node.getErasedNodeTypeBound()) ) or exists(NodeExt mid, boolean allowsFieldFlow | @@ -1589,13 +1574,13 @@ private predicate flowCand0( apf instanceof AccessPathFrontNil or exists(NodeExt mid | - localFlowBigStepExt(node, mid, true, config) and + localFlowBigStepExt(node, mid, true, _, config) and flowCand(mid, toReturn, apf, config) ) or exists(NodeExt mid, AccessPathFrontNil nil | flowCandFwd(node, _, apf, config) and - localFlowBigStepExt(node, mid, false, config) and + localFlowBigStepExt(node, mid, false, _, config) and flowCand(mid, toReturn, nil, config) and apf instanceof AccessPathFrontNil ) @@ -1810,18 +1795,6 @@ private predicate popWithFront(AccessPath ap0, Content f, AccessPathFront apf, A /** Gets the access path obtained by pushing `f` onto `ap`. */ private AccessPath push(Content f, AccessPath ap) { ap = pop(f, result) } -/** - * A node that requires an empty access path and should have its tracked type - * (re-)computed. This is either a source or a node reached through an - * additional step. - */ -private class AccessPathNilNode extends NormalNodeExt { - AccessPathNilNode() { flowCand(this.(AccessPathFrontNilNode), _, _, _) } - - /** Gets the `nil` path for this node. */ - AccessPathNil getAp() { result = TNil(this.getErasedNodeTypeBound()) } -} - /** * Holds if data can flow from a source to `node` with the given `ap`. */ @@ -1838,20 +1811,19 @@ private predicate flowFwd0( flowCand(node, _, _, config) and config.isSource(node.getNode()) and fromArg = false and - ap = node.(AccessPathNilNode).getAp() and + ap = TNil(node.getErasedNodeTypeBound()) and apf = ap.(AccessPathNil).getFront() or flowCand(node, _, _, unbind(config)) and ( exists(NodeExt mid | flowFwd(mid, fromArg, apf, ap, config) and - localFlowBigStepExt(mid, node, true, config) + localFlowBigStepExt(mid, node, true, _, config) ) or exists(NodeExt mid, AccessPathNil nil | flowFwd(mid, fromArg, _, nil, config) and - localFlowBigStepExt(mid, node, false, config) and - ap = node.(AccessPathNilNode).getAp() and + localFlowBigStepExt(mid, node, false, apf, config) and apf = ap.(AccessPathNil).getFront() ) or @@ -1865,7 +1837,7 @@ private predicate flowFwd0( flowFwd(mid, _, _, nil, config) and additionalJumpStepExt(mid, node, config) and fromArg = false and - ap = node.(AccessPathNilNode).getAp() and + ap = TNil(node.getErasedNodeTypeBound()) and apf = ap.(AccessPathNil).getFront() ) or @@ -1982,13 +1954,13 @@ private predicate flow0(NodeExt node, boolean toReturn, AccessPath ap, Configura ap instanceof AccessPathNil or exists(NodeExt mid | - localFlowBigStepExt(node, mid, true, config) and + localFlowBigStepExt(node, mid, true, _, config) and flow(mid, toReturn, ap, config) ) or exists(NodeExt mid, AccessPathNil nil | flowFwd(node, _, _, ap, config) and - localFlowBigStepExt(node, mid, false, config) and + localFlowBigStepExt(node, mid, false, _, config) and flow(mid, toReturn, nil, config) and ap instanceof AccessPathNil ) @@ -2164,7 +2136,7 @@ private newtype TPathNode = config.isSource(node) and cc instanceof CallContextAny and sc instanceof SummaryCtxNone and - ap = any(AccessPathNilNode nil | nil.getNode() = node).getAp() + ap = TNil(getErasedNodeTypeBound(node)) or // ... or a step from an existing PathNode to another node. exists(PathNodeMid mid | @@ -2357,12 +2329,11 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt pathIntoLocalStep(mid, midnode, cc, enclosing, sc, ap0, conf) and localCC = getLocalCallContext(cc, enclosing) | - localFlowBigStep(midnode, node, true, conf, localCC) and + localFlowBigStep(midnode, node, true, _, conf, localCC) and ap = ap0 or - localFlowBigStep(midnode, node, false, conf, localCC) and - ap0 instanceof AccessPathNil and - ap = any(AccessPathNilNode nil | nil.getNode() = node).getAp() + localFlowBigStep(midnode, node, false, ap.(AccessPathNil).getType(), conf, localCC) and + ap0 instanceof AccessPathNil ) or jumpStep(mid.getNode(), node, mid.getConfiguration()) and @@ -2374,7 +2345,7 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt cc instanceof CallContextAny and sc instanceof SummaryCtxNone and mid.getAp() instanceof AccessPathNil and - ap = any(AccessPathNilNode nil | nil.getNode() = node).getAp() + ap = TNil(getErasedNodeTypeBound(node)) or exists(Content f, AccessPath ap0 | pathReadStep(mid, node, ap0, f, cc) and ap = pop(f, ap0)) and sc = mid.getSummaryCtx() @@ -2397,7 +2368,7 @@ private predicate pathIntoLocalStep( midnode = mid.getNode() and cc = mid.getCallContext() and conf = mid.getConfiguration() and - localFlowBigStep(midnode, _, _, conf, _) and + localFlowBigStep(midnode, _, _, _, conf, _) and enclosing = midnode.getEnclosingCallable() and sc = mid.getSummaryCtx() and ap0 = mid.getAp() @@ -2949,7 +2920,7 @@ private module FlowExploration { config = mid.getConfiguration() } - pragma[noinline] + pragma[nomagic] private predicate partialPathOutOfCallable1( PartialPathNodePriv mid, DataFlowCall call, ReturnKindExt kind, CallContext cc, PartialAccessPath ap, Configuration config 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 5eb98aefe4e..e3f1da355ef 100644 --- a/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl3.qll +++ b/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl3.qll @@ -1307,15 +1307,20 @@ private predicate localFlowExit(Node node, Configuration config) { */ pragma[nomagic] private predicate localFlowStepPlus( - Node node1, Node node2, boolean preservesValue, Configuration config, LocalCallContext cc + Node node1, Node node2, boolean preservesValue, DataFlowType t, Configuration config, + LocalCallContext cc ) { not isUnreachableInCall(node2, cc.(LocalCallContextSpecificCall).getCall()) and ( localFlowEntry(node1, config) and ( - localFlowStep(node1, node2, config) and preservesValue = true + localFlowStep(node1, node2, config) and + preservesValue = true and + t = getErasedNodeTypeBound(node1) or - additionalLocalFlowStep(node1, node2, config) and preservesValue = false + additionalLocalFlowStep(node1, node2, config) and + preservesValue = false and + t = getErasedNodeTypeBound(node2) ) and node1 != node2 and cc.relevantFor(node1.getEnclosingCallable()) and @@ -1323,17 +1328,18 @@ private predicate localFlowStepPlus( nodeCand(TNormalNode(node2), unbind(config)) or exists(Node mid | - localFlowStepPlus(node1, mid, preservesValue, config, cc) and + localFlowStepPlus(node1, mid, preservesValue, t, config, cc) and localFlowStep(mid, node2, config) and not mid instanceof CastNode and nodeCand(TNormalNode(node2), unbind(config)) ) or exists(Node mid | - localFlowStepPlus(node1, mid, _, config, cc) and + localFlowStepPlus(node1, mid, _, _, config, cc) and additionalLocalFlowStep(mid, node2, config) and not mid instanceof CastNode and preservesValue = false and + t = getErasedNodeTypeBound(node2) and nodeCand(TNormalNode(node2), unbind(config)) ) ) @@ -1345,17 +1351,18 @@ private predicate localFlowStepPlus( */ pragma[nomagic] private predicate localFlowBigStep( - Node node1, Node node2, boolean preservesValue, Configuration config, LocalCallContext callContext + Node node1, Node node2, boolean preservesValue, DataFlowType t, Configuration config, + LocalCallContext callContext ) { - localFlowStepPlus(node1, node2, preservesValue, config, callContext) and + localFlowStepPlus(node1, node2, preservesValue, t, config, callContext) and localFlowExit(node2, config) } pragma[nomagic] private predicate localFlowBigStepExt( - NodeExt node1, NodeExt node2, boolean preservesValue, Configuration config + NodeExt node1, NodeExt node2, boolean preservesValue, AccessPathFrontNil apf, Configuration config ) { - localFlowBigStep(node1.getNode(), node2.getNode(), preservesValue, config, _) + localFlowBigStep(node1.getNode(), node2.getNode(), preservesValue, apf.getType(), config, _) } private newtype TAccessPathFront = @@ -1395,46 +1402,24 @@ private predicate flowCandFwd( else any() } -/** - * A node that requires an empty access path and should have its tracked type - * (re-)computed. This is either a source or a node reached through an - * additional step. - */ -private class AccessPathFrontNilNode extends NormalNodeExt { - AccessPathFrontNilNode() { - nodeCand(this, _) and - ( - any(Configuration c).isSource(this.getNode()) - or - localFlowBigStepExt(_, this, false, _) - or - additionalJumpStepExt(_, this, _) - ) - } - - /** Gets the `nil` path front for this node. */ - AccessPathFrontNil getApf() { result = TFrontNil(this.getErasedNodeTypeBound()) } -} - private predicate flowCandFwd0( NodeExt node, boolean fromArg, AccessPathFront apf, Configuration config ) { nodeCand2(node, _, false, config) and config.isSource(node.getNode()) and fromArg = false and - apf = node.(AccessPathFrontNilNode).getApf() + apf = TFrontNil(node.getErasedNodeTypeBound()) or nodeCand(node, unbind(config)) and ( exists(NodeExt mid | flowCandFwd(mid, fromArg, apf, config) and - localFlowBigStepExt(mid, node, true, config) + localFlowBigStepExt(mid, node, true, _, config) ) or exists(NodeExt mid, AccessPathFrontNil nil | flowCandFwd(mid, fromArg, nil, config) and - localFlowBigStepExt(mid, node, false, config) and - apf = node.(AccessPathFrontNilNode).getApf() + localFlowBigStepExt(mid, node, false, apf, config) ) or exists(NodeExt mid | @@ -1447,7 +1432,7 @@ private predicate flowCandFwd0( flowCandFwd(mid, _, nil, config) and additionalJumpStepExt(mid, node, config) and fromArg = false and - apf = node.(AccessPathFrontNilNode).getApf() + apf = TFrontNil(node.getErasedNodeTypeBound()) ) or exists(NodeExt mid, boolean allowsFieldFlow | @@ -1589,13 +1574,13 @@ private predicate flowCand0( apf instanceof AccessPathFrontNil or exists(NodeExt mid | - localFlowBigStepExt(node, mid, true, config) and + localFlowBigStepExt(node, mid, true, _, config) and flowCand(mid, toReturn, apf, config) ) or exists(NodeExt mid, AccessPathFrontNil nil | flowCandFwd(node, _, apf, config) and - localFlowBigStepExt(node, mid, false, config) and + localFlowBigStepExt(node, mid, false, _, config) and flowCand(mid, toReturn, nil, config) and apf instanceof AccessPathFrontNil ) @@ -1810,18 +1795,6 @@ private predicate popWithFront(AccessPath ap0, Content f, AccessPathFront apf, A /** Gets the access path obtained by pushing `f` onto `ap`. */ private AccessPath push(Content f, AccessPath ap) { ap = pop(f, result) } -/** - * A node that requires an empty access path and should have its tracked type - * (re-)computed. This is either a source or a node reached through an - * additional step. - */ -private class AccessPathNilNode extends NormalNodeExt { - AccessPathNilNode() { flowCand(this.(AccessPathFrontNilNode), _, _, _) } - - /** Gets the `nil` path for this node. */ - AccessPathNil getAp() { result = TNil(this.getErasedNodeTypeBound()) } -} - /** * Holds if data can flow from a source to `node` with the given `ap`. */ @@ -1838,20 +1811,19 @@ private predicate flowFwd0( flowCand(node, _, _, config) and config.isSource(node.getNode()) and fromArg = false and - ap = node.(AccessPathNilNode).getAp() and + ap = TNil(node.getErasedNodeTypeBound()) and apf = ap.(AccessPathNil).getFront() or flowCand(node, _, _, unbind(config)) and ( exists(NodeExt mid | flowFwd(mid, fromArg, apf, ap, config) and - localFlowBigStepExt(mid, node, true, config) + localFlowBigStepExt(mid, node, true, _, config) ) or exists(NodeExt mid, AccessPathNil nil | flowFwd(mid, fromArg, _, nil, config) and - localFlowBigStepExt(mid, node, false, config) and - ap = node.(AccessPathNilNode).getAp() and + localFlowBigStepExt(mid, node, false, apf, config) and apf = ap.(AccessPathNil).getFront() ) or @@ -1865,7 +1837,7 @@ private predicate flowFwd0( flowFwd(mid, _, _, nil, config) and additionalJumpStepExt(mid, node, config) and fromArg = false and - ap = node.(AccessPathNilNode).getAp() and + ap = TNil(node.getErasedNodeTypeBound()) and apf = ap.(AccessPathNil).getFront() ) or @@ -1982,13 +1954,13 @@ private predicate flow0(NodeExt node, boolean toReturn, AccessPath ap, Configura ap instanceof AccessPathNil or exists(NodeExt mid | - localFlowBigStepExt(node, mid, true, config) and + localFlowBigStepExt(node, mid, true, _, config) and flow(mid, toReturn, ap, config) ) or exists(NodeExt mid, AccessPathNil nil | flowFwd(node, _, _, ap, config) and - localFlowBigStepExt(node, mid, false, config) and + localFlowBigStepExt(node, mid, false, _, config) and flow(mid, toReturn, nil, config) and ap instanceof AccessPathNil ) @@ -2164,7 +2136,7 @@ private newtype TPathNode = config.isSource(node) and cc instanceof CallContextAny and sc instanceof SummaryCtxNone and - ap = any(AccessPathNilNode nil | nil.getNode() = node).getAp() + ap = TNil(getErasedNodeTypeBound(node)) or // ... or a step from an existing PathNode to another node. exists(PathNodeMid mid | @@ -2357,12 +2329,11 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt pathIntoLocalStep(mid, midnode, cc, enclosing, sc, ap0, conf) and localCC = getLocalCallContext(cc, enclosing) | - localFlowBigStep(midnode, node, true, conf, localCC) and + localFlowBigStep(midnode, node, true, _, conf, localCC) and ap = ap0 or - localFlowBigStep(midnode, node, false, conf, localCC) and - ap0 instanceof AccessPathNil and - ap = any(AccessPathNilNode nil | nil.getNode() = node).getAp() + localFlowBigStep(midnode, node, false, ap.(AccessPathNil).getType(), conf, localCC) and + ap0 instanceof AccessPathNil ) or jumpStep(mid.getNode(), node, mid.getConfiguration()) and @@ -2374,7 +2345,7 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt cc instanceof CallContextAny and sc instanceof SummaryCtxNone and mid.getAp() instanceof AccessPathNil and - ap = any(AccessPathNilNode nil | nil.getNode() = node).getAp() + ap = TNil(getErasedNodeTypeBound(node)) or exists(Content f, AccessPath ap0 | pathReadStep(mid, node, ap0, f, cc) and ap = pop(f, ap0)) and sc = mid.getSummaryCtx() @@ -2397,7 +2368,7 @@ private predicate pathIntoLocalStep( midnode = mid.getNode() and cc = mid.getCallContext() and conf = mid.getConfiguration() and - localFlowBigStep(midnode, _, _, conf, _) and + localFlowBigStep(midnode, _, _, _, conf, _) and enclosing = midnode.getEnclosingCallable() and sc = mid.getSummaryCtx() and ap0 = mid.getAp() @@ -2949,7 +2920,7 @@ private module FlowExploration { config = mid.getConfiguration() } - pragma[noinline] + pragma[nomagic] private predicate partialPathOutOfCallable1( PartialPathNodePriv mid, DataFlowCall call, ReturnKindExt kind, CallContext cc, PartialAccessPath ap, Configuration config 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 5eb98aefe4e..e3f1da355ef 100644 --- a/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl4.qll +++ b/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl4.qll @@ -1307,15 +1307,20 @@ private predicate localFlowExit(Node node, Configuration config) { */ pragma[nomagic] private predicate localFlowStepPlus( - Node node1, Node node2, boolean preservesValue, Configuration config, LocalCallContext cc + Node node1, Node node2, boolean preservesValue, DataFlowType t, Configuration config, + LocalCallContext cc ) { not isUnreachableInCall(node2, cc.(LocalCallContextSpecificCall).getCall()) and ( localFlowEntry(node1, config) and ( - localFlowStep(node1, node2, config) and preservesValue = true + localFlowStep(node1, node2, config) and + preservesValue = true and + t = getErasedNodeTypeBound(node1) or - additionalLocalFlowStep(node1, node2, config) and preservesValue = false + additionalLocalFlowStep(node1, node2, config) and + preservesValue = false and + t = getErasedNodeTypeBound(node2) ) and node1 != node2 and cc.relevantFor(node1.getEnclosingCallable()) and @@ -1323,17 +1328,18 @@ private predicate localFlowStepPlus( nodeCand(TNormalNode(node2), unbind(config)) or exists(Node mid | - localFlowStepPlus(node1, mid, preservesValue, config, cc) and + localFlowStepPlus(node1, mid, preservesValue, t, config, cc) and localFlowStep(mid, node2, config) and not mid instanceof CastNode and nodeCand(TNormalNode(node2), unbind(config)) ) or exists(Node mid | - localFlowStepPlus(node1, mid, _, config, cc) and + localFlowStepPlus(node1, mid, _, _, config, cc) and additionalLocalFlowStep(mid, node2, config) and not mid instanceof CastNode and preservesValue = false and + t = getErasedNodeTypeBound(node2) and nodeCand(TNormalNode(node2), unbind(config)) ) ) @@ -1345,17 +1351,18 @@ private predicate localFlowStepPlus( */ pragma[nomagic] private predicate localFlowBigStep( - Node node1, Node node2, boolean preservesValue, Configuration config, LocalCallContext callContext + Node node1, Node node2, boolean preservesValue, DataFlowType t, Configuration config, + LocalCallContext callContext ) { - localFlowStepPlus(node1, node2, preservesValue, config, callContext) and + localFlowStepPlus(node1, node2, preservesValue, t, config, callContext) and localFlowExit(node2, config) } pragma[nomagic] private predicate localFlowBigStepExt( - NodeExt node1, NodeExt node2, boolean preservesValue, Configuration config + NodeExt node1, NodeExt node2, boolean preservesValue, AccessPathFrontNil apf, Configuration config ) { - localFlowBigStep(node1.getNode(), node2.getNode(), preservesValue, config, _) + localFlowBigStep(node1.getNode(), node2.getNode(), preservesValue, apf.getType(), config, _) } private newtype TAccessPathFront = @@ -1395,46 +1402,24 @@ private predicate flowCandFwd( else any() } -/** - * A node that requires an empty access path and should have its tracked type - * (re-)computed. This is either a source or a node reached through an - * additional step. - */ -private class AccessPathFrontNilNode extends NormalNodeExt { - AccessPathFrontNilNode() { - nodeCand(this, _) and - ( - any(Configuration c).isSource(this.getNode()) - or - localFlowBigStepExt(_, this, false, _) - or - additionalJumpStepExt(_, this, _) - ) - } - - /** Gets the `nil` path front for this node. */ - AccessPathFrontNil getApf() { result = TFrontNil(this.getErasedNodeTypeBound()) } -} - private predicate flowCandFwd0( NodeExt node, boolean fromArg, AccessPathFront apf, Configuration config ) { nodeCand2(node, _, false, config) and config.isSource(node.getNode()) and fromArg = false and - apf = node.(AccessPathFrontNilNode).getApf() + apf = TFrontNil(node.getErasedNodeTypeBound()) or nodeCand(node, unbind(config)) and ( exists(NodeExt mid | flowCandFwd(mid, fromArg, apf, config) and - localFlowBigStepExt(mid, node, true, config) + localFlowBigStepExt(mid, node, true, _, config) ) or exists(NodeExt mid, AccessPathFrontNil nil | flowCandFwd(mid, fromArg, nil, config) and - localFlowBigStepExt(mid, node, false, config) and - apf = node.(AccessPathFrontNilNode).getApf() + localFlowBigStepExt(mid, node, false, apf, config) ) or exists(NodeExt mid | @@ -1447,7 +1432,7 @@ private predicate flowCandFwd0( flowCandFwd(mid, _, nil, config) and additionalJumpStepExt(mid, node, config) and fromArg = false and - apf = node.(AccessPathFrontNilNode).getApf() + apf = TFrontNil(node.getErasedNodeTypeBound()) ) or exists(NodeExt mid, boolean allowsFieldFlow | @@ -1589,13 +1574,13 @@ private predicate flowCand0( apf instanceof AccessPathFrontNil or exists(NodeExt mid | - localFlowBigStepExt(node, mid, true, config) and + localFlowBigStepExt(node, mid, true, _, config) and flowCand(mid, toReturn, apf, config) ) or exists(NodeExt mid, AccessPathFrontNil nil | flowCandFwd(node, _, apf, config) and - localFlowBigStepExt(node, mid, false, config) and + localFlowBigStepExt(node, mid, false, _, config) and flowCand(mid, toReturn, nil, config) and apf instanceof AccessPathFrontNil ) @@ -1810,18 +1795,6 @@ private predicate popWithFront(AccessPath ap0, Content f, AccessPathFront apf, A /** Gets the access path obtained by pushing `f` onto `ap`. */ private AccessPath push(Content f, AccessPath ap) { ap = pop(f, result) } -/** - * A node that requires an empty access path and should have its tracked type - * (re-)computed. This is either a source or a node reached through an - * additional step. - */ -private class AccessPathNilNode extends NormalNodeExt { - AccessPathNilNode() { flowCand(this.(AccessPathFrontNilNode), _, _, _) } - - /** Gets the `nil` path for this node. */ - AccessPathNil getAp() { result = TNil(this.getErasedNodeTypeBound()) } -} - /** * Holds if data can flow from a source to `node` with the given `ap`. */ @@ -1838,20 +1811,19 @@ private predicate flowFwd0( flowCand(node, _, _, config) and config.isSource(node.getNode()) and fromArg = false and - ap = node.(AccessPathNilNode).getAp() and + ap = TNil(node.getErasedNodeTypeBound()) and apf = ap.(AccessPathNil).getFront() or flowCand(node, _, _, unbind(config)) and ( exists(NodeExt mid | flowFwd(mid, fromArg, apf, ap, config) and - localFlowBigStepExt(mid, node, true, config) + localFlowBigStepExt(mid, node, true, _, config) ) or exists(NodeExt mid, AccessPathNil nil | flowFwd(mid, fromArg, _, nil, config) and - localFlowBigStepExt(mid, node, false, config) and - ap = node.(AccessPathNilNode).getAp() and + localFlowBigStepExt(mid, node, false, apf, config) and apf = ap.(AccessPathNil).getFront() ) or @@ -1865,7 +1837,7 @@ private predicate flowFwd0( flowFwd(mid, _, _, nil, config) and additionalJumpStepExt(mid, node, config) and fromArg = false and - ap = node.(AccessPathNilNode).getAp() and + ap = TNil(node.getErasedNodeTypeBound()) and apf = ap.(AccessPathNil).getFront() ) or @@ -1982,13 +1954,13 @@ private predicate flow0(NodeExt node, boolean toReturn, AccessPath ap, Configura ap instanceof AccessPathNil or exists(NodeExt mid | - localFlowBigStepExt(node, mid, true, config) and + localFlowBigStepExt(node, mid, true, _, config) and flow(mid, toReturn, ap, config) ) or exists(NodeExt mid, AccessPathNil nil | flowFwd(node, _, _, ap, config) and - localFlowBigStepExt(node, mid, false, config) and + localFlowBigStepExt(node, mid, false, _, config) and flow(mid, toReturn, nil, config) and ap instanceof AccessPathNil ) @@ -2164,7 +2136,7 @@ private newtype TPathNode = config.isSource(node) and cc instanceof CallContextAny and sc instanceof SummaryCtxNone and - ap = any(AccessPathNilNode nil | nil.getNode() = node).getAp() + ap = TNil(getErasedNodeTypeBound(node)) or // ... or a step from an existing PathNode to another node. exists(PathNodeMid mid | @@ -2357,12 +2329,11 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt pathIntoLocalStep(mid, midnode, cc, enclosing, sc, ap0, conf) and localCC = getLocalCallContext(cc, enclosing) | - localFlowBigStep(midnode, node, true, conf, localCC) and + localFlowBigStep(midnode, node, true, _, conf, localCC) and ap = ap0 or - localFlowBigStep(midnode, node, false, conf, localCC) and - ap0 instanceof AccessPathNil and - ap = any(AccessPathNilNode nil | nil.getNode() = node).getAp() + localFlowBigStep(midnode, node, false, ap.(AccessPathNil).getType(), conf, localCC) and + ap0 instanceof AccessPathNil ) or jumpStep(mid.getNode(), node, mid.getConfiguration()) and @@ -2374,7 +2345,7 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt cc instanceof CallContextAny and sc instanceof SummaryCtxNone and mid.getAp() instanceof AccessPathNil and - ap = any(AccessPathNilNode nil | nil.getNode() = node).getAp() + ap = TNil(getErasedNodeTypeBound(node)) or exists(Content f, AccessPath ap0 | pathReadStep(mid, node, ap0, f, cc) and ap = pop(f, ap0)) and sc = mid.getSummaryCtx() @@ -2397,7 +2368,7 @@ private predicate pathIntoLocalStep( midnode = mid.getNode() and cc = mid.getCallContext() and conf = mid.getConfiguration() and - localFlowBigStep(midnode, _, _, conf, _) and + localFlowBigStep(midnode, _, _, _, conf, _) and enclosing = midnode.getEnclosingCallable() and sc = mid.getSummaryCtx() and ap0 = mid.getAp() @@ -2949,7 +2920,7 @@ private module FlowExploration { config = mid.getConfiguration() } - pragma[noinline] + pragma[nomagic] private predicate partialPathOutOfCallable1( PartialPathNodePriv mid, DataFlowCall call, ReturnKindExt kind, CallContext cc, PartialAccessPath ap, Configuration config 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 5eb98aefe4e..e3f1da355ef 100644 --- a/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl5.qll +++ b/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl5.qll @@ -1307,15 +1307,20 @@ private predicate localFlowExit(Node node, Configuration config) { */ pragma[nomagic] private predicate localFlowStepPlus( - Node node1, Node node2, boolean preservesValue, Configuration config, LocalCallContext cc + Node node1, Node node2, boolean preservesValue, DataFlowType t, Configuration config, + LocalCallContext cc ) { not isUnreachableInCall(node2, cc.(LocalCallContextSpecificCall).getCall()) and ( localFlowEntry(node1, config) and ( - localFlowStep(node1, node2, config) and preservesValue = true + localFlowStep(node1, node2, config) and + preservesValue = true and + t = getErasedNodeTypeBound(node1) or - additionalLocalFlowStep(node1, node2, config) and preservesValue = false + additionalLocalFlowStep(node1, node2, config) and + preservesValue = false and + t = getErasedNodeTypeBound(node2) ) and node1 != node2 and cc.relevantFor(node1.getEnclosingCallable()) and @@ -1323,17 +1328,18 @@ private predicate localFlowStepPlus( nodeCand(TNormalNode(node2), unbind(config)) or exists(Node mid | - localFlowStepPlus(node1, mid, preservesValue, config, cc) and + localFlowStepPlus(node1, mid, preservesValue, t, config, cc) and localFlowStep(mid, node2, config) and not mid instanceof CastNode and nodeCand(TNormalNode(node2), unbind(config)) ) or exists(Node mid | - localFlowStepPlus(node1, mid, _, config, cc) and + localFlowStepPlus(node1, mid, _, _, config, cc) and additionalLocalFlowStep(mid, node2, config) and not mid instanceof CastNode and preservesValue = false and + t = getErasedNodeTypeBound(node2) and nodeCand(TNormalNode(node2), unbind(config)) ) ) @@ -1345,17 +1351,18 @@ private predicate localFlowStepPlus( */ pragma[nomagic] private predicate localFlowBigStep( - Node node1, Node node2, boolean preservesValue, Configuration config, LocalCallContext callContext + Node node1, Node node2, boolean preservesValue, DataFlowType t, Configuration config, + LocalCallContext callContext ) { - localFlowStepPlus(node1, node2, preservesValue, config, callContext) and + localFlowStepPlus(node1, node2, preservesValue, t, config, callContext) and localFlowExit(node2, config) } pragma[nomagic] private predicate localFlowBigStepExt( - NodeExt node1, NodeExt node2, boolean preservesValue, Configuration config + NodeExt node1, NodeExt node2, boolean preservesValue, AccessPathFrontNil apf, Configuration config ) { - localFlowBigStep(node1.getNode(), node2.getNode(), preservesValue, config, _) + localFlowBigStep(node1.getNode(), node2.getNode(), preservesValue, apf.getType(), config, _) } private newtype TAccessPathFront = @@ -1395,46 +1402,24 @@ private predicate flowCandFwd( else any() } -/** - * A node that requires an empty access path and should have its tracked type - * (re-)computed. This is either a source or a node reached through an - * additional step. - */ -private class AccessPathFrontNilNode extends NormalNodeExt { - AccessPathFrontNilNode() { - nodeCand(this, _) and - ( - any(Configuration c).isSource(this.getNode()) - or - localFlowBigStepExt(_, this, false, _) - or - additionalJumpStepExt(_, this, _) - ) - } - - /** Gets the `nil` path front for this node. */ - AccessPathFrontNil getApf() { result = TFrontNil(this.getErasedNodeTypeBound()) } -} - private predicate flowCandFwd0( NodeExt node, boolean fromArg, AccessPathFront apf, Configuration config ) { nodeCand2(node, _, false, config) and config.isSource(node.getNode()) and fromArg = false and - apf = node.(AccessPathFrontNilNode).getApf() + apf = TFrontNil(node.getErasedNodeTypeBound()) or nodeCand(node, unbind(config)) and ( exists(NodeExt mid | flowCandFwd(mid, fromArg, apf, config) and - localFlowBigStepExt(mid, node, true, config) + localFlowBigStepExt(mid, node, true, _, config) ) or exists(NodeExt mid, AccessPathFrontNil nil | flowCandFwd(mid, fromArg, nil, config) and - localFlowBigStepExt(mid, node, false, config) and - apf = node.(AccessPathFrontNilNode).getApf() + localFlowBigStepExt(mid, node, false, apf, config) ) or exists(NodeExt mid | @@ -1447,7 +1432,7 @@ private predicate flowCandFwd0( flowCandFwd(mid, _, nil, config) and additionalJumpStepExt(mid, node, config) and fromArg = false and - apf = node.(AccessPathFrontNilNode).getApf() + apf = TFrontNil(node.getErasedNodeTypeBound()) ) or exists(NodeExt mid, boolean allowsFieldFlow | @@ -1589,13 +1574,13 @@ private predicate flowCand0( apf instanceof AccessPathFrontNil or exists(NodeExt mid | - localFlowBigStepExt(node, mid, true, config) and + localFlowBigStepExt(node, mid, true, _, config) and flowCand(mid, toReturn, apf, config) ) or exists(NodeExt mid, AccessPathFrontNil nil | flowCandFwd(node, _, apf, config) and - localFlowBigStepExt(node, mid, false, config) and + localFlowBigStepExt(node, mid, false, _, config) and flowCand(mid, toReturn, nil, config) and apf instanceof AccessPathFrontNil ) @@ -1810,18 +1795,6 @@ private predicate popWithFront(AccessPath ap0, Content f, AccessPathFront apf, A /** Gets the access path obtained by pushing `f` onto `ap`. */ private AccessPath push(Content f, AccessPath ap) { ap = pop(f, result) } -/** - * A node that requires an empty access path and should have its tracked type - * (re-)computed. This is either a source or a node reached through an - * additional step. - */ -private class AccessPathNilNode extends NormalNodeExt { - AccessPathNilNode() { flowCand(this.(AccessPathFrontNilNode), _, _, _) } - - /** Gets the `nil` path for this node. */ - AccessPathNil getAp() { result = TNil(this.getErasedNodeTypeBound()) } -} - /** * Holds if data can flow from a source to `node` with the given `ap`. */ @@ -1838,20 +1811,19 @@ private predicate flowFwd0( flowCand(node, _, _, config) and config.isSource(node.getNode()) and fromArg = false and - ap = node.(AccessPathNilNode).getAp() and + ap = TNil(node.getErasedNodeTypeBound()) and apf = ap.(AccessPathNil).getFront() or flowCand(node, _, _, unbind(config)) and ( exists(NodeExt mid | flowFwd(mid, fromArg, apf, ap, config) and - localFlowBigStepExt(mid, node, true, config) + localFlowBigStepExt(mid, node, true, _, config) ) or exists(NodeExt mid, AccessPathNil nil | flowFwd(mid, fromArg, _, nil, config) and - localFlowBigStepExt(mid, node, false, config) and - ap = node.(AccessPathNilNode).getAp() and + localFlowBigStepExt(mid, node, false, apf, config) and apf = ap.(AccessPathNil).getFront() ) or @@ -1865,7 +1837,7 @@ private predicate flowFwd0( flowFwd(mid, _, _, nil, config) and additionalJumpStepExt(mid, node, config) and fromArg = false and - ap = node.(AccessPathNilNode).getAp() and + ap = TNil(node.getErasedNodeTypeBound()) and apf = ap.(AccessPathNil).getFront() ) or @@ -1982,13 +1954,13 @@ private predicate flow0(NodeExt node, boolean toReturn, AccessPath ap, Configura ap instanceof AccessPathNil or exists(NodeExt mid | - localFlowBigStepExt(node, mid, true, config) and + localFlowBigStepExt(node, mid, true, _, config) and flow(mid, toReturn, ap, config) ) or exists(NodeExt mid, AccessPathNil nil | flowFwd(node, _, _, ap, config) and - localFlowBigStepExt(node, mid, false, config) and + localFlowBigStepExt(node, mid, false, _, config) and flow(mid, toReturn, nil, config) and ap instanceof AccessPathNil ) @@ -2164,7 +2136,7 @@ private newtype TPathNode = config.isSource(node) and cc instanceof CallContextAny and sc instanceof SummaryCtxNone and - ap = any(AccessPathNilNode nil | nil.getNode() = node).getAp() + ap = TNil(getErasedNodeTypeBound(node)) or // ... or a step from an existing PathNode to another node. exists(PathNodeMid mid | @@ -2357,12 +2329,11 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt pathIntoLocalStep(mid, midnode, cc, enclosing, sc, ap0, conf) and localCC = getLocalCallContext(cc, enclosing) | - localFlowBigStep(midnode, node, true, conf, localCC) and + localFlowBigStep(midnode, node, true, _, conf, localCC) and ap = ap0 or - localFlowBigStep(midnode, node, false, conf, localCC) and - ap0 instanceof AccessPathNil and - ap = any(AccessPathNilNode nil | nil.getNode() = node).getAp() + localFlowBigStep(midnode, node, false, ap.(AccessPathNil).getType(), conf, localCC) and + ap0 instanceof AccessPathNil ) or jumpStep(mid.getNode(), node, mid.getConfiguration()) and @@ -2374,7 +2345,7 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt cc instanceof CallContextAny and sc instanceof SummaryCtxNone and mid.getAp() instanceof AccessPathNil and - ap = any(AccessPathNilNode nil | nil.getNode() = node).getAp() + ap = TNil(getErasedNodeTypeBound(node)) or exists(Content f, AccessPath ap0 | pathReadStep(mid, node, ap0, f, cc) and ap = pop(f, ap0)) and sc = mid.getSummaryCtx() @@ -2397,7 +2368,7 @@ private predicate pathIntoLocalStep( midnode = mid.getNode() and cc = mid.getCallContext() and conf = mid.getConfiguration() and - localFlowBigStep(midnode, _, _, conf, _) and + localFlowBigStep(midnode, _, _, _, conf, _) and enclosing = midnode.getEnclosingCallable() and sc = mid.getSummaryCtx() and ap0 = mid.getAp() @@ -2949,7 +2920,7 @@ private module FlowExploration { config = mid.getConfiguration() } - pragma[noinline] + pragma[nomagic] private predicate partialPathOutOfCallable1( PartialPathNodePriv mid, DataFlowCall call, ReturnKindExt kind, CallContext cc, PartialAccessPath ap, Configuration config diff --git a/csharp/ql/src/semmle/code/csharp/frameworks/JsonNET.qll b/csharp/ql/src/semmle/code/csharp/frameworks/JsonNET.qll index d61cbd878a8..68dfc910911 100644 --- a/csharp/ql/src/semmle/code/csharp/frameworks/JsonNET.qll +++ b/csharp/ql/src/semmle/code/csharp/frameworks/JsonNET.qll @@ -50,7 +50,7 @@ module JsonNET { ) { // ToString methods c = getAToStringMethod() and - preservesValue = true and + preservesValue = false and source = any(CallableFlowSourceArg arg | arg.getArgumentIndex() = 0) and sink instanceof CallableFlowSinkReturn or diff --git a/csharp/ql/src/semmle/code/csharp/ir/implementation/Opcode.qll b/csharp/ql/src/semmle/code/csharp/ir/implementation/Opcode.qll index 886183c32ba..83c9ac1215c 100644 --- a/csharp/ql/src/semmle/code/csharp/ir/implementation/Opcode.qll +++ b/csharp/ql/src/semmle/code/csharp/ir/implementation/Opcode.qll @@ -82,6 +82,7 @@ private newtype TOpcode = TSizedBufferReadSideEffect() or TSizedBufferMustWriteSideEffect() or TSizedBufferMayWriteSideEffect() or + TInitializeDynamicAllocation() or TChi() or TInlineAsm() or TUnreached() or @@ -213,23 +214,28 @@ abstract class IndirectReadOpcode extends IndirectMemoryAccessOpcode { } /** - * An opcode that accesses a memory buffer of unknown size. + * An opcode that accesses a memory buffer. */ abstract class BufferAccessOpcode extends Opcode { final override predicate hasAddressOperand() { any() } } +/** + * An opcode that accesses a memory buffer of unknown size. + */ +abstract class UnsizedBufferAccessOpcode extends BufferAccessOpcode { } + /** * An opcode that writes to a memory buffer of unknown size. */ -abstract class BufferWriteOpcode extends BufferAccessOpcode { +abstract class UnsizedBufferWriteOpcode extends UnsizedBufferAccessOpcode { final override MemoryAccessKind getWriteMemoryAccess() { result instanceof BufferMemoryAccess } } /** * An opcode that reads from a memory buffer of unknown size. */ -abstract class BufferReadOpcode extends BufferAccessOpcode { +abstract class UnsizedBufferReadOpcode extends UnsizedBufferAccessOpcode { final override MemoryAccessKind getReadMemoryAccess() { result instanceof BufferMemoryAccess } } @@ -261,9 +267,7 @@ abstract class EntireAllocationReadOpcode extends EntireAllocationAccessOpcode { /** * An opcode that accesses a memory buffer whose size is determined by a `BufferSizeOperand`. */ -abstract class SizedBufferAccessOpcode extends Opcode { - final override predicate hasAddressOperand() { any() } - +abstract class SizedBufferAccessOpcode extends BufferAccessOpcode { final override predicate hasBufferSizeOperand() { any() } } @@ -666,17 +670,18 @@ module Opcode { final override string toString() { result = "IndirectMayWriteSideEffect" } } - class BufferReadSideEffect extends ReadSideEffectOpcode, BufferReadOpcode, TBufferReadSideEffect { + class BufferReadSideEffect extends ReadSideEffectOpcode, UnsizedBufferReadOpcode, + TBufferReadSideEffect { final override string toString() { result = "BufferReadSideEffect" } } - class BufferMustWriteSideEffect extends WriteSideEffectOpcode, BufferWriteOpcode, + class BufferMustWriteSideEffect extends WriteSideEffectOpcode, UnsizedBufferWriteOpcode, TBufferMustWriteSideEffect { final override string toString() { result = "BufferMustWriteSideEffect" } } - class BufferMayWriteSideEffect extends WriteSideEffectOpcode, BufferWriteOpcode, MayWriteOpcode, - TBufferMayWriteSideEffect { + class BufferMayWriteSideEffect extends WriteSideEffectOpcode, UnsizedBufferWriteOpcode, + MayWriteOpcode, TBufferMayWriteSideEffect { final override string toString() { result = "BufferMayWriteSideEffect" } } @@ -695,6 +700,11 @@ module Opcode { final override string toString() { result = "SizedBufferMayWriteSideEffect" } } + class InitializeDynamicAllocation extends SideEffectOpcode, EntireAllocationWriteOpcode, + TInitializeDynamicAllocation { + final override string toString() { result = "InitializeDynamicAllocation" } + } + class Chi extends Opcode, TChi { final override string toString() { result = "Chi" } diff --git a/csharp/ql/src/semmle/code/csharp/ir/implementation/raw/IRSanity.qll b/csharp/ql/src/semmle/code/csharp/ir/implementation/raw/IRSanity.qll index de66a3c99dc..d07a99eab37 100644 --- a/csharp/ql/src/semmle/code/csharp/ir/implementation/raw/IRSanity.qll +++ b/csharp/ql/src/semmle/code/csharp/ir/implementation/raw/IRSanity.qll @@ -1,3 +1,275 @@ private import IR -import InstructionSanity -import IRTypeSanity +import InstructionSanity // module is below +import IRTypeSanity // module is in IRType.qll + +module InstructionSanity { + private import internal.InstructionImports as Imports + private import Imports::OperandTag + private import internal.IRInternal + + /** + * Holds if instruction `instr` is missing an expected operand with tag `tag`. + */ + query predicate missingOperand(Instruction instr, string message, IRFunction func, string funcText) { + exists(OperandTag tag | + instr.getOpcode().hasOperand(tag) and + not exists(NonPhiOperand operand | + operand = instr.getAnOperand() and + operand.getOperandTag() = tag + ) and + message = + "Instruction '" + instr.getOpcode().toString() + + "' is missing an expected operand with tag '" + tag.toString() + "' in function '$@'." and + func = instr.getEnclosingIRFunction() and + funcText = Language::getIdentityString(func.getFunction()) + ) + } + + /** + * Holds if instruction `instr` has an unexpected operand with tag `tag`. + */ + query predicate unexpectedOperand(Instruction instr, OperandTag tag) { + exists(NonPhiOperand operand | + operand = instr.getAnOperand() and + operand.getOperandTag() = tag + ) and + not instr.getOpcode().hasOperand(tag) and + not (instr instanceof CallInstruction and tag instanceof ArgumentOperandTag) and + not ( + instr instanceof BuiltInOperationInstruction and tag instanceof PositionalArgumentOperandTag + ) and + not (instr instanceof InlineAsmInstruction and tag instanceof AsmOperandTag) + } + + /** + * Holds if instruction `instr` has multiple operands with tag `tag`. + */ + query predicate duplicateOperand( + Instruction instr, string message, IRFunction func, string funcText + ) { + exists(OperandTag tag, int operandCount | + operandCount = + strictcount(NonPhiOperand operand | + operand = instr.getAnOperand() and + operand.getOperandTag() = tag + ) and + operandCount > 1 and + not tag instanceof UnmodeledUseOperandTag and + message = + "Instruction has " + operandCount + " operands with tag '" + tag.toString() + "'" + + " in function '$@'." and + func = instr.getEnclosingIRFunction() and + funcText = Language::getIdentityString(func.getFunction()) + ) + } + + /** + * Holds if `Phi` instruction `instr` is missing an operand corresponding to + * the predecessor block `pred`. + */ + query predicate missingPhiOperand(PhiInstruction instr, IRBlock pred) { + pred = instr.getBlock().getAPredecessor() and + not exists(PhiInputOperand operand | + operand = instr.getAnOperand() and + operand.getPredecessorBlock() = pred + ) + } + + query predicate missingOperandType(Operand operand, string message) { + exists(Language::Function func, Instruction use | + not exists(operand.getType()) and + use = operand.getUse() and + func = use.getEnclosingFunction() and + message = + "Operand '" + operand.toString() + "' of instruction '" + use.getOpcode().toString() + + "' missing type in function '" + Language::getIdentityString(func) + "'." + ) + } + + query predicate duplicateChiOperand( + ChiInstruction chi, string message, IRFunction func, string funcText + ) { + chi.getTotal() = chi.getPartial() and + message = + "Chi instruction for " + chi.getPartial().toString() + + " has duplicate operands in function $@" and + func = chi.getEnclosingIRFunction() and + funcText = Language::getIdentityString(func.getFunction()) + } + + query predicate sideEffectWithoutPrimary( + SideEffectInstruction instr, string message, IRFunction func, string funcText + ) { + not exists(instr.getPrimaryInstruction()) and + message = "Side effect instruction missing primary instruction in function $@" and + func = instr.getEnclosingIRFunction() and + funcText = Language::getIdentityString(func.getFunction()) + } + + /** + * Holds if an instruction, other than `ExitFunction`, has no successors. + */ + query predicate instructionWithoutSuccessor(Instruction instr) { + not exists(instr.getASuccessor()) and + not instr instanceof ExitFunctionInstruction and + // Phi instructions aren't linked into the instruction-level flow graph. + not instr instanceof PhiInstruction and + not instr instanceof UnreachedInstruction + } + + /** + * Holds if there are multiple (`n`) edges of kind `kind` from `source`, + * where `target` is among the targets of those edges. + */ + query predicate ambiguousSuccessors(Instruction source, EdgeKind kind, int n, Instruction target) { + n = strictcount(Instruction t | source.getSuccessor(kind) = t) and + n > 1 and + source.getSuccessor(kind) = target + } + + /** + * Holds if `instr` in `f` is part of a loop even though the AST of `f` + * contains no element that can cause loops. + */ + query predicate unexplainedLoop(Language::Function f, Instruction instr) { + exists(IRBlock block | + instr.getBlock() = block and + block.getEnclosingFunction() = f and + block.getASuccessor+() = block + ) and + not Language::hasPotentialLoop(f) + } + + /** + * Holds if a `Phi` instruction is present in a block with fewer than two + * predecessors. + */ + query predicate unnecessaryPhiInstruction(PhiInstruction instr) { + count(instr.getBlock().getAPredecessor()) < 2 + } + + /** + * Holds if operand `operand` consumes a value that was defined in + * a different function. + */ + query predicate operandAcrossFunctions(Operand operand, Instruction instr, Instruction defInstr) { + operand.getUse() = instr and + operand.getAnyDef() = defInstr and + instr.getEnclosingIRFunction() != defInstr.getEnclosingIRFunction() + } + + /** + * Holds if instruction `instr` is not in exactly one block. + */ + query predicate instructionWithoutUniqueBlock(Instruction instr, int blockCount) { + blockCount = count(instr.getBlock()) and + blockCount != 1 + } + + private predicate forwardEdge(IRBlock b1, IRBlock b2) { + b1.getASuccessor() = b2 and + not b1.getBackEdgeSuccessor(_) = b2 + } + + /** + * Holds if `f` contains a loop in which no edge is a back edge. + * + * This check ensures we don't have too _few_ back edges. + */ + query predicate containsLoopOfForwardEdges(IRFunction f) { + exists(IRBlock block | + forwardEdge+(block, block) and + block.getEnclosingIRFunction() = f + ) + } + + /** + * Holds if `block` is reachable from its function entry point but would not + * be reachable by traversing only forward edges. This check is skipped for + * functions containing `goto` statements as the property does not generally + * hold there. + * + * This check ensures we don't have too _many_ back edges. + */ + query predicate lostReachability(IRBlock block) { + exists(IRFunction f, IRBlock entry | + entry = f.getEntryBlock() and + entry.getASuccessor+() = block and + not forwardEdge+(entry, block) and + not Language::hasGoto(f.getFunction()) + ) + } + + /** + * Holds if the number of back edges differs between the `Instruction` graph + * and the `IRBlock` graph. + */ + query predicate backEdgeCountMismatch(Language::Function f, int fromInstr, int fromBlock) { + fromInstr = + count(Instruction i1, Instruction i2 | + i1.getEnclosingFunction() = f and i1.getBackEdgeSuccessor(_) = i2 + ) and + fromBlock = + count(IRBlock b1, IRBlock b2 | + b1.getEnclosingFunction() = f and b1.getBackEdgeSuccessor(_) = b2 + ) and + fromInstr != fromBlock + } + + /** + * Gets the point in the function at which the specified operand is evaluated. For most operands, + * this is at the instruction that consumes the use. For a `PhiInputOperand`, the effective point + * of evaluation is at the end of the corresponding predecessor block. + */ + private predicate pointOfEvaluation(Operand operand, IRBlock block, int index) { + block = operand.(PhiInputOperand).getPredecessorBlock() and + index = block.getInstructionCount() + or + exists(Instruction use | + use = operand.(NonPhiOperand).getUse() and + block.getInstruction(index) = use + ) + } + + /** + * Holds if `useOperand` has a definition that does not dominate the use. + */ + query predicate useNotDominatedByDefinition( + Operand useOperand, string message, IRFunction func, string funcText + ) { + exists(IRBlock useBlock, int useIndex, Instruction defInstr, IRBlock defBlock, int defIndex | + not useOperand.getUse() instanceof UnmodeledUseInstruction and + not defInstr instanceof UnmodeledDefinitionInstruction and + pointOfEvaluation(useOperand, useBlock, useIndex) and + defInstr = useOperand.getAnyDef() and + ( + defInstr instanceof PhiInstruction and + defBlock = defInstr.getBlock() and + defIndex = -1 + or + defBlock.getInstruction(defIndex) = defInstr + ) and + not ( + defBlock.strictlyDominates(useBlock) + or + defBlock = useBlock and + defIndex < useIndex + ) and + message = + "Operand '" + useOperand.toString() + + "' is not dominated by its definition in function '$@'." and + func = useOperand.getEnclosingIRFunction() and + 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/IRVariable.qll b/csharp/ql/src/semmle/code/csharp/ir/implementation/raw/IRVariable.qll index ee7aebc4749..ae585ec2c7e 100644 --- a/csharp/ql/src/semmle/code/csharp/ir/implementation/raw/IRVariable.qll +++ b/csharp/ql/src/semmle/code/csharp/ir/implementation/raw/IRVariable.qll @@ -176,7 +176,7 @@ IRTempVariable getIRTempVariable(Language::AST ast, TempVariableTag tag) { /** * A temporary variable introduced by IR construction. The most common examples are the variable - * generated to hold the return value of afunction, or the variable generated to hold the result of + * generated to hold the return value of a function, or the variable generated to hold the result of * a condition operator (`a ? b : c`). */ class IRTempVariable extends IRGeneratedVariable, IRAutomaticVariable, TIRTempVariable { 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 52cf46a5f0e..472f1673f0d 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 @@ -10,274 +10,6 @@ import Imports::MemoryAccessKind import Imports::Opcode private import Imports::OperandTag -module InstructionSanity { - /** - * Holds if instruction `instr` is missing an expected operand with tag `tag`. - */ - query predicate missingOperand(Instruction instr, string message, IRFunction func, string funcText) { - exists(OperandTag tag | - instr.getOpcode().hasOperand(tag) and - not exists(NonPhiOperand operand | - operand = instr.getAnOperand() and - operand.getOperandTag() = tag - ) and - message = - "Instruction '" + instr.getOpcode().toString() + - "' is missing an expected operand with tag '" + tag.toString() + "' in function '$@'." and - func = instr.getEnclosingIRFunction() and - funcText = Language::getIdentityString(func.getFunction()) - ) - } - - /** - * Holds if instruction `instr` has an unexpected operand with tag `tag`. - */ - query predicate unexpectedOperand(Instruction instr, OperandTag tag) { - exists(NonPhiOperand operand | - operand = instr.getAnOperand() and - operand.getOperandTag() = tag - ) and - not instr.getOpcode().hasOperand(tag) and - not (instr instanceof CallInstruction and tag instanceof ArgumentOperandTag) and - not ( - instr instanceof BuiltInOperationInstruction and tag instanceof PositionalArgumentOperandTag - ) and - not (instr instanceof InlineAsmInstruction and tag instanceof AsmOperandTag) - } - - /** - * Holds if instruction `instr` has multiple operands with tag `tag`. - */ - query predicate duplicateOperand( - Instruction instr, string message, IRFunction func, string funcText - ) { - exists(OperandTag tag, int operandCount | - operandCount = - strictcount(NonPhiOperand operand | - operand = instr.getAnOperand() and - operand.getOperandTag() = tag - ) and - operandCount > 1 and - not tag instanceof UnmodeledUseOperandTag and - message = - "Instruction has " + operandCount + " operands with tag '" + tag.toString() + "'" + - " in function '$@'." and - func = instr.getEnclosingIRFunction() and - funcText = Language::getIdentityString(func.getFunction()) - ) - } - - /** - * Holds if `Phi` instruction `instr` is missing an operand corresponding to - * the predecessor block `pred`. - */ - query predicate missingPhiOperand(PhiInstruction instr, IRBlock pred) { - pred = instr.getBlock().getAPredecessor() and - not exists(PhiInputOperand operand | - operand = instr.getAnOperand() and - operand.getPredecessorBlock() = pred - ) - } - - query predicate missingOperandType(Operand operand, string message) { - exists(Language::Function func, Instruction use | - not exists(operand.getType()) and - use = operand.getUse() and - func = use.getEnclosingFunction() and - message = - "Operand '" + operand.toString() + "' of instruction '" + use.getOpcode().toString() + - "' missing type in function '" + Language::getIdentityString(func) + "'." - ) - } - - query predicate duplicateChiOperand( - ChiInstruction chi, string message, IRFunction func, string funcText - ) { - chi.getTotal() = chi.getPartial() and - message = - "Chi instruction for " + chi.getPartial().toString() + - " has duplicate operands in function $@" and - func = chi.getEnclosingIRFunction() and - funcText = Language::getIdentityString(func.getFunction()) - } - - query predicate sideEffectWithoutPrimary( - SideEffectInstruction instr, string message, IRFunction func, string funcText - ) { - not exists(instr.getPrimaryInstruction()) and - message = "Side effect instruction missing primary instruction in function $@" and - func = instr.getEnclosingIRFunction() and - funcText = Language::getIdentityString(func.getFunction()) - } - - /** - * Holds if an instruction, other than `ExitFunction`, has no successors. - */ - query predicate instructionWithoutSuccessor(Instruction instr) { - not exists(instr.getASuccessor()) and - not instr instanceof ExitFunctionInstruction and - // Phi instructions aren't linked into the instruction-level flow graph. - not instr instanceof PhiInstruction and - not instr instanceof UnreachedInstruction - } - - /** - * Holds if there are multiple (`n`) edges of kind `kind` from `source`, - * where `target` is among the targets of those edges. - */ - query predicate ambiguousSuccessors(Instruction source, EdgeKind kind, int n, Instruction target) { - n = strictcount(Instruction t | source.getSuccessor(kind) = t) and - n > 1 and - source.getSuccessor(kind) = target - } - - /** - * Holds if `instr` in `f` is part of a loop even though the AST of `f` - * contains no element that can cause loops. - */ - query predicate unexplainedLoop(Language::Function f, Instruction instr) { - exists(IRBlock block | - instr.getBlock() = block and - block.getEnclosingFunction() = f and - block.getASuccessor+() = block - ) and - not Language::hasPotentialLoop(f) - } - - /** - * Holds if a `Phi` instruction is present in a block with fewer than two - * predecessors. - */ - query predicate unnecessaryPhiInstruction(PhiInstruction instr) { - count(instr.getBlock().getAPredecessor()) < 2 - } - - /** - * Holds if operand `operand` consumes a value that was defined in - * a different function. - */ - query predicate operandAcrossFunctions(Operand operand, Instruction instr, Instruction defInstr) { - operand.getUse() = instr and - operand.getAnyDef() = defInstr and - instr.getEnclosingIRFunction() != defInstr.getEnclosingIRFunction() - } - - /** - * Holds if instruction `instr` is not in exactly one block. - */ - query predicate instructionWithoutUniqueBlock(Instruction instr, int blockCount) { - blockCount = count(instr.getBlock()) and - blockCount != 1 - } - - private predicate forwardEdge(IRBlock b1, IRBlock b2) { - b1.getASuccessor() = b2 and - not b1.getBackEdgeSuccessor(_) = b2 - } - - /** - * Holds if `f` contains a loop in which no edge is a back edge. - * - * This check ensures we don't have too _few_ back edges. - */ - query predicate containsLoopOfForwardEdges(IRFunction f) { - exists(IRBlock block | - forwardEdge+(block, block) and - block.getEnclosingIRFunction() = f - ) - } - - /** - * Holds if `block` is reachable from its function entry point but would not - * be reachable by traversing only forward edges. This check is skipped for - * functions containing `goto` statements as the property does not generally - * hold there. - * - * This check ensures we don't have too _many_ back edges. - */ - query predicate lostReachability(IRBlock block) { - exists(IRFunction f, IRBlock entry | - entry = f.getEntryBlock() and - entry.getASuccessor+() = block and - not forwardEdge+(entry, block) and - not Language::hasGoto(f.getFunction()) - ) - } - - /** - * Holds if the number of back edges differs between the `Instruction` graph - * and the `IRBlock` graph. - */ - query predicate backEdgeCountMismatch(Language::Function f, int fromInstr, int fromBlock) { - fromInstr = - count(Instruction i1, Instruction i2 | - i1.getEnclosingFunction() = f and i1.getBackEdgeSuccessor(_) = i2 - ) and - fromBlock = - count(IRBlock b1, IRBlock b2 | - b1.getEnclosingFunction() = f and b1.getBackEdgeSuccessor(_) = b2 - ) and - fromInstr != fromBlock - } - - /** - * Gets the point in the function at which the specified operand is evaluated. For most operands, - * this is at the instruction that consumes the use. For a `PhiInputOperand`, the effective point - * of evaluation is at the end of the corresponding predecessor block. - */ - private predicate pointOfEvaluation(Operand operand, IRBlock block, int index) { - block = operand.(PhiInputOperand).getPredecessorBlock() and - index = block.getInstructionCount() - or - exists(Instruction use | - use = operand.(NonPhiOperand).getUse() and - block.getInstruction(index) = use - ) - } - - /** - * Holds if `useOperand` has a definition that does not dominate the use. - */ - query predicate useNotDominatedByDefinition( - Operand useOperand, string message, IRFunction func, string funcText - ) { - exists(IRBlock useBlock, int useIndex, Instruction defInstr, IRBlock defBlock, int defIndex | - not useOperand.getUse() instanceof UnmodeledUseInstruction and - not defInstr instanceof UnmodeledDefinitionInstruction and - pointOfEvaluation(useOperand, useBlock, useIndex) and - defInstr = useOperand.getAnyDef() and - ( - defInstr instanceof PhiInstruction and - defBlock = defInstr.getBlock() and - defIndex = -1 - or - defBlock.getInstruction(defIndex) = defInstr - ) and - not ( - defBlock.strictlyDominates(useBlock) - or - defBlock = useBlock and - defIndex < useIndex - ) and - message = - "Operand '" + useOperand.toString() + - "' is not dominated by its definition in function '$@'." and - func = useOperand.getEnclosingIRFunction() and - 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()) - } -} - /** * Gets an `Instruction` that is contained in `IRFunction`, and has a location with the specified * `File` and line number. Used for assigning register names when printing IR. @@ -1204,6 +936,11 @@ class CallInstruction extends Instruction { final Instruction getPositionalArgument(int index) { result = getPositionalArgumentOperand(index).getDef() } + + /** + * Gets the number of arguments of the call, including the `this` pointer, if any. + */ + final int getNumberOfArguments() { result = count(this.getAnArgumentOperand()) } } /** @@ -1352,6 +1089,26 @@ class SizedBufferMayWriteSideEffectInstruction extends WriteSideEffectInstructio Instruction getSizeDef() { result = getAnOperand().(BufferSizeOperand).getDef() } } +/** + * An instruction representing the initial value of newly allocated memory, e.g. the result of a + * call to `malloc`. + */ +class InitializeDynamicAllocationInstruction extends SideEffectInstruction { + InitializeDynamicAllocationInstruction() { + getOpcode() instanceof Opcode::InitializeDynamicAllocation + } + + /** + * Gets the address of the allocation this instruction is initializing. + */ + final AddressOperand getAllocationAddressOperand() { result = getAnOperand() } + + /** + * Gets the operand for the allocation this instruction is initializing. + */ + final Instruction getAllocationAddress() { result = getAllocationAddressOperand().getDef() } +} + /** * An instruction representing a GNU or MSVC inline assembly statement. */ diff --git a/csharp/ql/src/semmle/code/csharp/ir/implementation/unaliased_ssa/IRSanity.qll b/csharp/ql/src/semmle/code/csharp/ir/implementation/unaliased_ssa/IRSanity.qll index de66a3c99dc..d07a99eab37 100644 --- a/csharp/ql/src/semmle/code/csharp/ir/implementation/unaliased_ssa/IRSanity.qll +++ b/csharp/ql/src/semmle/code/csharp/ir/implementation/unaliased_ssa/IRSanity.qll @@ -1,3 +1,275 @@ private import IR -import InstructionSanity -import IRTypeSanity +import InstructionSanity // module is below +import IRTypeSanity // module is in IRType.qll + +module InstructionSanity { + private import internal.InstructionImports as Imports + private import Imports::OperandTag + private import internal.IRInternal + + /** + * Holds if instruction `instr` is missing an expected operand with tag `tag`. + */ + query predicate missingOperand(Instruction instr, string message, IRFunction func, string funcText) { + exists(OperandTag tag | + instr.getOpcode().hasOperand(tag) and + not exists(NonPhiOperand operand | + operand = instr.getAnOperand() and + operand.getOperandTag() = tag + ) and + message = + "Instruction '" + instr.getOpcode().toString() + + "' is missing an expected operand with tag '" + tag.toString() + "' in function '$@'." and + func = instr.getEnclosingIRFunction() and + funcText = Language::getIdentityString(func.getFunction()) + ) + } + + /** + * Holds if instruction `instr` has an unexpected operand with tag `tag`. + */ + query predicate unexpectedOperand(Instruction instr, OperandTag tag) { + exists(NonPhiOperand operand | + operand = instr.getAnOperand() and + operand.getOperandTag() = tag + ) and + not instr.getOpcode().hasOperand(tag) and + not (instr instanceof CallInstruction and tag instanceof ArgumentOperandTag) and + not ( + instr instanceof BuiltInOperationInstruction and tag instanceof PositionalArgumentOperandTag + ) and + not (instr instanceof InlineAsmInstruction and tag instanceof AsmOperandTag) + } + + /** + * Holds if instruction `instr` has multiple operands with tag `tag`. + */ + query predicate duplicateOperand( + Instruction instr, string message, IRFunction func, string funcText + ) { + exists(OperandTag tag, int operandCount | + operandCount = + strictcount(NonPhiOperand operand | + operand = instr.getAnOperand() and + operand.getOperandTag() = tag + ) and + operandCount > 1 and + not tag instanceof UnmodeledUseOperandTag and + message = + "Instruction has " + operandCount + " operands with tag '" + tag.toString() + "'" + + " in function '$@'." and + func = instr.getEnclosingIRFunction() and + funcText = Language::getIdentityString(func.getFunction()) + ) + } + + /** + * Holds if `Phi` instruction `instr` is missing an operand corresponding to + * the predecessor block `pred`. + */ + query predicate missingPhiOperand(PhiInstruction instr, IRBlock pred) { + pred = instr.getBlock().getAPredecessor() and + not exists(PhiInputOperand operand | + operand = instr.getAnOperand() and + operand.getPredecessorBlock() = pred + ) + } + + query predicate missingOperandType(Operand operand, string message) { + exists(Language::Function func, Instruction use | + not exists(operand.getType()) and + use = operand.getUse() and + func = use.getEnclosingFunction() and + message = + "Operand '" + operand.toString() + "' of instruction '" + use.getOpcode().toString() + + "' missing type in function '" + Language::getIdentityString(func) + "'." + ) + } + + query predicate duplicateChiOperand( + ChiInstruction chi, string message, IRFunction func, string funcText + ) { + chi.getTotal() = chi.getPartial() and + message = + "Chi instruction for " + chi.getPartial().toString() + + " has duplicate operands in function $@" and + func = chi.getEnclosingIRFunction() and + funcText = Language::getIdentityString(func.getFunction()) + } + + query predicate sideEffectWithoutPrimary( + SideEffectInstruction instr, string message, IRFunction func, string funcText + ) { + not exists(instr.getPrimaryInstruction()) and + message = "Side effect instruction missing primary instruction in function $@" and + func = instr.getEnclosingIRFunction() and + funcText = Language::getIdentityString(func.getFunction()) + } + + /** + * Holds if an instruction, other than `ExitFunction`, has no successors. + */ + query predicate instructionWithoutSuccessor(Instruction instr) { + not exists(instr.getASuccessor()) and + not instr instanceof ExitFunctionInstruction and + // Phi instructions aren't linked into the instruction-level flow graph. + not instr instanceof PhiInstruction and + not instr instanceof UnreachedInstruction + } + + /** + * Holds if there are multiple (`n`) edges of kind `kind` from `source`, + * where `target` is among the targets of those edges. + */ + query predicate ambiguousSuccessors(Instruction source, EdgeKind kind, int n, Instruction target) { + n = strictcount(Instruction t | source.getSuccessor(kind) = t) and + n > 1 and + source.getSuccessor(kind) = target + } + + /** + * Holds if `instr` in `f` is part of a loop even though the AST of `f` + * contains no element that can cause loops. + */ + query predicate unexplainedLoop(Language::Function f, Instruction instr) { + exists(IRBlock block | + instr.getBlock() = block and + block.getEnclosingFunction() = f and + block.getASuccessor+() = block + ) and + not Language::hasPotentialLoop(f) + } + + /** + * Holds if a `Phi` instruction is present in a block with fewer than two + * predecessors. + */ + query predicate unnecessaryPhiInstruction(PhiInstruction instr) { + count(instr.getBlock().getAPredecessor()) < 2 + } + + /** + * Holds if operand `operand` consumes a value that was defined in + * a different function. + */ + query predicate operandAcrossFunctions(Operand operand, Instruction instr, Instruction defInstr) { + operand.getUse() = instr and + operand.getAnyDef() = defInstr and + instr.getEnclosingIRFunction() != defInstr.getEnclosingIRFunction() + } + + /** + * Holds if instruction `instr` is not in exactly one block. + */ + query predicate instructionWithoutUniqueBlock(Instruction instr, int blockCount) { + blockCount = count(instr.getBlock()) and + blockCount != 1 + } + + private predicate forwardEdge(IRBlock b1, IRBlock b2) { + b1.getASuccessor() = b2 and + not b1.getBackEdgeSuccessor(_) = b2 + } + + /** + * Holds if `f` contains a loop in which no edge is a back edge. + * + * This check ensures we don't have too _few_ back edges. + */ + query predicate containsLoopOfForwardEdges(IRFunction f) { + exists(IRBlock block | + forwardEdge+(block, block) and + block.getEnclosingIRFunction() = f + ) + } + + /** + * Holds if `block` is reachable from its function entry point but would not + * be reachable by traversing only forward edges. This check is skipped for + * functions containing `goto` statements as the property does not generally + * hold there. + * + * This check ensures we don't have too _many_ back edges. + */ + query predicate lostReachability(IRBlock block) { + exists(IRFunction f, IRBlock entry | + entry = f.getEntryBlock() and + entry.getASuccessor+() = block and + not forwardEdge+(entry, block) and + not Language::hasGoto(f.getFunction()) + ) + } + + /** + * Holds if the number of back edges differs between the `Instruction` graph + * and the `IRBlock` graph. + */ + query predicate backEdgeCountMismatch(Language::Function f, int fromInstr, int fromBlock) { + fromInstr = + count(Instruction i1, Instruction i2 | + i1.getEnclosingFunction() = f and i1.getBackEdgeSuccessor(_) = i2 + ) and + fromBlock = + count(IRBlock b1, IRBlock b2 | + b1.getEnclosingFunction() = f and b1.getBackEdgeSuccessor(_) = b2 + ) and + fromInstr != fromBlock + } + + /** + * Gets the point in the function at which the specified operand is evaluated. For most operands, + * this is at the instruction that consumes the use. For a `PhiInputOperand`, the effective point + * of evaluation is at the end of the corresponding predecessor block. + */ + private predicate pointOfEvaluation(Operand operand, IRBlock block, int index) { + block = operand.(PhiInputOperand).getPredecessorBlock() and + index = block.getInstructionCount() + or + exists(Instruction use | + use = operand.(NonPhiOperand).getUse() and + block.getInstruction(index) = use + ) + } + + /** + * Holds if `useOperand` has a definition that does not dominate the use. + */ + query predicate useNotDominatedByDefinition( + Operand useOperand, string message, IRFunction func, string funcText + ) { + exists(IRBlock useBlock, int useIndex, Instruction defInstr, IRBlock defBlock, int defIndex | + not useOperand.getUse() instanceof UnmodeledUseInstruction and + not defInstr instanceof UnmodeledDefinitionInstruction and + pointOfEvaluation(useOperand, useBlock, useIndex) and + defInstr = useOperand.getAnyDef() and + ( + defInstr instanceof PhiInstruction and + defBlock = defInstr.getBlock() and + defIndex = -1 + or + defBlock.getInstruction(defIndex) = defInstr + ) and + not ( + defBlock.strictlyDominates(useBlock) + or + defBlock = useBlock and + defIndex < useIndex + ) and + message = + "Operand '" + useOperand.toString() + + "' is not dominated by its definition in function '$@'." and + func = useOperand.getEnclosingIRFunction() and + 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/IRVariable.qll b/csharp/ql/src/semmle/code/csharp/ir/implementation/unaliased_ssa/IRVariable.qll index ee7aebc4749..ae585ec2c7e 100644 --- a/csharp/ql/src/semmle/code/csharp/ir/implementation/unaliased_ssa/IRVariable.qll +++ b/csharp/ql/src/semmle/code/csharp/ir/implementation/unaliased_ssa/IRVariable.qll @@ -176,7 +176,7 @@ IRTempVariable getIRTempVariable(Language::AST ast, TempVariableTag tag) { /** * A temporary variable introduced by IR construction. The most common examples are the variable - * generated to hold the return value of afunction, or the variable generated to hold the result of + * generated to hold the return value of a function, or the variable generated to hold the result of * a condition operator (`a ? b : c`). */ class IRTempVariable extends IRGeneratedVariable, IRAutomaticVariable, TIRTempVariable { 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 52cf46a5f0e..472f1673f0d 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 @@ -10,274 +10,6 @@ import Imports::MemoryAccessKind import Imports::Opcode private import Imports::OperandTag -module InstructionSanity { - /** - * Holds if instruction `instr` is missing an expected operand with tag `tag`. - */ - query predicate missingOperand(Instruction instr, string message, IRFunction func, string funcText) { - exists(OperandTag tag | - instr.getOpcode().hasOperand(tag) and - not exists(NonPhiOperand operand | - operand = instr.getAnOperand() and - operand.getOperandTag() = tag - ) and - message = - "Instruction '" + instr.getOpcode().toString() + - "' is missing an expected operand with tag '" + tag.toString() + "' in function '$@'." and - func = instr.getEnclosingIRFunction() and - funcText = Language::getIdentityString(func.getFunction()) - ) - } - - /** - * Holds if instruction `instr` has an unexpected operand with tag `tag`. - */ - query predicate unexpectedOperand(Instruction instr, OperandTag tag) { - exists(NonPhiOperand operand | - operand = instr.getAnOperand() and - operand.getOperandTag() = tag - ) and - not instr.getOpcode().hasOperand(tag) and - not (instr instanceof CallInstruction and tag instanceof ArgumentOperandTag) and - not ( - instr instanceof BuiltInOperationInstruction and tag instanceof PositionalArgumentOperandTag - ) and - not (instr instanceof InlineAsmInstruction and tag instanceof AsmOperandTag) - } - - /** - * Holds if instruction `instr` has multiple operands with tag `tag`. - */ - query predicate duplicateOperand( - Instruction instr, string message, IRFunction func, string funcText - ) { - exists(OperandTag tag, int operandCount | - operandCount = - strictcount(NonPhiOperand operand | - operand = instr.getAnOperand() and - operand.getOperandTag() = tag - ) and - operandCount > 1 and - not tag instanceof UnmodeledUseOperandTag and - message = - "Instruction has " + operandCount + " operands with tag '" + tag.toString() + "'" + - " in function '$@'." and - func = instr.getEnclosingIRFunction() and - funcText = Language::getIdentityString(func.getFunction()) - ) - } - - /** - * Holds if `Phi` instruction `instr` is missing an operand corresponding to - * the predecessor block `pred`. - */ - query predicate missingPhiOperand(PhiInstruction instr, IRBlock pred) { - pred = instr.getBlock().getAPredecessor() and - not exists(PhiInputOperand operand | - operand = instr.getAnOperand() and - operand.getPredecessorBlock() = pred - ) - } - - query predicate missingOperandType(Operand operand, string message) { - exists(Language::Function func, Instruction use | - not exists(operand.getType()) and - use = operand.getUse() and - func = use.getEnclosingFunction() and - message = - "Operand '" + operand.toString() + "' of instruction '" + use.getOpcode().toString() + - "' missing type in function '" + Language::getIdentityString(func) + "'." - ) - } - - query predicate duplicateChiOperand( - ChiInstruction chi, string message, IRFunction func, string funcText - ) { - chi.getTotal() = chi.getPartial() and - message = - "Chi instruction for " + chi.getPartial().toString() + - " has duplicate operands in function $@" and - func = chi.getEnclosingIRFunction() and - funcText = Language::getIdentityString(func.getFunction()) - } - - query predicate sideEffectWithoutPrimary( - SideEffectInstruction instr, string message, IRFunction func, string funcText - ) { - not exists(instr.getPrimaryInstruction()) and - message = "Side effect instruction missing primary instruction in function $@" and - func = instr.getEnclosingIRFunction() and - funcText = Language::getIdentityString(func.getFunction()) - } - - /** - * Holds if an instruction, other than `ExitFunction`, has no successors. - */ - query predicate instructionWithoutSuccessor(Instruction instr) { - not exists(instr.getASuccessor()) and - not instr instanceof ExitFunctionInstruction and - // Phi instructions aren't linked into the instruction-level flow graph. - not instr instanceof PhiInstruction and - not instr instanceof UnreachedInstruction - } - - /** - * Holds if there are multiple (`n`) edges of kind `kind` from `source`, - * where `target` is among the targets of those edges. - */ - query predicate ambiguousSuccessors(Instruction source, EdgeKind kind, int n, Instruction target) { - n = strictcount(Instruction t | source.getSuccessor(kind) = t) and - n > 1 and - source.getSuccessor(kind) = target - } - - /** - * Holds if `instr` in `f` is part of a loop even though the AST of `f` - * contains no element that can cause loops. - */ - query predicate unexplainedLoop(Language::Function f, Instruction instr) { - exists(IRBlock block | - instr.getBlock() = block and - block.getEnclosingFunction() = f and - block.getASuccessor+() = block - ) and - not Language::hasPotentialLoop(f) - } - - /** - * Holds if a `Phi` instruction is present in a block with fewer than two - * predecessors. - */ - query predicate unnecessaryPhiInstruction(PhiInstruction instr) { - count(instr.getBlock().getAPredecessor()) < 2 - } - - /** - * Holds if operand `operand` consumes a value that was defined in - * a different function. - */ - query predicate operandAcrossFunctions(Operand operand, Instruction instr, Instruction defInstr) { - operand.getUse() = instr and - operand.getAnyDef() = defInstr and - instr.getEnclosingIRFunction() != defInstr.getEnclosingIRFunction() - } - - /** - * Holds if instruction `instr` is not in exactly one block. - */ - query predicate instructionWithoutUniqueBlock(Instruction instr, int blockCount) { - blockCount = count(instr.getBlock()) and - blockCount != 1 - } - - private predicate forwardEdge(IRBlock b1, IRBlock b2) { - b1.getASuccessor() = b2 and - not b1.getBackEdgeSuccessor(_) = b2 - } - - /** - * Holds if `f` contains a loop in which no edge is a back edge. - * - * This check ensures we don't have too _few_ back edges. - */ - query predicate containsLoopOfForwardEdges(IRFunction f) { - exists(IRBlock block | - forwardEdge+(block, block) and - block.getEnclosingIRFunction() = f - ) - } - - /** - * Holds if `block` is reachable from its function entry point but would not - * be reachable by traversing only forward edges. This check is skipped for - * functions containing `goto` statements as the property does not generally - * hold there. - * - * This check ensures we don't have too _many_ back edges. - */ - query predicate lostReachability(IRBlock block) { - exists(IRFunction f, IRBlock entry | - entry = f.getEntryBlock() and - entry.getASuccessor+() = block and - not forwardEdge+(entry, block) and - not Language::hasGoto(f.getFunction()) - ) - } - - /** - * Holds if the number of back edges differs between the `Instruction` graph - * and the `IRBlock` graph. - */ - query predicate backEdgeCountMismatch(Language::Function f, int fromInstr, int fromBlock) { - fromInstr = - count(Instruction i1, Instruction i2 | - i1.getEnclosingFunction() = f and i1.getBackEdgeSuccessor(_) = i2 - ) and - fromBlock = - count(IRBlock b1, IRBlock b2 | - b1.getEnclosingFunction() = f and b1.getBackEdgeSuccessor(_) = b2 - ) and - fromInstr != fromBlock - } - - /** - * Gets the point in the function at which the specified operand is evaluated. For most operands, - * this is at the instruction that consumes the use. For a `PhiInputOperand`, the effective point - * of evaluation is at the end of the corresponding predecessor block. - */ - private predicate pointOfEvaluation(Operand operand, IRBlock block, int index) { - block = operand.(PhiInputOperand).getPredecessorBlock() and - index = block.getInstructionCount() - or - exists(Instruction use | - use = operand.(NonPhiOperand).getUse() and - block.getInstruction(index) = use - ) - } - - /** - * Holds if `useOperand` has a definition that does not dominate the use. - */ - query predicate useNotDominatedByDefinition( - Operand useOperand, string message, IRFunction func, string funcText - ) { - exists(IRBlock useBlock, int useIndex, Instruction defInstr, IRBlock defBlock, int defIndex | - not useOperand.getUse() instanceof UnmodeledUseInstruction and - not defInstr instanceof UnmodeledDefinitionInstruction and - pointOfEvaluation(useOperand, useBlock, useIndex) and - defInstr = useOperand.getAnyDef() and - ( - defInstr instanceof PhiInstruction and - defBlock = defInstr.getBlock() and - defIndex = -1 - or - defBlock.getInstruction(defIndex) = defInstr - ) and - not ( - defBlock.strictlyDominates(useBlock) - or - defBlock = useBlock and - defIndex < useIndex - ) and - message = - "Operand '" + useOperand.toString() + - "' is not dominated by its definition in function '$@'." and - func = useOperand.getEnclosingIRFunction() and - 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()) - } -} - /** * Gets an `Instruction` that is contained in `IRFunction`, and has a location with the specified * `File` and line number. Used for assigning register names when printing IR. @@ -1204,6 +936,11 @@ class CallInstruction extends Instruction { final Instruction getPositionalArgument(int index) { result = getPositionalArgumentOperand(index).getDef() } + + /** + * Gets the number of arguments of the call, including the `this` pointer, if any. + */ + final int getNumberOfArguments() { result = count(this.getAnArgumentOperand()) } } /** @@ -1352,6 +1089,26 @@ class SizedBufferMayWriteSideEffectInstruction extends WriteSideEffectInstructio Instruction getSizeDef() { result = getAnOperand().(BufferSizeOperand).getDef() } } +/** + * An instruction representing the initial value of newly allocated memory, e.g. the result of a + * call to `malloc`. + */ +class InitializeDynamicAllocationInstruction extends SideEffectInstruction { + InitializeDynamicAllocationInstruction() { + getOpcode() instanceof Opcode::InitializeDynamicAllocation + } + + /** + * Gets the address of the allocation this instruction is initializing. + */ + final AddressOperand getAllocationAddressOperand() { result = getAnOperand() } + + /** + * Gets the operand for the allocation this instruction is initializing. + */ + final Instruction getAllocationAddress() { result = getAllocationAddressOperand().getDef() } +} + /** * An instruction representing a GNU or MSVC inline assembly statement. */ diff --git a/csharp/ql/src/semmle/code/csharp/ir/implementation/unaliased_ssa/internal/SSAConstruction.qll b/csharp/ql/src/semmle/code/csharp/ir/implementation/unaliased_ssa/internal/SSAConstruction.qll index 62753e298cd..6578968b1ff 100644 --- a/csharp/ql/src/semmle/code/csharp/ir/implementation/unaliased_ssa/internal/SSAConstruction.qll +++ b/csharp/ql/src/semmle/code/csharp/ir/implementation/unaliased_ssa/internal/SSAConstruction.qll @@ -665,17 +665,18 @@ module DefUse { private predicate definitionReachesRank( Alias::MemoryLocation useLocation, OldBlock block, int defRank, int reachesRank ) { + // The def always reaches the next use, even if there is also a def on the + // use instruction. hasDefinitionAtRank(useLocation, _, block, defRank, _) and - reachesRank <= exitRank(useLocation, block) and // Without this, the predicate would be infinite. - ( - // The def always reaches the next use, even if there is also a def on the - // use instruction. - reachesRank = defRank + 1 - or - // If the def reached the previous rank, it also reaches the current rank, - // unless there was another def at the previous rank. - definitionReachesRank(useLocation, block, defRank, reachesRank - 1) and - not hasDefinitionAtRank(useLocation, _, block, reachesRank - 1, _) + reachesRank = defRank + 1 + or + // If the def reached the previous rank, it also reaches the current rank, + // unless there was another def at the previous rank. + exists(int prevRank | + reachesRank = prevRank + 1 and + definitionReachesRank(useLocation, block, defRank, prevRank) and + not prevRank = exitRank(useLocation, block) and + not hasDefinitionAtRank(useLocation, _, block, prevRank, _) ) } diff --git a/csharp/ql/test/experimental/README.md b/csharp/ql/test/experimental/README.md new file mode 100644 index 00000000000..2a9db304b9f --- /dev/null +++ b/csharp/ql/test/experimental/README.md @@ -0,0 +1 @@ +This directory contains tests for [experimental](../../../../docs/experimental.md) CodeQL queries and libraries. diff --git a/csharp/ql/test/library-tests/dataflow/global/TaintTrackingEdges.expected b/csharp/ql/test/library-tests/dataflow/global/TaintTrackingEdges.expected index acfcdbbf147..bd9d777b9dd 100644 --- a/csharp/ql/test/library-tests/dataflow/global/TaintTrackingEdges.expected +++ b/csharp/ql/test/library-tests/dataflow/global/TaintTrackingEdges.expected @@ -1381,7 +1381,7 @@ | GlobalDataFlow.cs:78:30:78:34 | SSA def(sink3) : Object | GlobalDataFlow.cs:79:15:79:19 | access to local variable sink3 | | GlobalDataFlow.cs:78:30:78:34 | SSA def(sink3) : Object | GlobalDataFlow.cs:79:15:79:19 | access to local variable sink3 : Object | | GlobalDataFlow.cs:78:30:78:34 | SSA def(sink3) : Object | GlobalDataFlow.cs:80:23:80:65 | (...) ... | -| GlobalDataFlow.cs:78:30:78:34 | SSA def(sink3) : Object | GlobalDataFlow.cs:80:23:80:65 | (...) ... : IEnumerable | +| GlobalDataFlow.cs:78:30:78:34 | SSA def(sink3) : Object | GlobalDataFlow.cs:80:23:80:65 | (...) ... : String[] | | GlobalDataFlow.cs:78:30:78:34 | SSA def(sink3) : Object | GlobalDataFlow.cs:80:44:80:65 | array creation of type String[] | | GlobalDataFlow.cs:78:30:78:34 | SSA def(sink3) : Object | GlobalDataFlow.cs:80:44:80:65 | array creation of type String[] : String[] | | GlobalDataFlow.cs:78:30:78:34 | SSA def(sink3) : Object | GlobalDataFlow.cs:80:59:80:63 | access to local variable sink3 | @@ -1391,7 +1391,7 @@ | GlobalDataFlow.cs:78:30:78:34 | SSA def(sink3) : String | GlobalDataFlow.cs:79:15:79:19 | access to local variable sink3 | | GlobalDataFlow.cs:78:30:78:34 | SSA def(sink3) : String | GlobalDataFlow.cs:79:15:79:19 | access to local variable sink3 : String | | GlobalDataFlow.cs:78:30:78:34 | SSA def(sink3) : String | GlobalDataFlow.cs:80:23:80:65 | (...) ... | -| GlobalDataFlow.cs:78:30:78:34 | SSA def(sink3) : String | GlobalDataFlow.cs:80:23:80:65 | (...) ... : IEnumerable | +| GlobalDataFlow.cs:78:30:78:34 | SSA def(sink3) : String | GlobalDataFlow.cs:80:23:80:65 | (...) ... : String[] | | GlobalDataFlow.cs:78:30:78:34 | SSA def(sink3) : String | GlobalDataFlow.cs:80:44:80:65 | array creation of type String[] | | GlobalDataFlow.cs:78:30:78:34 | SSA def(sink3) : String | GlobalDataFlow.cs:80:44:80:65 | array creation of type String[] : String[] | | GlobalDataFlow.cs:78:30:78:34 | SSA def(sink3) : String | GlobalDataFlow.cs:80:59:80:63 | access to local variable sink3 | @@ -1401,7 +1401,7 @@ | GlobalDataFlow.cs:78:30:78:34 | SSA def(sink3) : T | GlobalDataFlow.cs:79:15:79:19 | access to local variable sink3 | | GlobalDataFlow.cs:78:30:78:34 | SSA def(sink3) : T | GlobalDataFlow.cs:79:15:79:19 | access to local variable sink3 : T | | GlobalDataFlow.cs:78:30:78:34 | SSA def(sink3) : T | GlobalDataFlow.cs:80:23:80:65 | (...) ... | -| GlobalDataFlow.cs:78:30:78:34 | SSA def(sink3) : T | GlobalDataFlow.cs:80:23:80:65 | (...) ... : IEnumerable | +| GlobalDataFlow.cs:78:30:78:34 | SSA def(sink3) : T | GlobalDataFlow.cs:80:23:80:65 | (...) ... : String[] | | GlobalDataFlow.cs:78:30:78:34 | SSA def(sink3) : T | GlobalDataFlow.cs:80:44:80:65 | array creation of type String[] | | GlobalDataFlow.cs:78:30:78:34 | SSA def(sink3) : T | GlobalDataFlow.cs:80:44:80:65 | array creation of type String[] : String[] | | GlobalDataFlow.cs:78:30:78:34 | SSA def(sink3) : T | GlobalDataFlow.cs:80:59:80:63 | access to local variable sink3 | @@ -1413,7 +1413,7 @@ | GlobalDataFlow.cs:78:41:78:45 | access to local variable sink3 : String | GlobalDataFlow.cs:287:50:287:50 | z | | GlobalDataFlow.cs:78:41:78:45 | access to local variable sink3 : String | GlobalDataFlow.cs:287:50:287:50 | z : String | | GlobalDataFlow.cs:79:15:79:19 | access to local variable sink3 : Object | GlobalDataFlow.cs:80:23:80:65 | (...) ... | -| GlobalDataFlow.cs:79:15:79:19 | access to local variable sink3 : Object | GlobalDataFlow.cs:80:23:80:65 | (...) ... : IEnumerable | +| GlobalDataFlow.cs:79:15:79:19 | access to local variable sink3 : Object | GlobalDataFlow.cs:80:23:80:65 | (...) ... : String[] | | GlobalDataFlow.cs:79:15:79:19 | access to local variable sink3 : Object | GlobalDataFlow.cs:80:44:80:65 | array creation of type String[] | | GlobalDataFlow.cs:79:15:79:19 | access to local variable sink3 : Object | GlobalDataFlow.cs:80:44:80:65 | array creation of type String[] : String[] | | GlobalDataFlow.cs:79:15:79:19 | access to local variable sink3 : Object | GlobalDataFlow.cs:80:59:80:63 | access to local variable sink3 | @@ -1421,7 +1421,7 @@ | GlobalDataFlow.cs:79:15:79:19 | access to local variable sink3 : Object | GlobalDataFlow.cs:135:29:135:33 | access to local variable sink3 | | GlobalDataFlow.cs:79:15:79:19 | access to local variable sink3 : Object | GlobalDataFlow.cs:135:29:135:33 | access to local variable sink3 : Object | | GlobalDataFlow.cs:79:15:79:19 | access to local variable sink3 : String | GlobalDataFlow.cs:80:23:80:65 | (...) ... | -| GlobalDataFlow.cs:79:15:79:19 | access to local variable sink3 : String | GlobalDataFlow.cs:80:23:80:65 | (...) ... : IEnumerable | +| GlobalDataFlow.cs:79:15:79:19 | access to local variable sink3 : String | GlobalDataFlow.cs:80:23:80:65 | (...) ... : String[] | | GlobalDataFlow.cs:79:15:79:19 | access to local variable sink3 : String | GlobalDataFlow.cs:80:44:80:65 | array creation of type String[] | | GlobalDataFlow.cs:79:15:79:19 | access to local variable sink3 : String | GlobalDataFlow.cs:80:44:80:65 | array creation of type String[] : String[] | | GlobalDataFlow.cs:79:15:79:19 | access to local variable sink3 : String | GlobalDataFlow.cs:80:59:80:63 | access to local variable sink3 | @@ -1429,7 +1429,7 @@ | GlobalDataFlow.cs:79:15:79:19 | access to local variable sink3 : String | GlobalDataFlow.cs:135:29:135:33 | access to local variable sink3 | | GlobalDataFlow.cs:79:15:79:19 | access to local variable sink3 : String | GlobalDataFlow.cs:135:29:135:33 | access to local variable sink3 : String | | GlobalDataFlow.cs:79:15:79:19 | access to local variable sink3 : T | GlobalDataFlow.cs:80:23:80:65 | (...) ... | -| GlobalDataFlow.cs:79:15:79:19 | access to local variable sink3 : T | GlobalDataFlow.cs:80:23:80:65 | (...) ... : IEnumerable | +| GlobalDataFlow.cs:79:15:79:19 | access to local variable sink3 : T | GlobalDataFlow.cs:80:23:80:65 | (...) ... : String[] | | GlobalDataFlow.cs:79:15:79:19 | access to local variable sink3 : T | GlobalDataFlow.cs:80:44:80:65 | array creation of type String[] | | GlobalDataFlow.cs:79:15:79:19 | access to local variable sink3 : T | GlobalDataFlow.cs:80:44:80:65 | array creation of type String[] : String[] | | GlobalDataFlow.cs:79:15:79:19 | access to local variable sink3 : T | GlobalDataFlow.cs:80:59:80:63 | access to local variable sink3 | @@ -1439,7 +1439,7 @@ | GlobalDataFlow.cs:80:13:80:85 | SSA def(sink13) : IEnumerable | GlobalDataFlow.cs:81:15:81:20 | access to local variable sink13 | | GlobalDataFlow.cs:80:13:80:85 | SSA def(sink13) : IEnumerable | GlobalDataFlow.cs:81:15:81:20 | access to local variable sink13 : IEnumerable | | GlobalDataFlow.cs:80:13:80:85 | SSA def(sink13) : IEnumerable | GlobalDataFlow.cs:82:23:82:74 | (...) ... | -| GlobalDataFlow.cs:80:13:80:85 | SSA def(sink13) : IEnumerable | GlobalDataFlow.cs:82:23:82:74 | (...) ... : IEnumerable | +| GlobalDataFlow.cs:80:13:80:85 | SSA def(sink13) : IEnumerable | GlobalDataFlow.cs:82:23:82:74 | (...) ... : String[] | | GlobalDataFlow.cs:80:13:80:85 | SSA def(sink13) : IEnumerable | GlobalDataFlow.cs:82:44:82:74 | array creation of type String[] | | GlobalDataFlow.cs:80:13:80:85 | SSA def(sink13) : IEnumerable | GlobalDataFlow.cs:82:44:82:74 | array creation of type String[] : String[] | | GlobalDataFlow.cs:80:13:80:85 | SSA def(sink13) : IEnumerable | GlobalDataFlow.cs:82:59:82:64 | access to local variable sink13 | @@ -1449,7 +1449,7 @@ | GlobalDataFlow.cs:80:13:80:85 | SSA def(sink13) : IEnumerable | GlobalDataFlow.cs:81:15:81:20 | access to local variable sink13 | | GlobalDataFlow.cs:80:13:80:85 | SSA def(sink13) : IEnumerable | GlobalDataFlow.cs:81:15:81:20 | access to local variable sink13 : IEnumerable | | GlobalDataFlow.cs:80:13:80:85 | SSA def(sink13) : IEnumerable | GlobalDataFlow.cs:82:23:82:74 | (...) ... | -| GlobalDataFlow.cs:80:13:80:85 | SSA def(sink13) : IEnumerable | GlobalDataFlow.cs:82:23:82:74 | (...) ... : IEnumerable | +| GlobalDataFlow.cs:80:13:80:85 | SSA def(sink13) : IEnumerable | GlobalDataFlow.cs:82:23:82:74 | (...) ... : String[] | | GlobalDataFlow.cs:80:13:80:85 | SSA def(sink13) : IEnumerable | GlobalDataFlow.cs:82:44:82:74 | array creation of type String[] | | GlobalDataFlow.cs:80:13:80:85 | SSA def(sink13) : IEnumerable | GlobalDataFlow.cs:82:44:82:74 | array creation of type String[] : String[] | | GlobalDataFlow.cs:80:13:80:85 | SSA def(sink13) : IEnumerable | GlobalDataFlow.cs:82:59:82:64 | access to local variable sink13 | @@ -1461,7 +1461,7 @@ | GlobalDataFlow.cs:80:22:80:85 | call to method SelectEven : IEnumerable | GlobalDataFlow.cs:81:15:81:20 | access to local variable sink13 | | GlobalDataFlow.cs:80:22:80:85 | call to method SelectEven : IEnumerable | GlobalDataFlow.cs:81:15:81:20 | access to local variable sink13 : IEnumerable | | GlobalDataFlow.cs:80:22:80:85 | call to method SelectEven : IEnumerable | GlobalDataFlow.cs:82:23:82:74 | (...) ... | -| GlobalDataFlow.cs:80:22:80:85 | call to method SelectEven : IEnumerable | GlobalDataFlow.cs:82:23:82:74 | (...) ... : IEnumerable | +| GlobalDataFlow.cs:80:22:80:85 | call to method SelectEven : IEnumerable | GlobalDataFlow.cs:82:23:82:74 | (...) ... : String[] | | GlobalDataFlow.cs:80:22:80:85 | call to method SelectEven : IEnumerable | GlobalDataFlow.cs:82:44:82:74 | array creation of type String[] | | GlobalDataFlow.cs:80:22:80:85 | call to method SelectEven : IEnumerable | GlobalDataFlow.cs:82:44:82:74 | array creation of type String[] : String[] | | GlobalDataFlow.cs:80:22:80:85 | call to method SelectEven : IEnumerable | GlobalDataFlow.cs:82:59:82:64 | access to local variable sink13 | @@ -1473,7 +1473,7 @@ | GlobalDataFlow.cs:80:22:80:85 | call to method SelectEven : IEnumerable | GlobalDataFlow.cs:81:15:81:20 | access to local variable sink13 | | GlobalDataFlow.cs:80:22:80:85 | call to method SelectEven : IEnumerable | GlobalDataFlow.cs:81:15:81:20 | access to local variable sink13 : IEnumerable | | GlobalDataFlow.cs:80:22:80:85 | call to method SelectEven : IEnumerable | GlobalDataFlow.cs:82:23:82:74 | (...) ... | -| GlobalDataFlow.cs:80:22:80:85 | call to method SelectEven : IEnumerable | GlobalDataFlow.cs:82:23:82:74 | (...) ... : IEnumerable | +| GlobalDataFlow.cs:80:22:80:85 | call to method SelectEven : IEnumerable | GlobalDataFlow.cs:82:23:82:74 | (...) ... : String[] | | GlobalDataFlow.cs:80:22:80:85 | call to method SelectEven : IEnumerable | GlobalDataFlow.cs:82:44:82:74 | array creation of type String[] | | GlobalDataFlow.cs:80:22:80:85 | call to method SelectEven : IEnumerable | GlobalDataFlow.cs:82:44:82:74 | array creation of type String[] : String[] | | GlobalDataFlow.cs:80:22:80:85 | call to method SelectEven : IEnumerable | GlobalDataFlow.cs:82:59:82:64 | access to local variable sink13 | @@ -1491,19 +1491,19 @@ | GlobalDataFlow.cs:80:44:80:65 | array creation of type String[] : String[] | GlobalDataFlow.cs:80:23:80:65 | (...) ... | | GlobalDataFlow.cs:80:44:80:65 | array creation of type String[] : String[] | GlobalDataFlow.cs:80:23:80:65 | (...) ... : String[] | | GlobalDataFlow.cs:80:59:80:63 | access to local variable sink3 : Object | GlobalDataFlow.cs:80:23:80:65 | (...) ... | -| GlobalDataFlow.cs:80:59:80:63 | access to local variable sink3 : Object | GlobalDataFlow.cs:80:23:80:65 | (...) ... : IEnumerable | +| GlobalDataFlow.cs:80:59:80:63 | access to local variable sink3 : Object | GlobalDataFlow.cs:80:23:80:65 | (...) ... : String[] | | GlobalDataFlow.cs:80:59:80:63 | access to local variable sink3 : Object | GlobalDataFlow.cs:80:44:80:65 | array creation of type String[] | | GlobalDataFlow.cs:80:59:80:63 | access to local variable sink3 : Object | GlobalDataFlow.cs:80:44:80:65 | array creation of type String[] : String[] | | GlobalDataFlow.cs:80:59:80:63 | access to local variable sink3 : Object | GlobalDataFlow.cs:135:29:135:33 | access to local variable sink3 | | GlobalDataFlow.cs:80:59:80:63 | access to local variable sink3 : Object | GlobalDataFlow.cs:135:29:135:33 | access to local variable sink3 : Object | | GlobalDataFlow.cs:80:59:80:63 | access to local variable sink3 : String | GlobalDataFlow.cs:80:23:80:65 | (...) ... | -| GlobalDataFlow.cs:80:59:80:63 | access to local variable sink3 : String | GlobalDataFlow.cs:80:23:80:65 | (...) ... : IEnumerable | +| GlobalDataFlow.cs:80:59:80:63 | access to local variable sink3 : String | GlobalDataFlow.cs:80:23:80:65 | (...) ... : String[] | | GlobalDataFlow.cs:80:59:80:63 | access to local variable sink3 : String | GlobalDataFlow.cs:80:44:80:65 | array creation of type String[] | | GlobalDataFlow.cs:80:59:80:63 | access to local variable sink3 : String | GlobalDataFlow.cs:80:44:80:65 | array creation of type String[] : String[] | | GlobalDataFlow.cs:80:59:80:63 | access to local variable sink3 : String | GlobalDataFlow.cs:135:29:135:33 | access to local variable sink3 | | GlobalDataFlow.cs:80:59:80:63 | access to local variable sink3 : String | GlobalDataFlow.cs:135:29:135:33 | access to local variable sink3 : String | | GlobalDataFlow.cs:80:59:80:63 | access to local variable sink3 : T | GlobalDataFlow.cs:80:23:80:65 | (...) ... | -| GlobalDataFlow.cs:80:59:80:63 | access to local variable sink3 : T | GlobalDataFlow.cs:80:23:80:65 | (...) ... : IEnumerable | +| GlobalDataFlow.cs:80:59:80:63 | access to local variable sink3 : T | GlobalDataFlow.cs:80:23:80:65 | (...) ... : String[] | | GlobalDataFlow.cs:80:59:80:63 | access to local variable sink3 : T | GlobalDataFlow.cs:80:44:80:65 | array creation of type String[] | | GlobalDataFlow.cs:80:59:80:63 | access to local variable sink3 : T | GlobalDataFlow.cs:80:44:80:65 | array creation of type String[] : String[] | | GlobalDataFlow.cs:80:59:80:63 | access to local variable sink3 : T | GlobalDataFlow.cs:135:29:135:33 | access to local variable sink3 | @@ -1521,7 +1521,7 @@ | GlobalDataFlow.cs:80:84:80:84 | access to parameter x : String | GlobalDataFlow.cs:431:44:431:47 | delegate call | | GlobalDataFlow.cs:80:84:80:84 | access to parameter x : String | GlobalDataFlow.cs:431:44:431:47 | delegate call : String | | GlobalDataFlow.cs:81:15:81:20 | access to local variable sink13 : IEnumerable | GlobalDataFlow.cs:82:23:82:74 | (...) ... | -| GlobalDataFlow.cs:81:15:81:20 | access to local variable sink13 : IEnumerable | GlobalDataFlow.cs:82:23:82:74 | (...) ... : IEnumerable | +| GlobalDataFlow.cs:81:15:81:20 | access to local variable sink13 : IEnumerable | GlobalDataFlow.cs:82:23:82:74 | (...) ... : String[] | | GlobalDataFlow.cs:81:15:81:20 | access to local variable sink13 : IEnumerable | GlobalDataFlow.cs:82:44:82:74 | array creation of type String[] | | GlobalDataFlow.cs:81:15:81:20 | access to local variable sink13 : IEnumerable | GlobalDataFlow.cs:82:44:82:74 | array creation of type String[] : String[] | | GlobalDataFlow.cs:81:15:81:20 | access to local variable sink13 : IEnumerable | GlobalDataFlow.cs:82:59:82:64 | access to local variable sink13 | @@ -1529,7 +1529,7 @@ | GlobalDataFlow.cs:81:15:81:20 | access to local variable sink13 : IEnumerable | GlobalDataFlow.cs:82:59:82:72 | call to method First | | GlobalDataFlow.cs:81:15:81:20 | access to local variable sink13 : IEnumerable | GlobalDataFlow.cs:82:59:82:72 | call to method First : String | | GlobalDataFlow.cs:81:15:81:20 | access to local variable sink13 : IEnumerable | GlobalDataFlow.cs:82:23:82:74 | (...) ... | -| GlobalDataFlow.cs:81:15:81:20 | access to local variable sink13 : IEnumerable | GlobalDataFlow.cs:82:23:82:74 | (...) ... : IEnumerable | +| GlobalDataFlow.cs:81:15:81:20 | access to local variable sink13 : IEnumerable | GlobalDataFlow.cs:82:23:82:74 | (...) ... : String[] | | GlobalDataFlow.cs:81:15:81:20 | access to local variable sink13 : IEnumerable | GlobalDataFlow.cs:82:44:82:74 | array creation of type String[] | | GlobalDataFlow.cs:81:15:81:20 | access to local variable sink13 : IEnumerable | GlobalDataFlow.cs:82:44:82:74 | array creation of type String[] : String[] | | GlobalDataFlow.cs:81:15:81:20 | access to local variable sink13 : IEnumerable | GlobalDataFlow.cs:82:59:82:64 | access to local variable sink13 | @@ -1539,7 +1539,7 @@ | GlobalDataFlow.cs:82:13:82:95 | SSA def(sink14) : IEnumerable | GlobalDataFlow.cs:83:15:83:20 | access to local variable sink14 | | GlobalDataFlow.cs:82:13:82:95 | SSA def(sink14) : IEnumerable | GlobalDataFlow.cs:83:15:83:20 | access to local variable sink14 : IEnumerable | | GlobalDataFlow.cs:82:13:82:95 | SSA def(sink14) : IEnumerable | GlobalDataFlow.cs:84:23:84:74 | (...) ... | -| GlobalDataFlow.cs:82:13:82:95 | SSA def(sink14) : IEnumerable | GlobalDataFlow.cs:84:23:84:74 | (...) ... : IEnumerable | +| GlobalDataFlow.cs:82:13:82:95 | SSA def(sink14) : IEnumerable | GlobalDataFlow.cs:84:23:84:74 | (...) ... : String[] | | GlobalDataFlow.cs:82:13:82:95 | SSA def(sink14) : IEnumerable | GlobalDataFlow.cs:84:44:84:74 | array creation of type String[] | | GlobalDataFlow.cs:82:13:82:95 | SSA def(sink14) : IEnumerable | GlobalDataFlow.cs:84:44:84:74 | array creation of type String[] : String[] | | GlobalDataFlow.cs:82:13:82:95 | SSA def(sink14) : IEnumerable | GlobalDataFlow.cs:84:59:84:64 | access to local variable sink14 | @@ -1553,7 +1553,7 @@ | GlobalDataFlow.cs:82:13:82:95 | SSA def(sink14) : IEnumerable | GlobalDataFlow.cs:90:75:90:88 | call to method First | | GlobalDataFlow.cs:82:13:82:95 | SSA def(sink14) : IEnumerable | GlobalDataFlow.cs:90:75:90:88 | call to method First : String | | GlobalDataFlow.cs:82:13:82:95 | SSA def(sink14) : IEnumerable | GlobalDataFlow.cs:116:21:116:72 | (...) ... | -| GlobalDataFlow.cs:82:13:82:95 | SSA def(sink14) : IEnumerable | GlobalDataFlow.cs:116:21:116:72 | (...) ... : IEnumerable | +| GlobalDataFlow.cs:82:13:82:95 | SSA def(sink14) : IEnumerable | GlobalDataFlow.cs:116:21:116:72 | (...) ... : String[] | | GlobalDataFlow.cs:82:13:82:95 | SSA def(sink14) : IEnumerable | GlobalDataFlow.cs:116:42:116:72 | array creation of type String[] | | GlobalDataFlow.cs:82:13:82:95 | SSA def(sink14) : IEnumerable | GlobalDataFlow.cs:116:42:116:72 | array creation of type String[] : String[] | | GlobalDataFlow.cs:82:13:82:95 | SSA def(sink14) : IEnumerable | GlobalDataFlow.cs:116:57:116:62 | access to local variable sink14 | @@ -1569,7 +1569,7 @@ | GlobalDataFlow.cs:82:22:82:95 | call to method Select : IEnumerable | GlobalDataFlow.cs:83:15:83:20 | access to local variable sink14 | | GlobalDataFlow.cs:82:22:82:95 | call to method Select : IEnumerable | GlobalDataFlow.cs:83:15:83:20 | access to local variable sink14 : IEnumerable | | GlobalDataFlow.cs:82:22:82:95 | call to method Select : IEnumerable | GlobalDataFlow.cs:84:23:84:74 | (...) ... | -| GlobalDataFlow.cs:82:22:82:95 | call to method Select : IEnumerable | GlobalDataFlow.cs:84:23:84:74 | (...) ... : IEnumerable | +| GlobalDataFlow.cs:82:22:82:95 | call to method Select : IEnumerable | GlobalDataFlow.cs:84:23:84:74 | (...) ... : String[] | | GlobalDataFlow.cs:82:22:82:95 | call to method Select : IEnumerable | GlobalDataFlow.cs:84:44:84:74 | array creation of type String[] | | GlobalDataFlow.cs:82:22:82:95 | call to method Select : IEnumerable | GlobalDataFlow.cs:84:44:84:74 | array creation of type String[] : String[] | | GlobalDataFlow.cs:82:22:82:95 | call to method Select : IEnumerable | GlobalDataFlow.cs:84:59:84:64 | access to local variable sink14 | @@ -1583,7 +1583,7 @@ | GlobalDataFlow.cs:82:22:82:95 | call to method Select : IEnumerable | GlobalDataFlow.cs:90:75:90:88 | call to method First | | GlobalDataFlow.cs:82:22:82:95 | call to method Select : IEnumerable | GlobalDataFlow.cs:90:75:90:88 | call to method First : String | | GlobalDataFlow.cs:82:22:82:95 | call to method Select : IEnumerable | GlobalDataFlow.cs:116:21:116:72 | (...) ... | -| GlobalDataFlow.cs:82:22:82:95 | call to method Select : IEnumerable | GlobalDataFlow.cs:116:21:116:72 | (...) ... : IEnumerable | +| GlobalDataFlow.cs:82:22:82:95 | call to method Select : IEnumerable | GlobalDataFlow.cs:116:21:116:72 | (...) ... : String[] | | GlobalDataFlow.cs:82:22:82:95 | call to method Select : IEnumerable | GlobalDataFlow.cs:116:42:116:72 | array creation of type String[] | | GlobalDataFlow.cs:82:22:82:95 | call to method Select : IEnumerable | GlobalDataFlow.cs:116:42:116:72 | array creation of type String[] : String[] | | GlobalDataFlow.cs:82:22:82:95 | call to method Select : IEnumerable | GlobalDataFlow.cs:116:57:116:62 | access to local variable sink14 | @@ -1605,19 +1605,19 @@ | GlobalDataFlow.cs:82:44:82:74 | array creation of type String[] : String[] | GlobalDataFlow.cs:82:23:82:74 | (...) ... | | GlobalDataFlow.cs:82:44:82:74 | array creation of type String[] : String[] | GlobalDataFlow.cs:82:23:82:74 | (...) ... : String[] | | GlobalDataFlow.cs:82:59:82:64 | access to local variable sink13 : IEnumerable | GlobalDataFlow.cs:82:23:82:74 | (...) ... | -| GlobalDataFlow.cs:82:59:82:64 | access to local variable sink13 : IEnumerable | GlobalDataFlow.cs:82:23:82:74 | (...) ... : IEnumerable | +| GlobalDataFlow.cs:82:59:82:64 | access to local variable sink13 : IEnumerable | GlobalDataFlow.cs:82:23:82:74 | (...) ... : String[] | | GlobalDataFlow.cs:82:59:82:64 | access to local variable sink13 : IEnumerable | GlobalDataFlow.cs:82:44:82:74 | array creation of type String[] | | GlobalDataFlow.cs:82:59:82:64 | access to local variable sink13 : IEnumerable | GlobalDataFlow.cs:82:44:82:74 | array creation of type String[] : String[] | | GlobalDataFlow.cs:82:59:82:64 | access to local variable sink13 : IEnumerable | GlobalDataFlow.cs:82:59:82:72 | call to method First | | GlobalDataFlow.cs:82:59:82:64 | access to local variable sink13 : IEnumerable | GlobalDataFlow.cs:82:59:82:72 | call to method First : String | | GlobalDataFlow.cs:82:59:82:64 | access to local variable sink13 : IEnumerable | GlobalDataFlow.cs:82:23:82:74 | (...) ... | -| GlobalDataFlow.cs:82:59:82:64 | access to local variable sink13 : IEnumerable | GlobalDataFlow.cs:82:23:82:74 | (...) ... : IEnumerable | +| GlobalDataFlow.cs:82:59:82:64 | access to local variable sink13 : IEnumerable | GlobalDataFlow.cs:82:23:82:74 | (...) ... : String[] | | GlobalDataFlow.cs:82:59:82:64 | access to local variable sink13 : IEnumerable | GlobalDataFlow.cs:82:44:82:74 | array creation of type String[] | | GlobalDataFlow.cs:82:59:82:64 | access to local variable sink13 : IEnumerable | GlobalDataFlow.cs:82:44:82:74 | array creation of type String[] : String[] | | GlobalDataFlow.cs:82:59:82:64 | access to local variable sink13 : IEnumerable | GlobalDataFlow.cs:82:59:82:72 | call to method First | | GlobalDataFlow.cs:82:59:82:64 | access to local variable sink13 : IEnumerable | GlobalDataFlow.cs:82:59:82:72 | call to method First : String | | GlobalDataFlow.cs:82:59:82:72 | call to method First : String | GlobalDataFlow.cs:82:23:82:74 | (...) ... | -| GlobalDataFlow.cs:82:59:82:72 | call to method First : String | GlobalDataFlow.cs:82:23:82:74 | (...) ... : IEnumerable | +| GlobalDataFlow.cs:82:59:82:72 | call to method First : String | GlobalDataFlow.cs:82:23:82:74 | (...) ... : String[] | | GlobalDataFlow.cs:82:59:82:72 | call to method First : String | GlobalDataFlow.cs:82:44:82:74 | array creation of type String[] | | GlobalDataFlow.cs:82:59:82:72 | call to method First : String | GlobalDataFlow.cs:82:44:82:74 | array creation of type String[] : String[] | | GlobalDataFlow.cs:82:84:82:94 | [output] delegate creation of type Func : String | GlobalDataFlow.cs:82:13:82:95 | SSA def(sink14) | @@ -1627,7 +1627,7 @@ | GlobalDataFlow.cs:82:84:82:94 | [output] delegate creation of type Func : String | GlobalDataFlow.cs:83:15:83:20 | access to local variable sink14 | | GlobalDataFlow.cs:82:84:82:94 | [output] delegate creation of type Func : String | GlobalDataFlow.cs:83:15:83:20 | access to local variable sink14 : IEnumerable | | GlobalDataFlow.cs:82:84:82:94 | [output] delegate creation of type Func : String | GlobalDataFlow.cs:84:23:84:74 | (...) ... | -| GlobalDataFlow.cs:82:84:82:94 | [output] delegate creation of type Func : String | GlobalDataFlow.cs:84:23:84:74 | (...) ... : IEnumerable | +| GlobalDataFlow.cs:82:84:82:94 | [output] delegate creation of type Func : String | GlobalDataFlow.cs:84:23:84:74 | (...) ... : String[] | | GlobalDataFlow.cs:82:84:82:94 | [output] delegate creation of type Func : String | GlobalDataFlow.cs:84:44:84:74 | array creation of type String[] | | GlobalDataFlow.cs:82:84:82:94 | [output] delegate creation of type Func : String | GlobalDataFlow.cs:84:44:84:74 | array creation of type String[] : String[] | | GlobalDataFlow.cs:82:84:82:94 | [output] delegate creation of type Func : String | GlobalDataFlow.cs:84:59:84:64 | access to local variable sink14 | @@ -1641,7 +1641,7 @@ | GlobalDataFlow.cs:82:84:82:94 | [output] delegate creation of type Func : String | GlobalDataFlow.cs:90:75:90:88 | call to method First | | GlobalDataFlow.cs:82:84:82:94 | [output] delegate creation of type Func : String | GlobalDataFlow.cs:90:75:90:88 | call to method First : String | | GlobalDataFlow.cs:82:84:82:94 | [output] delegate creation of type Func : String | GlobalDataFlow.cs:116:21:116:72 | (...) ... | -| GlobalDataFlow.cs:82:84:82:94 | [output] delegate creation of type Func : String | GlobalDataFlow.cs:116:21:116:72 | (...) ... : IEnumerable | +| GlobalDataFlow.cs:82:84:82:94 | [output] delegate creation of type Func : String | GlobalDataFlow.cs:116:21:116:72 | (...) ... : String[] | | GlobalDataFlow.cs:82:84:82:94 | [output] delegate creation of type Func : String | GlobalDataFlow.cs:116:42:116:72 | array creation of type String[] | | GlobalDataFlow.cs:82:84:82:94 | [output] delegate creation of type Func : String | GlobalDataFlow.cs:116:42:116:72 | array creation of type String[] : String[] | | GlobalDataFlow.cs:82:84:82:94 | [output] delegate creation of type Func : String | GlobalDataFlow.cs:116:57:116:62 | access to local variable sink14 | @@ -1659,7 +1659,7 @@ | GlobalDataFlow.cs:82:84:82:94 | [output] delegate creation of type Func : T | GlobalDataFlow.cs:83:15:83:20 | access to local variable sink14 | | GlobalDataFlow.cs:82:84:82:94 | [output] delegate creation of type Func : T | GlobalDataFlow.cs:83:15:83:20 | access to local variable sink14 : IEnumerable | | GlobalDataFlow.cs:82:84:82:94 | [output] delegate creation of type Func : T | GlobalDataFlow.cs:84:23:84:74 | (...) ... | -| GlobalDataFlow.cs:82:84:82:94 | [output] delegate creation of type Func : T | GlobalDataFlow.cs:84:23:84:74 | (...) ... : IEnumerable | +| GlobalDataFlow.cs:82:84:82:94 | [output] delegate creation of type Func : T | GlobalDataFlow.cs:84:23:84:74 | (...) ... : String[] | | GlobalDataFlow.cs:82:84:82:94 | [output] delegate creation of type Func : T | GlobalDataFlow.cs:84:44:84:74 | array creation of type String[] | | GlobalDataFlow.cs:82:84:82:94 | [output] delegate creation of type Func : T | GlobalDataFlow.cs:84:44:84:74 | array creation of type String[] : String[] | | GlobalDataFlow.cs:82:84:82:94 | [output] delegate creation of type Func : T | GlobalDataFlow.cs:84:59:84:64 | access to local variable sink14 | @@ -1673,7 +1673,7 @@ | GlobalDataFlow.cs:82:84:82:94 | [output] delegate creation of type Func : T | GlobalDataFlow.cs:90:75:90:88 | call to method First | | GlobalDataFlow.cs:82:84:82:94 | [output] delegate creation of type Func : T | GlobalDataFlow.cs:90:75:90:88 | call to method First : String | | GlobalDataFlow.cs:82:84:82:94 | [output] delegate creation of type Func : T | GlobalDataFlow.cs:116:21:116:72 | (...) ... | -| GlobalDataFlow.cs:82:84:82:94 | [output] delegate creation of type Func : T | GlobalDataFlow.cs:116:21:116:72 | (...) ... : IEnumerable | +| GlobalDataFlow.cs:82:84:82:94 | [output] delegate creation of type Func : T | GlobalDataFlow.cs:116:21:116:72 | (...) ... : String[] | | GlobalDataFlow.cs:82:84:82:94 | [output] delegate creation of type Func : T | GlobalDataFlow.cs:116:42:116:72 | array creation of type String[] | | GlobalDataFlow.cs:82:84:82:94 | [output] delegate creation of type Func : T | GlobalDataFlow.cs:116:42:116:72 | array creation of type String[] : String[] | | GlobalDataFlow.cs:82:84:82:94 | [output] delegate creation of type Func : T | GlobalDataFlow.cs:116:57:116:62 | access to local variable sink14 | @@ -1685,7 +1685,7 @@ | GlobalDataFlow.cs:82:84:82:94 | [output] delegate creation of type Func : T | GlobalDataFlow.cs:122:20:122:25 | access to local variable sink14 | | GlobalDataFlow.cs:82:84:82:94 | [output] delegate creation of type Func : T | GlobalDataFlow.cs:122:20:122:25 | access to local variable sink14 : IEnumerable | | GlobalDataFlow.cs:83:15:83:20 | access to local variable sink14 : IEnumerable | GlobalDataFlow.cs:84:23:84:74 | (...) ... | -| GlobalDataFlow.cs:83:15:83:20 | access to local variable sink14 : IEnumerable | GlobalDataFlow.cs:84:23:84:74 | (...) ... : IEnumerable | +| GlobalDataFlow.cs:83:15:83:20 | access to local variable sink14 : IEnumerable | GlobalDataFlow.cs:84:23:84:74 | (...) ... : String[] | | GlobalDataFlow.cs:83:15:83:20 | access to local variable sink14 : IEnumerable | GlobalDataFlow.cs:84:44:84:74 | array creation of type String[] | | GlobalDataFlow.cs:83:15:83:20 | access to local variable sink14 : IEnumerable | GlobalDataFlow.cs:84:44:84:74 | array creation of type String[] : String[] | | GlobalDataFlow.cs:83:15:83:20 | access to local variable sink14 : IEnumerable | GlobalDataFlow.cs:84:59:84:64 | access to local variable sink14 | @@ -1699,7 +1699,7 @@ | GlobalDataFlow.cs:83:15:83:20 | access to local variable sink14 : IEnumerable | GlobalDataFlow.cs:90:75:90:88 | call to method First | | GlobalDataFlow.cs:83:15:83:20 | access to local variable sink14 : IEnumerable | GlobalDataFlow.cs:90:75:90:88 | call to method First : String | | GlobalDataFlow.cs:83:15:83:20 | access to local variable sink14 : IEnumerable | GlobalDataFlow.cs:116:21:116:72 | (...) ... | -| GlobalDataFlow.cs:83:15:83:20 | access to local variable sink14 : IEnumerable | GlobalDataFlow.cs:116:21:116:72 | (...) ... : IEnumerable | +| GlobalDataFlow.cs:83:15:83:20 | access to local variable sink14 : IEnumerable | GlobalDataFlow.cs:116:21:116:72 | (...) ... : String[] | | GlobalDataFlow.cs:83:15:83:20 | access to local variable sink14 : IEnumerable | GlobalDataFlow.cs:116:42:116:72 | array creation of type String[] | | GlobalDataFlow.cs:83:15:83:20 | access to local variable sink14 : IEnumerable | GlobalDataFlow.cs:116:42:116:72 | array creation of type String[] : String[] | | GlobalDataFlow.cs:83:15:83:20 | access to local variable sink14 : IEnumerable | GlobalDataFlow.cs:116:57:116:62 | access to local variable sink14 | @@ -1713,7 +1713,7 @@ | GlobalDataFlow.cs:84:13:84:136 | SSA def(sink15) : IEnumerable | GlobalDataFlow.cs:85:15:85:20 | access to local variable sink15 | | GlobalDataFlow.cs:84:13:84:136 | SSA def(sink15) : IEnumerable | GlobalDataFlow.cs:85:15:85:20 | access to local variable sink15 : IEnumerable | | GlobalDataFlow.cs:84:13:84:136 | SSA def(sink15) : IEnumerable | GlobalDataFlow.cs:86:70:86:121 | (...) ... | -| GlobalDataFlow.cs:84:13:84:136 | SSA def(sink15) : IEnumerable | GlobalDataFlow.cs:86:70:86:121 | (...) ... : IEnumerable | +| GlobalDataFlow.cs:84:13:84:136 | SSA def(sink15) : IEnumerable | GlobalDataFlow.cs:86:70:86:121 | (...) ... : String[] | | GlobalDataFlow.cs:84:13:84:136 | SSA def(sink15) : IEnumerable | GlobalDataFlow.cs:86:91:86:121 | array creation of type String[] | | GlobalDataFlow.cs:84:13:84:136 | SSA def(sink15) : IEnumerable | GlobalDataFlow.cs:86:91:86:121 | array creation of type String[] : String[] | | GlobalDataFlow.cs:84:13:84:136 | SSA def(sink15) : IEnumerable | GlobalDataFlow.cs:86:106:86:111 | access to local variable sink15 | @@ -1721,7 +1721,7 @@ | GlobalDataFlow.cs:84:13:84:136 | SSA def(sink15) : IEnumerable | GlobalDataFlow.cs:86:106:86:119 | call to method First | | GlobalDataFlow.cs:84:13:84:136 | SSA def(sink15) : IEnumerable | GlobalDataFlow.cs:86:106:86:119 | call to method First : String | | GlobalDataFlow.cs:84:13:84:136 | SSA def(sink15) : IEnumerable | GlobalDataFlow.cs:118:68:118:119 | (...) ... | -| GlobalDataFlow.cs:84:13:84:136 | SSA def(sink15) : IEnumerable | GlobalDataFlow.cs:118:68:118:119 | (...) ... : IEnumerable | +| GlobalDataFlow.cs:84:13:84:136 | SSA def(sink15) : IEnumerable | GlobalDataFlow.cs:118:68:118:119 | (...) ... : String[] | | GlobalDataFlow.cs:84:13:84:136 | SSA def(sink15) : IEnumerable | GlobalDataFlow.cs:118:89:118:119 | array creation of type String[] | | GlobalDataFlow.cs:84:13:84:136 | SSA def(sink15) : IEnumerable | GlobalDataFlow.cs:118:89:118:119 | array creation of type String[] : String[] | | GlobalDataFlow.cs:84:13:84:136 | SSA def(sink15) : IEnumerable | GlobalDataFlow.cs:118:104:118:109 | access to local variable sink15 | @@ -1733,7 +1733,7 @@ | GlobalDataFlow.cs:84:22:84:136 | call to method Zip : IEnumerable | GlobalDataFlow.cs:85:15:85:20 | access to local variable sink15 | | GlobalDataFlow.cs:84:22:84:136 | call to method Zip : IEnumerable | GlobalDataFlow.cs:85:15:85:20 | access to local variable sink15 : IEnumerable | | GlobalDataFlow.cs:84:22:84:136 | call to method Zip : IEnumerable | GlobalDataFlow.cs:86:70:86:121 | (...) ... | -| GlobalDataFlow.cs:84:22:84:136 | call to method Zip : IEnumerable | GlobalDataFlow.cs:86:70:86:121 | (...) ... : IEnumerable | +| GlobalDataFlow.cs:84:22:84:136 | call to method Zip : IEnumerable | GlobalDataFlow.cs:86:70:86:121 | (...) ... : String[] | | GlobalDataFlow.cs:84:22:84:136 | call to method Zip : IEnumerable | GlobalDataFlow.cs:86:91:86:121 | array creation of type String[] | | GlobalDataFlow.cs:84:22:84:136 | call to method Zip : IEnumerable | GlobalDataFlow.cs:86:91:86:121 | array creation of type String[] : String[] | | GlobalDataFlow.cs:84:22:84:136 | call to method Zip : IEnumerable | GlobalDataFlow.cs:86:106:86:111 | access to local variable sink15 | @@ -1741,7 +1741,7 @@ | GlobalDataFlow.cs:84:22:84:136 | call to method Zip : IEnumerable | GlobalDataFlow.cs:86:106:86:119 | call to method First | | GlobalDataFlow.cs:84:22:84:136 | call to method Zip : IEnumerable | GlobalDataFlow.cs:86:106:86:119 | call to method First : String | | GlobalDataFlow.cs:84:22:84:136 | call to method Zip : IEnumerable | GlobalDataFlow.cs:118:68:118:119 | (...) ... | -| GlobalDataFlow.cs:84:22:84:136 | call to method Zip : IEnumerable | GlobalDataFlow.cs:118:68:118:119 | (...) ... : IEnumerable | +| GlobalDataFlow.cs:84:22:84:136 | call to method Zip : IEnumerable | GlobalDataFlow.cs:118:68:118:119 | (...) ... : String[] | | GlobalDataFlow.cs:84:22:84:136 | call to method Zip : IEnumerable | GlobalDataFlow.cs:118:89:118:119 | array creation of type String[] | | GlobalDataFlow.cs:84:22:84:136 | call to method Zip : IEnumerable | GlobalDataFlow.cs:118:89:118:119 | array creation of type String[] : String[] | | GlobalDataFlow.cs:84:22:84:136 | call to method Zip : IEnumerable | GlobalDataFlow.cs:118:104:118:109 | access to local variable sink15 | @@ -1759,7 +1759,7 @@ | GlobalDataFlow.cs:84:44:84:74 | array creation of type String[] : String[] | GlobalDataFlow.cs:84:23:84:74 | (...) ... | | GlobalDataFlow.cs:84:44:84:74 | array creation of type String[] : String[] | GlobalDataFlow.cs:84:23:84:74 | (...) ... : String[] | | GlobalDataFlow.cs:84:59:84:64 | access to local variable sink14 : IEnumerable | GlobalDataFlow.cs:84:23:84:74 | (...) ... | -| GlobalDataFlow.cs:84:59:84:64 | access to local variable sink14 : IEnumerable | GlobalDataFlow.cs:84:23:84:74 | (...) ... : IEnumerable | +| GlobalDataFlow.cs:84:59:84:64 | access to local variable sink14 : IEnumerable | GlobalDataFlow.cs:84:23:84:74 | (...) ... : String[] | | GlobalDataFlow.cs:84:59:84:64 | access to local variable sink14 : IEnumerable | GlobalDataFlow.cs:84:44:84:74 | array creation of type String[] | | GlobalDataFlow.cs:84:59:84:64 | access to local variable sink14 : IEnumerable | GlobalDataFlow.cs:84:44:84:74 | array creation of type String[] : String[] | | GlobalDataFlow.cs:84:59:84:64 | access to local variable sink14 : IEnumerable | GlobalDataFlow.cs:84:59:84:72 | call to method First | @@ -1771,7 +1771,7 @@ | GlobalDataFlow.cs:84:59:84:64 | access to local variable sink14 : IEnumerable | GlobalDataFlow.cs:90:75:90:88 | call to method First | | GlobalDataFlow.cs:84:59:84:64 | access to local variable sink14 : IEnumerable | GlobalDataFlow.cs:90:75:90:88 | call to method First : String | | GlobalDataFlow.cs:84:59:84:64 | access to local variable sink14 : IEnumerable | GlobalDataFlow.cs:116:21:116:72 | (...) ... | -| GlobalDataFlow.cs:84:59:84:64 | access to local variable sink14 : IEnumerable | GlobalDataFlow.cs:116:21:116:72 | (...) ... : IEnumerable | +| GlobalDataFlow.cs:84:59:84:64 | access to local variable sink14 : IEnumerable | GlobalDataFlow.cs:116:21:116:72 | (...) ... : String[] | | GlobalDataFlow.cs:84:59:84:64 | access to local variable sink14 : IEnumerable | GlobalDataFlow.cs:116:42:116:72 | array creation of type String[] | | GlobalDataFlow.cs:84:59:84:64 | access to local variable sink14 : IEnumerable | GlobalDataFlow.cs:116:42:116:72 | array creation of type String[] : String[] | | GlobalDataFlow.cs:84:59:84:64 | access to local variable sink14 : IEnumerable | GlobalDataFlow.cs:116:57:116:62 | access to local variable sink14 | @@ -1783,13 +1783,13 @@ | GlobalDataFlow.cs:84:59:84:64 | access to local variable sink14 : IEnumerable | GlobalDataFlow.cs:122:20:122:25 | access to local variable sink14 | | GlobalDataFlow.cs:84:59:84:64 | access to local variable sink14 : IEnumerable | GlobalDataFlow.cs:122:20:122:25 | access to local variable sink14 : IEnumerable | | GlobalDataFlow.cs:84:59:84:72 | call to method First : String | GlobalDataFlow.cs:84:23:84:74 | (...) ... | -| GlobalDataFlow.cs:84:59:84:72 | call to method First : String | GlobalDataFlow.cs:84:23:84:74 | (...) ... : IEnumerable | +| GlobalDataFlow.cs:84:59:84:72 | call to method First : String | GlobalDataFlow.cs:84:23:84:74 | (...) ... : String[] | | GlobalDataFlow.cs:84:59:84:72 | call to method First : String | GlobalDataFlow.cs:84:44:84:74 | array creation of type String[] | | GlobalDataFlow.cs:84:59:84:72 | call to method First : String | GlobalDataFlow.cs:84:44:84:74 | array creation of type String[] : String[] | | GlobalDataFlow.cs:84:103:84:121 | array creation of type String[] : String[] | GlobalDataFlow.cs:84:82:84:121 | (...) ... | | GlobalDataFlow.cs:84:103:84:121 | array creation of type String[] : String[] | GlobalDataFlow.cs:84:82:84:121 | (...) ... : String[] | | GlobalDataFlow.cs:84:118:84:119 | "" : String | GlobalDataFlow.cs:84:82:84:121 | (...) ... | -| GlobalDataFlow.cs:84:118:84:119 | "" : String | GlobalDataFlow.cs:84:82:84:121 | (...) ... : IEnumerable | +| GlobalDataFlow.cs:84:118:84:119 | "" : String | GlobalDataFlow.cs:84:82:84:121 | (...) ... : String[] | | GlobalDataFlow.cs:84:118:84:119 | "" : String | GlobalDataFlow.cs:84:103:84:121 | array creation of type String[] | | GlobalDataFlow.cs:84:118:84:119 | "" : String | GlobalDataFlow.cs:84:103:84:121 | array creation of type String[] : String[] | | GlobalDataFlow.cs:84:125:84:135 | [output] (...) => ... : String | GlobalDataFlow.cs:84:13:84:136 | SSA def(sink15) | @@ -1799,7 +1799,7 @@ | GlobalDataFlow.cs:84:125:84:135 | [output] (...) => ... : String | GlobalDataFlow.cs:85:15:85:20 | access to local variable sink15 | | GlobalDataFlow.cs:84:125:84:135 | [output] (...) => ... : String | GlobalDataFlow.cs:85:15:85:20 | access to local variable sink15 : IEnumerable | | GlobalDataFlow.cs:84:125:84:135 | [output] (...) => ... : String | GlobalDataFlow.cs:86:70:86:121 | (...) ... | -| GlobalDataFlow.cs:84:125:84:135 | [output] (...) => ... : String | GlobalDataFlow.cs:86:70:86:121 | (...) ... : IEnumerable | +| GlobalDataFlow.cs:84:125:84:135 | [output] (...) => ... : String | GlobalDataFlow.cs:86:70:86:121 | (...) ... : String[] | | GlobalDataFlow.cs:84:125:84:135 | [output] (...) => ... : String | GlobalDataFlow.cs:86:91:86:121 | array creation of type String[] | | GlobalDataFlow.cs:84:125:84:135 | [output] (...) => ... : String | GlobalDataFlow.cs:86:91:86:121 | array creation of type String[] : String[] | | GlobalDataFlow.cs:84:125:84:135 | [output] (...) => ... : String | GlobalDataFlow.cs:86:106:86:111 | access to local variable sink15 | @@ -1807,7 +1807,7 @@ | GlobalDataFlow.cs:84:125:84:135 | [output] (...) => ... : String | GlobalDataFlow.cs:86:106:86:119 | call to method First | | GlobalDataFlow.cs:84:125:84:135 | [output] (...) => ... : String | GlobalDataFlow.cs:86:106:86:119 | call to method First : String | | GlobalDataFlow.cs:84:125:84:135 | [output] (...) => ... : String | GlobalDataFlow.cs:118:68:118:119 | (...) ... | -| GlobalDataFlow.cs:84:125:84:135 | [output] (...) => ... : String | GlobalDataFlow.cs:118:68:118:119 | (...) ... : IEnumerable | +| GlobalDataFlow.cs:84:125:84:135 | [output] (...) => ... : String | GlobalDataFlow.cs:118:68:118:119 | (...) ... : String[] | | GlobalDataFlow.cs:84:125:84:135 | [output] (...) => ... : String | GlobalDataFlow.cs:118:89:118:119 | array creation of type String[] | | GlobalDataFlow.cs:84:125:84:135 | [output] (...) => ... : String | GlobalDataFlow.cs:118:89:118:119 | array creation of type String[] : String[] | | GlobalDataFlow.cs:84:125:84:135 | [output] (...) => ... : String | GlobalDataFlow.cs:118:104:118:109 | access to local variable sink15 | @@ -1835,7 +1835,7 @@ | GlobalDataFlow.cs:84:135:84:135 | access to parameter x : String | GlobalDataFlow.cs:84:125:84:135 | [output] (...) => ... | | GlobalDataFlow.cs:84:135:84:135 | access to parameter x : String | GlobalDataFlow.cs:84:125:84:135 | [output] (...) => ... : String | | GlobalDataFlow.cs:85:15:85:20 | access to local variable sink15 : IEnumerable | GlobalDataFlow.cs:86:70:86:121 | (...) ... | -| GlobalDataFlow.cs:85:15:85:20 | access to local variable sink15 : IEnumerable | GlobalDataFlow.cs:86:70:86:121 | (...) ... : IEnumerable | +| GlobalDataFlow.cs:85:15:85:20 | access to local variable sink15 : IEnumerable | GlobalDataFlow.cs:86:70:86:121 | (...) ... : String[] | | GlobalDataFlow.cs:85:15:85:20 | access to local variable sink15 : IEnumerable | GlobalDataFlow.cs:86:91:86:121 | array creation of type String[] | | GlobalDataFlow.cs:85:15:85:20 | access to local variable sink15 : IEnumerable | GlobalDataFlow.cs:86:91:86:121 | array creation of type String[] : String[] | | GlobalDataFlow.cs:85:15:85:20 | access to local variable sink15 : IEnumerable | GlobalDataFlow.cs:86:106:86:111 | access to local variable sink15 | @@ -1843,7 +1843,7 @@ | GlobalDataFlow.cs:85:15:85:20 | access to local variable sink15 : IEnumerable | GlobalDataFlow.cs:86:106:86:119 | call to method First | | GlobalDataFlow.cs:85:15:85:20 | access to local variable sink15 : IEnumerable | GlobalDataFlow.cs:86:106:86:119 | call to method First : String | | GlobalDataFlow.cs:85:15:85:20 | access to local variable sink15 : IEnumerable | GlobalDataFlow.cs:118:68:118:119 | (...) ... | -| GlobalDataFlow.cs:85:15:85:20 | access to local variable sink15 : IEnumerable | GlobalDataFlow.cs:118:68:118:119 | (...) ... : IEnumerable | +| GlobalDataFlow.cs:85:15:85:20 | access to local variable sink15 : IEnumerable | GlobalDataFlow.cs:118:68:118:119 | (...) ... : String[] | | GlobalDataFlow.cs:85:15:85:20 | access to local variable sink15 : IEnumerable | GlobalDataFlow.cs:118:89:118:119 | array creation of type String[] | | GlobalDataFlow.cs:85:15:85:20 | access to local variable sink15 : IEnumerable | GlobalDataFlow.cs:118:89:118:119 | array creation of type String[] : String[] | | GlobalDataFlow.cs:85:15:85:20 | access to local variable sink15 : IEnumerable | GlobalDataFlow.cs:118:104:118:109 | access to local variable sink15 | @@ -1859,7 +1859,7 @@ | GlobalDataFlow.cs:86:44:86:62 | array creation of type String[] : String[] | GlobalDataFlow.cs:86:23:86:62 | (...) ... | | GlobalDataFlow.cs:86:44:86:62 | array creation of type String[] : String[] | GlobalDataFlow.cs:86:23:86:62 | (...) ... : String[] | | GlobalDataFlow.cs:86:59:86:60 | "" : String | GlobalDataFlow.cs:86:23:86:62 | (...) ... | -| GlobalDataFlow.cs:86:59:86:60 | "" : String | GlobalDataFlow.cs:86:23:86:62 | (...) ... : IEnumerable | +| GlobalDataFlow.cs:86:59:86:60 | "" : String | GlobalDataFlow.cs:86:23:86:62 | (...) ... : String[] | | GlobalDataFlow.cs:86:59:86:60 | "" : String | GlobalDataFlow.cs:86:44:86:62 | array creation of type String[] | | GlobalDataFlow.cs:86:59:86:60 | "" : String | GlobalDataFlow.cs:86:44:86:62 | array creation of type String[] : String[] | | GlobalDataFlow.cs:86:70:86:121 | (...) ... : IEnumerable | GlobalDataFlow.cs:86:125:86:135 | [output] (...) => ... | @@ -1873,13 +1873,13 @@ | GlobalDataFlow.cs:86:91:86:121 | array creation of type String[] : String[] | GlobalDataFlow.cs:86:70:86:121 | (...) ... | | GlobalDataFlow.cs:86:91:86:121 | array creation of type String[] : String[] | GlobalDataFlow.cs:86:70:86:121 | (...) ... : String[] | | GlobalDataFlow.cs:86:106:86:111 | access to local variable sink15 : IEnumerable | GlobalDataFlow.cs:86:70:86:121 | (...) ... | -| GlobalDataFlow.cs:86:106:86:111 | access to local variable sink15 : IEnumerable | GlobalDataFlow.cs:86:70:86:121 | (...) ... : IEnumerable | +| GlobalDataFlow.cs:86:106:86:111 | access to local variable sink15 : IEnumerable | GlobalDataFlow.cs:86:70:86:121 | (...) ... : String[] | | GlobalDataFlow.cs:86:106:86:111 | access to local variable sink15 : IEnumerable | GlobalDataFlow.cs:86:91:86:121 | array creation of type String[] | | GlobalDataFlow.cs:86:106:86:111 | access to local variable sink15 : IEnumerable | GlobalDataFlow.cs:86:91:86:121 | array creation of type String[] : String[] | | GlobalDataFlow.cs:86:106:86:111 | access to local variable sink15 : IEnumerable | GlobalDataFlow.cs:86:106:86:119 | call to method First | | GlobalDataFlow.cs:86:106:86:111 | access to local variable sink15 : IEnumerable | GlobalDataFlow.cs:86:106:86:119 | call to method First : String | | GlobalDataFlow.cs:86:106:86:111 | access to local variable sink15 : IEnumerable | GlobalDataFlow.cs:118:68:118:119 | (...) ... | -| GlobalDataFlow.cs:86:106:86:111 | access to local variable sink15 : IEnumerable | GlobalDataFlow.cs:118:68:118:119 | (...) ... : IEnumerable | +| GlobalDataFlow.cs:86:106:86:111 | access to local variable sink15 : IEnumerable | GlobalDataFlow.cs:118:68:118:119 | (...) ... : String[] | | GlobalDataFlow.cs:86:106:86:111 | access to local variable sink15 : IEnumerable | GlobalDataFlow.cs:118:89:118:119 | array creation of type String[] | | GlobalDataFlow.cs:86:106:86:111 | access to local variable sink15 : IEnumerable | GlobalDataFlow.cs:118:89:118:119 | array creation of type String[] : String[] | | GlobalDataFlow.cs:86:106:86:111 | access to local variable sink15 : IEnumerable | GlobalDataFlow.cs:118:104:118:109 | access to local variable sink15 | @@ -1887,7 +1887,7 @@ | GlobalDataFlow.cs:86:106:86:111 | access to local variable sink15 : IEnumerable | GlobalDataFlow.cs:118:104:118:117 | call to method First | | GlobalDataFlow.cs:86:106:86:111 | access to local variable sink15 : IEnumerable | GlobalDataFlow.cs:118:104:118:117 | call to method First : String | | GlobalDataFlow.cs:86:106:86:119 | call to method First : String | GlobalDataFlow.cs:86:70:86:121 | (...) ... | -| GlobalDataFlow.cs:86:106:86:119 | call to method First : String | GlobalDataFlow.cs:86:70:86:121 | (...) ... : IEnumerable | +| GlobalDataFlow.cs:86:106:86:119 | call to method First : String | GlobalDataFlow.cs:86:70:86:121 | (...) ... : String[] | | GlobalDataFlow.cs:86:106:86:119 | call to method First : String | GlobalDataFlow.cs:86:91:86:121 | array creation of type String[] | | GlobalDataFlow.cs:86:106:86:119 | call to method First : String | GlobalDataFlow.cs:86:91:86:121 | array creation of type String[] : String[] | | GlobalDataFlow.cs:86:125:86:135 | [output] (...) => ... : String | GlobalDataFlow.cs:86:13:86:136 | SSA def(sink16) | @@ -1927,7 +1927,7 @@ | GlobalDataFlow.cs:88:22:88:27 | access to local variable sink14 : IEnumerable | GlobalDataFlow.cs:90:75:90:88 | call to method First | | GlobalDataFlow.cs:88:22:88:27 | access to local variable sink14 : IEnumerable | GlobalDataFlow.cs:90:75:90:88 | call to method First : String | | GlobalDataFlow.cs:88:22:88:27 | access to local variable sink14 : IEnumerable | GlobalDataFlow.cs:116:21:116:72 | (...) ... | -| GlobalDataFlow.cs:88:22:88:27 | access to local variable sink14 : IEnumerable | GlobalDataFlow.cs:116:21:116:72 | (...) ... : IEnumerable | +| GlobalDataFlow.cs:88:22:88:27 | access to local variable sink14 : IEnumerable | GlobalDataFlow.cs:116:21:116:72 | (...) ... : String[] | | GlobalDataFlow.cs:88:22:88:27 | access to local variable sink14 : IEnumerable | GlobalDataFlow.cs:116:42:116:72 | array creation of type String[] | | GlobalDataFlow.cs:88:22:88:27 | access to local variable sink14 : IEnumerable | GlobalDataFlow.cs:116:42:116:72 | array creation of type String[] : String[] | | GlobalDataFlow.cs:88:22:88:27 | access to local variable sink14 : IEnumerable | GlobalDataFlow.cs:116:57:116:62 | access to local variable sink14 | @@ -2069,13 +2069,13 @@ | GlobalDataFlow.cs:90:44:90:62 | array creation of type String[] : String[] | GlobalDataFlow.cs:90:23:90:62 | (...) ... | | GlobalDataFlow.cs:90:44:90:62 | array creation of type String[] : String[] | GlobalDataFlow.cs:90:23:90:62 | (...) ... : String[] | | GlobalDataFlow.cs:90:59:90:60 | "" : String | GlobalDataFlow.cs:90:23:90:62 | (...) ... | -| GlobalDataFlow.cs:90:59:90:60 | "" : String | GlobalDataFlow.cs:90:23:90:62 | (...) ... : IEnumerable | +| GlobalDataFlow.cs:90:59:90:60 | "" : String | GlobalDataFlow.cs:90:23:90:62 | (...) ... : String[] | | GlobalDataFlow.cs:90:59:90:60 | "" : String | GlobalDataFlow.cs:90:44:90:62 | array creation of type String[] | | GlobalDataFlow.cs:90:59:90:60 | "" : String | GlobalDataFlow.cs:90:44:90:62 | array creation of type String[] : String[] | | GlobalDataFlow.cs:90:75:90:80 | access to local variable sink14 : IEnumerable | GlobalDataFlow.cs:90:75:90:88 | call to method First | | GlobalDataFlow.cs:90:75:90:80 | access to local variable sink14 : IEnumerable | GlobalDataFlow.cs:90:75:90:88 | call to method First : String | | GlobalDataFlow.cs:90:75:90:80 | access to local variable sink14 : IEnumerable | GlobalDataFlow.cs:116:21:116:72 | (...) ... | -| GlobalDataFlow.cs:90:75:90:80 | access to local variable sink14 : IEnumerable | GlobalDataFlow.cs:116:21:116:72 | (...) ... : IEnumerable | +| GlobalDataFlow.cs:90:75:90:80 | access to local variable sink14 : IEnumerable | GlobalDataFlow.cs:116:21:116:72 | (...) ... : String[] | | GlobalDataFlow.cs:90:75:90:80 | access to local variable sink14 : IEnumerable | GlobalDataFlow.cs:116:42:116:72 | array creation of type String[] | | GlobalDataFlow.cs:90:75:90:80 | access to local variable sink14 : IEnumerable | GlobalDataFlow.cs:116:42:116:72 | array creation of type String[] : String[] | | GlobalDataFlow.cs:90:75:90:80 | access to local variable sink14 : IEnumerable | GlobalDataFlow.cs:116:57:116:62 | access to local variable sink14 | @@ -2365,13 +2365,13 @@ | GlobalDataFlow.cs:108:27:108:34 | SSA def(nonSink0) : String | GlobalDataFlow.cs:111:15:111:22 | access to local variable nonSink0 | | GlobalDataFlow.cs:108:27:108:34 | SSA def(nonSink0) : String | GlobalDataFlow.cs:111:15:111:22 | access to local variable nonSink0 : String | | GlobalDataFlow.cs:108:27:108:34 | SSA def(nonSink0) : String | GlobalDataFlow.cs:112:25:112:70 | (...) ... | -| GlobalDataFlow.cs:108:27:108:34 | SSA def(nonSink0) : String | GlobalDataFlow.cs:112:25:112:70 | (...) ... : IEnumerable | +| GlobalDataFlow.cs:108:27:108:34 | SSA def(nonSink0) : String | GlobalDataFlow.cs:112:25:112:70 | (...) ... : String[] | | GlobalDataFlow.cs:108:27:108:34 | SSA def(nonSink0) : String | GlobalDataFlow.cs:112:46:112:70 | array creation of type String[] | | GlobalDataFlow.cs:108:27:108:34 | SSA def(nonSink0) : String | GlobalDataFlow.cs:112:46:112:70 | array creation of type String[] : String[] | | GlobalDataFlow.cs:108:27:108:34 | SSA def(nonSink0) : String | GlobalDataFlow.cs:112:61:112:68 | access to local variable nonSink0 | | GlobalDataFlow.cs:108:27:108:34 | SSA def(nonSink0) : String | GlobalDataFlow.cs:112:61:112:68 | access to local variable nonSink0 : String | | GlobalDataFlow.cs:108:27:108:34 | SSA def(nonSink0) : String | GlobalDataFlow.cs:114:21:114:66 | (...) ... | -| GlobalDataFlow.cs:108:27:108:34 | SSA def(nonSink0) : String | GlobalDataFlow.cs:114:21:114:66 | (...) ... : IEnumerable | +| GlobalDataFlow.cs:108:27:108:34 | SSA def(nonSink0) : String | GlobalDataFlow.cs:114:21:114:66 | (...) ... : String[] | | GlobalDataFlow.cs:108:27:108:34 | SSA def(nonSink0) : String | GlobalDataFlow.cs:114:42:114:66 | array creation of type String[] | | GlobalDataFlow.cs:108:27:108:34 | SSA def(nonSink0) : String | GlobalDataFlow.cs:114:42:114:66 | array creation of type String[] : String[] | | GlobalDataFlow.cs:108:27:108:34 | SSA def(nonSink0) : String | GlobalDataFlow.cs:114:57:114:64 | access to local variable nonSink0 | @@ -2383,13 +2383,13 @@ | GlobalDataFlow.cs:108:27:108:34 | SSA def(nonSink0) : T | GlobalDataFlow.cs:111:15:111:22 | access to local variable nonSink0 | | GlobalDataFlow.cs:108:27:108:34 | SSA def(nonSink0) : T | GlobalDataFlow.cs:111:15:111:22 | access to local variable nonSink0 : T | | GlobalDataFlow.cs:108:27:108:34 | SSA def(nonSink0) : T | GlobalDataFlow.cs:112:25:112:70 | (...) ... | -| GlobalDataFlow.cs:108:27:108:34 | SSA def(nonSink0) : T | GlobalDataFlow.cs:112:25:112:70 | (...) ... : IEnumerable | +| GlobalDataFlow.cs:108:27:108:34 | SSA def(nonSink0) : T | GlobalDataFlow.cs:112:25:112:70 | (...) ... : String[] | | GlobalDataFlow.cs:108:27:108:34 | SSA def(nonSink0) : T | GlobalDataFlow.cs:112:46:112:70 | array creation of type String[] | | GlobalDataFlow.cs:108:27:108:34 | SSA def(nonSink0) : T | GlobalDataFlow.cs:112:46:112:70 | array creation of type String[] : String[] | | GlobalDataFlow.cs:108:27:108:34 | SSA def(nonSink0) : T | GlobalDataFlow.cs:112:61:112:68 | access to local variable nonSink0 | | GlobalDataFlow.cs:108:27:108:34 | SSA def(nonSink0) : T | GlobalDataFlow.cs:112:61:112:68 | access to local variable nonSink0 : T | | GlobalDataFlow.cs:108:27:108:34 | SSA def(nonSink0) : T | GlobalDataFlow.cs:114:21:114:66 | (...) ... | -| GlobalDataFlow.cs:108:27:108:34 | SSA def(nonSink0) : T | GlobalDataFlow.cs:114:21:114:66 | (...) ... : IEnumerable | +| GlobalDataFlow.cs:108:27:108:34 | SSA def(nonSink0) : T | GlobalDataFlow.cs:114:21:114:66 | (...) ... : String[] | | GlobalDataFlow.cs:108:27:108:34 | SSA def(nonSink0) : T | GlobalDataFlow.cs:114:42:114:66 | array creation of type String[] | | GlobalDataFlow.cs:108:27:108:34 | SSA def(nonSink0) : T | GlobalDataFlow.cs:114:42:114:66 | array creation of type String[] : String[] | | GlobalDataFlow.cs:108:27:108:34 | SSA def(nonSink0) : T | GlobalDataFlow.cs:114:57:114:64 | access to local variable nonSink0 | @@ -2407,13 +2407,13 @@ | GlobalDataFlow.cs:109:15:109:22 | access to local variable nonSink0 : String | GlobalDataFlow.cs:111:15:111:22 | access to local variable nonSink0 | | GlobalDataFlow.cs:109:15:109:22 | access to local variable nonSink0 : String | GlobalDataFlow.cs:111:15:111:22 | access to local variable nonSink0 : String | | GlobalDataFlow.cs:109:15:109:22 | access to local variable nonSink0 : String | GlobalDataFlow.cs:112:25:112:70 | (...) ... | -| GlobalDataFlow.cs:109:15:109:22 | access to local variable nonSink0 : String | GlobalDataFlow.cs:112:25:112:70 | (...) ... : IEnumerable | +| GlobalDataFlow.cs:109:15:109:22 | access to local variable nonSink0 : String | GlobalDataFlow.cs:112:25:112:70 | (...) ... : String[] | | GlobalDataFlow.cs:109:15:109:22 | access to local variable nonSink0 : String | GlobalDataFlow.cs:112:46:112:70 | array creation of type String[] | | GlobalDataFlow.cs:109:15:109:22 | access to local variable nonSink0 : String | GlobalDataFlow.cs:112:46:112:70 | array creation of type String[] : String[] | | GlobalDataFlow.cs:109:15:109:22 | access to local variable nonSink0 : String | GlobalDataFlow.cs:112:61:112:68 | access to local variable nonSink0 | | GlobalDataFlow.cs:109:15:109:22 | access to local variable nonSink0 : String | GlobalDataFlow.cs:112:61:112:68 | access to local variable nonSink0 : String | | GlobalDataFlow.cs:109:15:109:22 | access to local variable nonSink0 : String | GlobalDataFlow.cs:114:21:114:66 | (...) ... | -| GlobalDataFlow.cs:109:15:109:22 | access to local variable nonSink0 : String | GlobalDataFlow.cs:114:21:114:66 | (...) ... : IEnumerable | +| GlobalDataFlow.cs:109:15:109:22 | access to local variable nonSink0 : String | GlobalDataFlow.cs:114:21:114:66 | (...) ... : String[] | | GlobalDataFlow.cs:109:15:109:22 | access to local variable nonSink0 : String | GlobalDataFlow.cs:114:42:114:66 | array creation of type String[] | | GlobalDataFlow.cs:109:15:109:22 | access to local variable nonSink0 : String | GlobalDataFlow.cs:114:42:114:66 | array creation of type String[] : String[] | | GlobalDataFlow.cs:109:15:109:22 | access to local variable nonSink0 : String | GlobalDataFlow.cs:114:57:114:64 | access to local variable nonSink0 | @@ -2423,13 +2423,13 @@ | GlobalDataFlow.cs:109:15:109:22 | access to local variable nonSink0 : T | GlobalDataFlow.cs:111:15:111:22 | access to local variable nonSink0 | | GlobalDataFlow.cs:109:15:109:22 | access to local variable nonSink0 : T | GlobalDataFlow.cs:111:15:111:22 | access to local variable nonSink0 : T | | GlobalDataFlow.cs:109:15:109:22 | access to local variable nonSink0 : T | GlobalDataFlow.cs:112:25:112:70 | (...) ... | -| GlobalDataFlow.cs:109:15:109:22 | access to local variable nonSink0 : T | GlobalDataFlow.cs:112:25:112:70 | (...) ... : IEnumerable | +| GlobalDataFlow.cs:109:15:109:22 | access to local variable nonSink0 : T | GlobalDataFlow.cs:112:25:112:70 | (...) ... : String[] | | GlobalDataFlow.cs:109:15:109:22 | access to local variable nonSink0 : T | GlobalDataFlow.cs:112:46:112:70 | array creation of type String[] | | GlobalDataFlow.cs:109:15:109:22 | access to local variable nonSink0 : T | GlobalDataFlow.cs:112:46:112:70 | array creation of type String[] : String[] | | GlobalDataFlow.cs:109:15:109:22 | access to local variable nonSink0 : T | GlobalDataFlow.cs:112:61:112:68 | access to local variable nonSink0 | | GlobalDataFlow.cs:109:15:109:22 | access to local variable nonSink0 : T | GlobalDataFlow.cs:112:61:112:68 | access to local variable nonSink0 : T | | GlobalDataFlow.cs:109:15:109:22 | access to local variable nonSink0 : T | GlobalDataFlow.cs:114:21:114:66 | (...) ... | -| GlobalDataFlow.cs:109:15:109:22 | access to local variable nonSink0 : T | GlobalDataFlow.cs:114:21:114:66 | (...) ... : IEnumerable | +| GlobalDataFlow.cs:109:15:109:22 | access to local variable nonSink0 : T | GlobalDataFlow.cs:114:21:114:66 | (...) ... : String[] | | GlobalDataFlow.cs:109:15:109:22 | access to local variable nonSink0 : T | GlobalDataFlow.cs:114:42:114:66 | array creation of type String[] | | GlobalDataFlow.cs:109:15:109:22 | access to local variable nonSink0 : T | GlobalDataFlow.cs:114:42:114:66 | array creation of type String[] : String[] | | GlobalDataFlow.cs:109:15:109:22 | access to local variable nonSink0 : T | GlobalDataFlow.cs:114:57:114:64 | access to local variable nonSink0 | @@ -2461,13 +2461,13 @@ | GlobalDataFlow.cs:110:41:110:48 | access to local variable nonSink0 : String | GlobalDataFlow.cs:111:15:111:22 | access to local variable nonSink0 | | GlobalDataFlow.cs:110:41:110:48 | access to local variable nonSink0 : String | GlobalDataFlow.cs:111:15:111:22 | access to local variable nonSink0 : String | | GlobalDataFlow.cs:110:41:110:48 | access to local variable nonSink0 : String | GlobalDataFlow.cs:112:25:112:70 | (...) ... | -| GlobalDataFlow.cs:110:41:110:48 | access to local variable nonSink0 : String | GlobalDataFlow.cs:112:25:112:70 | (...) ... : IEnumerable | +| GlobalDataFlow.cs:110:41:110:48 | access to local variable nonSink0 : String | GlobalDataFlow.cs:112:25:112:70 | (...) ... : String[] | | GlobalDataFlow.cs:110:41:110:48 | access to local variable nonSink0 : String | GlobalDataFlow.cs:112:46:112:70 | array creation of type String[] | | GlobalDataFlow.cs:110:41:110:48 | access to local variable nonSink0 : String | GlobalDataFlow.cs:112:46:112:70 | array creation of type String[] : String[] | | GlobalDataFlow.cs:110:41:110:48 | access to local variable nonSink0 : String | GlobalDataFlow.cs:112:61:112:68 | access to local variable nonSink0 | | GlobalDataFlow.cs:110:41:110:48 | access to local variable nonSink0 : String | GlobalDataFlow.cs:112:61:112:68 | access to local variable nonSink0 : String | | GlobalDataFlow.cs:110:41:110:48 | access to local variable nonSink0 : String | GlobalDataFlow.cs:114:21:114:66 | (...) ... | -| GlobalDataFlow.cs:110:41:110:48 | access to local variable nonSink0 : String | GlobalDataFlow.cs:114:21:114:66 | (...) ... : IEnumerable | +| GlobalDataFlow.cs:110:41:110:48 | access to local variable nonSink0 : String | GlobalDataFlow.cs:114:21:114:66 | (...) ... : String[] | | GlobalDataFlow.cs:110:41:110:48 | access to local variable nonSink0 : String | GlobalDataFlow.cs:114:42:114:66 | array creation of type String[] | | GlobalDataFlow.cs:110:41:110:48 | access to local variable nonSink0 : String | GlobalDataFlow.cs:114:42:114:66 | array creation of type String[] : String[] | | GlobalDataFlow.cs:110:41:110:48 | access to local variable nonSink0 : String | GlobalDataFlow.cs:114:57:114:64 | access to local variable nonSink0 | @@ -2477,13 +2477,13 @@ | GlobalDataFlow.cs:110:41:110:48 | access to local variable nonSink0 : T | GlobalDataFlow.cs:111:15:111:22 | access to local variable nonSink0 | | GlobalDataFlow.cs:110:41:110:48 | access to local variable nonSink0 : T | GlobalDataFlow.cs:111:15:111:22 | access to local variable nonSink0 : T | | GlobalDataFlow.cs:110:41:110:48 | access to local variable nonSink0 : T | GlobalDataFlow.cs:112:25:112:70 | (...) ... | -| GlobalDataFlow.cs:110:41:110:48 | access to local variable nonSink0 : T | GlobalDataFlow.cs:112:25:112:70 | (...) ... : IEnumerable | +| GlobalDataFlow.cs:110:41:110:48 | access to local variable nonSink0 : T | GlobalDataFlow.cs:112:25:112:70 | (...) ... : String[] | | GlobalDataFlow.cs:110:41:110:48 | access to local variable nonSink0 : T | GlobalDataFlow.cs:112:46:112:70 | array creation of type String[] | | GlobalDataFlow.cs:110:41:110:48 | access to local variable nonSink0 : T | GlobalDataFlow.cs:112:46:112:70 | array creation of type String[] : String[] | | GlobalDataFlow.cs:110:41:110:48 | access to local variable nonSink0 : T | GlobalDataFlow.cs:112:61:112:68 | access to local variable nonSink0 | | GlobalDataFlow.cs:110:41:110:48 | access to local variable nonSink0 : T | GlobalDataFlow.cs:112:61:112:68 | access to local variable nonSink0 : T | | GlobalDataFlow.cs:110:41:110:48 | access to local variable nonSink0 : T | GlobalDataFlow.cs:114:21:114:66 | (...) ... | -| GlobalDataFlow.cs:110:41:110:48 | access to local variable nonSink0 : T | GlobalDataFlow.cs:114:21:114:66 | (...) ... : IEnumerable | +| GlobalDataFlow.cs:110:41:110:48 | access to local variable nonSink0 : T | GlobalDataFlow.cs:114:21:114:66 | (...) ... : String[] | | GlobalDataFlow.cs:110:41:110:48 | access to local variable nonSink0 : T | GlobalDataFlow.cs:114:42:114:66 | array creation of type String[] | | GlobalDataFlow.cs:110:41:110:48 | access to local variable nonSink0 : T | GlobalDataFlow.cs:114:42:114:66 | array creation of type String[] : String[] | | GlobalDataFlow.cs:110:41:110:48 | access to local variable nonSink0 : T | GlobalDataFlow.cs:114:57:114:64 | access to local variable nonSink0 | @@ -2491,25 +2491,25 @@ | GlobalDataFlow.cs:110:41:110:48 | access to local variable nonSink0 : T | GlobalDataFlow.cs:287:50:287:50 | z | | GlobalDataFlow.cs:110:41:110:48 | access to local variable nonSink0 : T | GlobalDataFlow.cs:287:50:287:50 | z : T | | GlobalDataFlow.cs:111:15:111:22 | access to local variable nonSink0 : String | GlobalDataFlow.cs:112:25:112:70 | (...) ... | -| GlobalDataFlow.cs:111:15:111:22 | access to local variable nonSink0 : String | GlobalDataFlow.cs:112:25:112:70 | (...) ... : IEnumerable | +| GlobalDataFlow.cs:111:15:111:22 | access to local variable nonSink0 : String | GlobalDataFlow.cs:112:25:112:70 | (...) ... : String[] | | GlobalDataFlow.cs:111:15:111:22 | access to local variable nonSink0 : String | GlobalDataFlow.cs:112:46:112:70 | array creation of type String[] | | GlobalDataFlow.cs:111:15:111:22 | access to local variable nonSink0 : String | GlobalDataFlow.cs:112:46:112:70 | array creation of type String[] : String[] | | GlobalDataFlow.cs:111:15:111:22 | access to local variable nonSink0 : String | GlobalDataFlow.cs:112:61:112:68 | access to local variable nonSink0 | | GlobalDataFlow.cs:111:15:111:22 | access to local variable nonSink0 : String | GlobalDataFlow.cs:112:61:112:68 | access to local variable nonSink0 : String | | GlobalDataFlow.cs:111:15:111:22 | access to local variable nonSink0 : String | GlobalDataFlow.cs:114:21:114:66 | (...) ... | -| GlobalDataFlow.cs:111:15:111:22 | access to local variable nonSink0 : String | GlobalDataFlow.cs:114:21:114:66 | (...) ... : IEnumerable | +| GlobalDataFlow.cs:111:15:111:22 | access to local variable nonSink0 : String | GlobalDataFlow.cs:114:21:114:66 | (...) ... : String[] | | GlobalDataFlow.cs:111:15:111:22 | access to local variable nonSink0 : String | GlobalDataFlow.cs:114:42:114:66 | array creation of type String[] | | GlobalDataFlow.cs:111:15:111:22 | access to local variable nonSink0 : String | GlobalDataFlow.cs:114:42:114:66 | array creation of type String[] : String[] | | GlobalDataFlow.cs:111:15:111:22 | access to local variable nonSink0 : String | GlobalDataFlow.cs:114:57:114:64 | access to local variable nonSink0 | | GlobalDataFlow.cs:111:15:111:22 | access to local variable nonSink0 : String | GlobalDataFlow.cs:114:57:114:64 | access to local variable nonSink0 : String | | GlobalDataFlow.cs:111:15:111:22 | access to local variable nonSink0 : T | GlobalDataFlow.cs:112:25:112:70 | (...) ... | -| GlobalDataFlow.cs:111:15:111:22 | access to local variable nonSink0 : T | GlobalDataFlow.cs:112:25:112:70 | (...) ... : IEnumerable | +| GlobalDataFlow.cs:111:15:111:22 | access to local variable nonSink0 : T | GlobalDataFlow.cs:112:25:112:70 | (...) ... : String[] | | GlobalDataFlow.cs:111:15:111:22 | access to local variable nonSink0 : T | GlobalDataFlow.cs:112:46:112:70 | array creation of type String[] | | GlobalDataFlow.cs:111:15:111:22 | access to local variable nonSink0 : T | GlobalDataFlow.cs:112:46:112:70 | array creation of type String[] : String[] | | GlobalDataFlow.cs:111:15:111:22 | access to local variable nonSink0 : T | GlobalDataFlow.cs:112:61:112:68 | access to local variable nonSink0 | | GlobalDataFlow.cs:111:15:111:22 | access to local variable nonSink0 : T | GlobalDataFlow.cs:112:61:112:68 | access to local variable nonSink0 : T | | GlobalDataFlow.cs:111:15:111:22 | access to local variable nonSink0 : T | GlobalDataFlow.cs:114:21:114:66 | (...) ... | -| GlobalDataFlow.cs:111:15:111:22 | access to local variable nonSink0 : T | GlobalDataFlow.cs:114:21:114:66 | (...) ... : IEnumerable | +| GlobalDataFlow.cs:111:15:111:22 | access to local variable nonSink0 : T | GlobalDataFlow.cs:114:21:114:66 | (...) ... : String[] | | GlobalDataFlow.cs:111:15:111:22 | access to local variable nonSink0 : T | GlobalDataFlow.cs:114:42:114:66 | array creation of type String[] | | GlobalDataFlow.cs:111:15:111:22 | access to local variable nonSink0 : T | GlobalDataFlow.cs:114:42:114:66 | array creation of type String[] : String[] | | GlobalDataFlow.cs:111:15:111:22 | access to local variable nonSink0 : T | GlobalDataFlow.cs:114:57:114:64 | access to local variable nonSink0 | @@ -2537,21 +2537,21 @@ | GlobalDataFlow.cs:112:46:112:70 | array creation of type String[] : String[] | GlobalDataFlow.cs:112:25:112:70 | (...) ... | | GlobalDataFlow.cs:112:46:112:70 | array creation of type String[] : String[] | GlobalDataFlow.cs:112:25:112:70 | (...) ... : String[] | | GlobalDataFlow.cs:112:61:112:68 | access to local variable nonSink0 : String | GlobalDataFlow.cs:112:25:112:70 | (...) ... | -| GlobalDataFlow.cs:112:61:112:68 | access to local variable nonSink0 : String | GlobalDataFlow.cs:112:25:112:70 | (...) ... : IEnumerable | +| GlobalDataFlow.cs:112:61:112:68 | access to local variable nonSink0 : String | GlobalDataFlow.cs:112:25:112:70 | (...) ... : String[] | | GlobalDataFlow.cs:112:61:112:68 | access to local variable nonSink0 : String | GlobalDataFlow.cs:112:46:112:70 | array creation of type String[] | | GlobalDataFlow.cs:112:61:112:68 | access to local variable nonSink0 : String | GlobalDataFlow.cs:112:46:112:70 | array creation of type String[] : String[] | | GlobalDataFlow.cs:112:61:112:68 | access to local variable nonSink0 : String | GlobalDataFlow.cs:114:21:114:66 | (...) ... | -| GlobalDataFlow.cs:112:61:112:68 | access to local variable nonSink0 : String | GlobalDataFlow.cs:114:21:114:66 | (...) ... : IEnumerable | +| GlobalDataFlow.cs:112:61:112:68 | access to local variable nonSink0 : String | GlobalDataFlow.cs:114:21:114:66 | (...) ... : String[] | | GlobalDataFlow.cs:112:61:112:68 | access to local variable nonSink0 : String | GlobalDataFlow.cs:114:42:114:66 | array creation of type String[] | | GlobalDataFlow.cs:112:61:112:68 | access to local variable nonSink0 : String | GlobalDataFlow.cs:114:42:114:66 | array creation of type String[] : String[] | | GlobalDataFlow.cs:112:61:112:68 | access to local variable nonSink0 : String | GlobalDataFlow.cs:114:57:114:64 | access to local variable nonSink0 | | GlobalDataFlow.cs:112:61:112:68 | access to local variable nonSink0 : String | GlobalDataFlow.cs:114:57:114:64 | access to local variable nonSink0 : String | | GlobalDataFlow.cs:112:61:112:68 | access to local variable nonSink0 : T | GlobalDataFlow.cs:112:25:112:70 | (...) ... | -| GlobalDataFlow.cs:112:61:112:68 | access to local variable nonSink0 : T | GlobalDataFlow.cs:112:25:112:70 | (...) ... : IEnumerable | +| GlobalDataFlow.cs:112:61:112:68 | access to local variable nonSink0 : T | GlobalDataFlow.cs:112:25:112:70 | (...) ... : String[] | | GlobalDataFlow.cs:112:61:112:68 | access to local variable nonSink0 : T | GlobalDataFlow.cs:112:46:112:70 | array creation of type String[] | | GlobalDataFlow.cs:112:61:112:68 | access to local variable nonSink0 : T | GlobalDataFlow.cs:112:46:112:70 | array creation of type String[] : String[] | | GlobalDataFlow.cs:112:61:112:68 | access to local variable nonSink0 : T | GlobalDataFlow.cs:114:21:114:66 | (...) ... | -| GlobalDataFlow.cs:112:61:112:68 | access to local variable nonSink0 : T | GlobalDataFlow.cs:114:21:114:66 | (...) ... : IEnumerable | +| GlobalDataFlow.cs:112:61:112:68 | access to local variable nonSink0 : T | GlobalDataFlow.cs:114:21:114:66 | (...) ... : String[] | | GlobalDataFlow.cs:112:61:112:68 | access to local variable nonSink0 : T | GlobalDataFlow.cs:114:42:114:66 | array creation of type String[] | | GlobalDataFlow.cs:112:61:112:68 | access to local variable nonSink0 : T | GlobalDataFlow.cs:114:42:114:66 | array creation of type String[] : String[] | | GlobalDataFlow.cs:112:61:112:68 | access to local variable nonSink0 : T | GlobalDataFlow.cs:114:57:114:64 | access to local variable nonSink0 | @@ -2585,11 +2585,11 @@ | GlobalDataFlow.cs:114:42:114:66 | array creation of type String[] : String[] | GlobalDataFlow.cs:114:21:114:66 | (...) ... | | GlobalDataFlow.cs:114:42:114:66 | array creation of type String[] : String[] | GlobalDataFlow.cs:114:21:114:66 | (...) ... : String[] | | GlobalDataFlow.cs:114:57:114:64 | access to local variable nonSink0 : String | GlobalDataFlow.cs:114:21:114:66 | (...) ... | -| GlobalDataFlow.cs:114:57:114:64 | access to local variable nonSink0 : String | GlobalDataFlow.cs:114:21:114:66 | (...) ... : IEnumerable | +| GlobalDataFlow.cs:114:57:114:64 | access to local variable nonSink0 : String | GlobalDataFlow.cs:114:21:114:66 | (...) ... : String[] | | GlobalDataFlow.cs:114:57:114:64 | access to local variable nonSink0 : String | GlobalDataFlow.cs:114:42:114:66 | array creation of type String[] | | GlobalDataFlow.cs:114:57:114:64 | access to local variable nonSink0 : String | GlobalDataFlow.cs:114:42:114:66 | array creation of type String[] : String[] | | GlobalDataFlow.cs:114:57:114:64 | access to local variable nonSink0 : T | GlobalDataFlow.cs:114:21:114:66 | (...) ... | -| GlobalDataFlow.cs:114:57:114:64 | access to local variable nonSink0 : T | GlobalDataFlow.cs:114:21:114:66 | (...) ... : IEnumerable | +| GlobalDataFlow.cs:114:57:114:64 | access to local variable nonSink0 : T | GlobalDataFlow.cs:114:21:114:66 | (...) ... : String[] | | GlobalDataFlow.cs:114:57:114:64 | access to local variable nonSink0 : T | GlobalDataFlow.cs:114:42:114:66 | array creation of type String[] | | GlobalDataFlow.cs:114:57:114:64 | access to local variable nonSink0 : T | GlobalDataFlow.cs:114:42:114:66 | array creation of type String[] : String[] | | GlobalDataFlow.cs:114:76:114:76 | x : IEnumerable | GlobalDataFlow.cs:114:76:114:76 | x | @@ -2627,7 +2627,7 @@ | GlobalDataFlow.cs:116:42:116:72 | array creation of type String[] : String[] | GlobalDataFlow.cs:116:21:116:72 | (...) ... | | GlobalDataFlow.cs:116:42:116:72 | array creation of type String[] : String[] | GlobalDataFlow.cs:116:21:116:72 | (...) ... : String[] | | GlobalDataFlow.cs:116:57:116:62 | access to local variable sink14 : IEnumerable | GlobalDataFlow.cs:116:21:116:72 | (...) ... | -| GlobalDataFlow.cs:116:57:116:62 | access to local variable sink14 : IEnumerable | GlobalDataFlow.cs:116:21:116:72 | (...) ... : IEnumerable | +| GlobalDataFlow.cs:116:57:116:62 | access to local variable sink14 : IEnumerable | GlobalDataFlow.cs:116:21:116:72 | (...) ... : String[] | | GlobalDataFlow.cs:116:57:116:62 | access to local variable sink14 : IEnumerable | GlobalDataFlow.cs:116:42:116:72 | array creation of type String[] | | GlobalDataFlow.cs:116:57:116:62 | access to local variable sink14 : IEnumerable | GlobalDataFlow.cs:116:42:116:72 | array creation of type String[] : String[] | | GlobalDataFlow.cs:116:57:116:62 | access to local variable sink14 : IEnumerable | GlobalDataFlow.cs:116:57:116:70 | call to method First | @@ -2637,7 +2637,7 @@ | GlobalDataFlow.cs:116:57:116:62 | access to local variable sink14 : IEnumerable | GlobalDataFlow.cs:122:20:122:25 | access to local variable sink14 | | GlobalDataFlow.cs:116:57:116:62 | access to local variable sink14 : IEnumerable | GlobalDataFlow.cs:122:20:122:25 | access to local variable sink14 : IEnumerable | | GlobalDataFlow.cs:116:57:116:70 | call to method First : String | GlobalDataFlow.cs:116:21:116:72 | (...) ... | -| GlobalDataFlow.cs:116:57:116:70 | call to method First : String | GlobalDataFlow.cs:116:21:116:72 | (...) ... : IEnumerable | +| GlobalDataFlow.cs:116:57:116:70 | call to method First : String | GlobalDataFlow.cs:116:21:116:72 | (...) ... : String[] | | GlobalDataFlow.cs:116:57:116:70 | call to method First : String | GlobalDataFlow.cs:116:42:116:72 | array creation of type String[] | | GlobalDataFlow.cs:116:57:116:70 | call to method First : String | GlobalDataFlow.cs:116:42:116:72 | array creation of type String[] : String[] | | GlobalDataFlow.cs:116:80:116:119 | (...) ... : IEnumerable | GlobalDataFlow.cs:116:123:116:133 | [output] (...) => ... | @@ -2651,7 +2651,7 @@ | GlobalDataFlow.cs:116:101:116:119 | array creation of type String[] : String[] | GlobalDataFlow.cs:116:80:116:119 | (...) ... | | GlobalDataFlow.cs:116:101:116:119 | array creation of type String[] : String[] | GlobalDataFlow.cs:116:80:116:119 | (...) ... : String[] | | GlobalDataFlow.cs:116:116:116:117 | "" : String | GlobalDataFlow.cs:116:80:116:119 | (...) ... | -| GlobalDataFlow.cs:116:116:116:117 | "" : String | GlobalDataFlow.cs:116:80:116:119 | (...) ... : IEnumerable | +| GlobalDataFlow.cs:116:116:116:117 | "" : String | GlobalDataFlow.cs:116:80:116:119 | (...) ... : String[] | | GlobalDataFlow.cs:116:116:116:117 | "" : String | GlobalDataFlow.cs:116:101:116:119 | array creation of type String[] | | GlobalDataFlow.cs:116:116:116:117 | "" : String | GlobalDataFlow.cs:116:101:116:119 | array creation of type String[] : String[] | | GlobalDataFlow.cs:116:123:116:133 | [output] (...) => ... : String | GlobalDataFlow.cs:116:9:116:134 | SSA def(nonSink1) | @@ -2701,19 +2701,19 @@ | GlobalDataFlow.cs:118:42:118:60 | array creation of type String[] : String[] | GlobalDataFlow.cs:118:21:118:60 | (...) ... | | GlobalDataFlow.cs:118:42:118:60 | array creation of type String[] : String[] | GlobalDataFlow.cs:118:21:118:60 | (...) ... : String[] | | GlobalDataFlow.cs:118:57:118:58 | "" : String | GlobalDataFlow.cs:118:21:118:60 | (...) ... | -| GlobalDataFlow.cs:118:57:118:58 | "" : String | GlobalDataFlow.cs:118:21:118:60 | (...) ... : IEnumerable | +| GlobalDataFlow.cs:118:57:118:58 | "" : String | GlobalDataFlow.cs:118:21:118:60 | (...) ... : String[] | | GlobalDataFlow.cs:118:57:118:58 | "" : String | GlobalDataFlow.cs:118:42:118:60 | array creation of type String[] | | GlobalDataFlow.cs:118:57:118:58 | "" : String | GlobalDataFlow.cs:118:42:118:60 | array creation of type String[] : String[] | | GlobalDataFlow.cs:118:89:118:119 | array creation of type String[] : String[] | GlobalDataFlow.cs:118:68:118:119 | (...) ... | | GlobalDataFlow.cs:118:89:118:119 | array creation of type String[] : String[] | GlobalDataFlow.cs:118:68:118:119 | (...) ... : String[] | | GlobalDataFlow.cs:118:104:118:109 | access to local variable sink15 : IEnumerable | GlobalDataFlow.cs:118:68:118:119 | (...) ... | -| GlobalDataFlow.cs:118:104:118:109 | access to local variable sink15 : IEnumerable | GlobalDataFlow.cs:118:68:118:119 | (...) ... : IEnumerable | +| GlobalDataFlow.cs:118:104:118:109 | access to local variable sink15 : IEnumerable | GlobalDataFlow.cs:118:68:118:119 | (...) ... : String[] | | GlobalDataFlow.cs:118:104:118:109 | access to local variable sink15 : IEnumerable | GlobalDataFlow.cs:118:89:118:119 | array creation of type String[] | | GlobalDataFlow.cs:118:104:118:109 | access to local variable sink15 : IEnumerable | GlobalDataFlow.cs:118:89:118:119 | array creation of type String[] : String[] | | GlobalDataFlow.cs:118:104:118:109 | access to local variable sink15 : IEnumerable | GlobalDataFlow.cs:118:104:118:117 | call to method First | | GlobalDataFlow.cs:118:104:118:109 | access to local variable sink15 : IEnumerable | GlobalDataFlow.cs:118:104:118:117 | call to method First : String | | GlobalDataFlow.cs:118:104:118:117 | call to method First : String | GlobalDataFlow.cs:118:68:118:119 | (...) ... | -| GlobalDataFlow.cs:118:104:118:117 | call to method First : String | GlobalDataFlow.cs:118:68:118:119 | (...) ... : IEnumerable | +| GlobalDataFlow.cs:118:104:118:117 | call to method First : String | GlobalDataFlow.cs:118:68:118:119 | (...) ... : String[] | | GlobalDataFlow.cs:118:104:118:117 | call to method First : String | GlobalDataFlow.cs:118:89:118:119 | array creation of type String[] | | GlobalDataFlow.cs:118:104:118:117 | call to method First : String | GlobalDataFlow.cs:118:89:118:119 | array creation of type String[] : String[] | | GlobalDataFlow.cs:118:123:118:133 | [output] (...) => ... : String | GlobalDataFlow.cs:118:9:118:134 | SSA def(nonSink1) | @@ -3833,17 +3833,17 @@ | GlobalDataFlow.cs:222:19:222:39 | call to method Select : IQueryable | GlobalDataFlow.cs:223:15:223:21 | access to local variable nonSink | | GlobalDataFlow.cs:222:19:222:39 | call to method Select : IQueryable | GlobalDataFlow.cs:223:15:223:21 | access to local variable nonSink : IQueryable | | GlobalDataFlow.cs:222:37:222:38 | [output] access to local variable f2 : String | GlobalDataFlow.cs:222:9:222:39 | SSA def(nonSink) | -| GlobalDataFlow.cs:222:37:222:38 | [output] access to local variable f2 : String | GlobalDataFlow.cs:222:9:222:39 | SSA def(nonSink) : IEnumerable | +| GlobalDataFlow.cs:222:37:222:38 | [output] access to local variable f2 : String | GlobalDataFlow.cs:222:9:222:39 | SSA def(nonSink) : IQueryable | | GlobalDataFlow.cs:222:37:222:38 | [output] access to local variable f2 : String | GlobalDataFlow.cs:222:19:222:39 | call to method Select | | GlobalDataFlow.cs:222:37:222:38 | [output] access to local variable f2 : String | GlobalDataFlow.cs:222:19:222:39 | call to method Select : IQueryable | | GlobalDataFlow.cs:222:37:222:38 | [output] access to local variable f2 : String | GlobalDataFlow.cs:223:15:223:21 | access to local variable nonSink | -| GlobalDataFlow.cs:222:37:222:38 | [output] access to local variable f2 : String | GlobalDataFlow.cs:223:15:223:21 | access to local variable nonSink : IEnumerable | +| GlobalDataFlow.cs:222:37:222:38 | [output] access to local variable f2 : String | GlobalDataFlow.cs:223:15:223:21 | access to local variable nonSink : IQueryable | | GlobalDataFlow.cs:222:37:222:38 | [output] access to local variable f2 : T | GlobalDataFlow.cs:222:9:222:39 | SSA def(nonSink) | -| GlobalDataFlow.cs:222:37:222:38 | [output] access to local variable f2 : T | GlobalDataFlow.cs:222:9:222:39 | SSA def(nonSink) : IEnumerable | +| GlobalDataFlow.cs:222:37:222:38 | [output] access to local variable f2 : T | GlobalDataFlow.cs:222:9:222:39 | SSA def(nonSink) : IQueryable | | GlobalDataFlow.cs:222:37:222:38 | [output] access to local variable f2 : T | GlobalDataFlow.cs:222:19:222:39 | call to method Select | | GlobalDataFlow.cs:222:37:222:38 | [output] access to local variable f2 : T | GlobalDataFlow.cs:222:19:222:39 | call to method Select : IQueryable | | GlobalDataFlow.cs:222:37:222:38 | [output] access to local variable f2 : T | GlobalDataFlow.cs:223:15:223:21 | access to local variable nonSink | -| GlobalDataFlow.cs:222:37:222:38 | [output] access to local variable f2 : T | GlobalDataFlow.cs:223:15:223:21 | access to local variable nonSink : IEnumerable | +| GlobalDataFlow.cs:222:37:222:38 | [output] access to local variable f2 : T | GlobalDataFlow.cs:223:15:223:21 | access to local variable nonSink : IQueryable | | GlobalDataFlow.cs:224:9:224:39 | SSA def(nonSink) : IEnumerable | GlobalDataFlow.cs:225:15:225:21 | access to local variable nonSink | | GlobalDataFlow.cs:224:9:224:39 | SSA def(nonSink) : IEnumerable | GlobalDataFlow.cs:225:15:225:21 | access to local variable nonSink : IEnumerable | | GlobalDataFlow.cs:224:19:224:28 | access to parameter notTainted : IQueryable | GlobalDataFlow.cs:218:35:218:46 | nonSinkParam | @@ -3879,17 +3879,17 @@ | GlobalDataFlow.cs:226:19:226:39 | call to method Select : IQueryable | GlobalDataFlow.cs:227:15:227:21 | access to local variable nonSink | | GlobalDataFlow.cs:226:19:226:39 | call to method Select : IQueryable | GlobalDataFlow.cs:227:15:227:21 | access to local variable nonSink : IQueryable | | GlobalDataFlow.cs:226:37:226:38 | [output] access to local variable f4 : String | GlobalDataFlow.cs:226:9:226:39 | SSA def(nonSink) | -| GlobalDataFlow.cs:226:37:226:38 | [output] access to local variable f4 : String | GlobalDataFlow.cs:226:9:226:39 | SSA def(nonSink) : IEnumerable | +| GlobalDataFlow.cs:226:37:226:38 | [output] access to local variable f4 : String | GlobalDataFlow.cs:226:9:226:39 | SSA def(nonSink) : IQueryable | | GlobalDataFlow.cs:226:37:226:38 | [output] access to local variable f4 : String | GlobalDataFlow.cs:226:19:226:39 | call to method Select | | GlobalDataFlow.cs:226:37:226:38 | [output] access to local variable f4 : String | GlobalDataFlow.cs:226:19:226:39 | call to method Select : IQueryable | | GlobalDataFlow.cs:226:37:226:38 | [output] access to local variable f4 : String | GlobalDataFlow.cs:227:15:227:21 | access to local variable nonSink | -| GlobalDataFlow.cs:226:37:226:38 | [output] access to local variable f4 : String | GlobalDataFlow.cs:227:15:227:21 | access to local variable nonSink : IEnumerable | +| GlobalDataFlow.cs:226:37:226:38 | [output] access to local variable f4 : String | GlobalDataFlow.cs:227:15:227:21 | access to local variable nonSink : IQueryable | | GlobalDataFlow.cs:226:37:226:38 | [output] access to local variable f4 : T | GlobalDataFlow.cs:226:9:226:39 | SSA def(nonSink) | -| GlobalDataFlow.cs:226:37:226:38 | [output] access to local variable f4 : T | GlobalDataFlow.cs:226:9:226:39 | SSA def(nonSink) : IEnumerable | +| GlobalDataFlow.cs:226:37:226:38 | [output] access to local variable f4 : T | GlobalDataFlow.cs:226:9:226:39 | SSA def(nonSink) : IQueryable | | GlobalDataFlow.cs:226:37:226:38 | [output] access to local variable f4 : T | GlobalDataFlow.cs:226:19:226:39 | call to method Select | | GlobalDataFlow.cs:226:37:226:38 | [output] access to local variable f4 : T | GlobalDataFlow.cs:226:19:226:39 | call to method Select : IQueryable | | GlobalDataFlow.cs:226:37:226:38 | [output] access to local variable f4 : T | GlobalDataFlow.cs:227:15:227:21 | access to local variable nonSink | -| GlobalDataFlow.cs:226:37:226:38 | [output] access to local variable f4 : T | GlobalDataFlow.cs:227:15:227:21 | access to local variable nonSink : IEnumerable | +| GlobalDataFlow.cs:226:37:226:38 | [output] access to local variable f4 : T | GlobalDataFlow.cs:227:15:227:21 | access to local variable nonSink : IQueryable | | GlobalDataFlow.cs:228:9:228:49 | SSA def(nonSink) : IEnumerable | GlobalDataFlow.cs:229:15:229:21 | access to local variable nonSink | | GlobalDataFlow.cs:228:9:228:49 | SSA def(nonSink) : IEnumerable | GlobalDataFlow.cs:229:15:229:21 | access to local variable nonSink : IEnumerable | | GlobalDataFlow.cs:228:19:228:28 | access to parameter notTainted : IQueryable | GlobalDataFlow.cs:228:37:228:48 | [output] delegate creation of type Func | diff --git a/csharp/ql/test/library-tests/dataflow/global/TaintTrackingPath.expected b/csharp/ql/test/library-tests/dataflow/global/TaintTrackingPath.expected index 59bb9b3d220..c612897d3ee 100644 --- a/csharp/ql/test/library-tests/dataflow/global/TaintTrackingPath.expected +++ b/csharp/ql/test/library-tests/dataflow/global/TaintTrackingPath.expected @@ -137,21 +137,21 @@ edges | GlobalDataFlow.cs:75:30:75:34 | SSA def(sink2) : String | GlobalDataFlow.cs:78:19:78:23 | access to local variable sink2 : String | | GlobalDataFlow.cs:78:19:78:23 | access to local variable sink2 : String | GlobalDataFlow.cs:78:30:78:34 | SSA def(sink3) : String | | GlobalDataFlow.cs:78:30:78:34 | SSA def(sink3) : String | GlobalDataFlow.cs:79:15:79:19 | access to local variable sink3 | -| GlobalDataFlow.cs:78:30:78:34 | SSA def(sink3) : String | GlobalDataFlow.cs:80:23:80:65 | (...) ... : IEnumerable | +| GlobalDataFlow.cs:78:30:78:34 | SSA def(sink3) : String | GlobalDataFlow.cs:80:23:80:65 | (...) ... : String[] | | GlobalDataFlow.cs:78:30:78:34 | SSA def(sink3) : String | GlobalDataFlow.cs:135:29:135:33 | access to local variable sink3 : String | | GlobalDataFlow.cs:80:22:80:85 | call to method SelectEven : IEnumerable | GlobalDataFlow.cs:81:15:81:20 | access to local variable sink13 | -| GlobalDataFlow.cs:80:22:80:85 | call to method SelectEven : IEnumerable | GlobalDataFlow.cs:82:23:82:74 | (...) ... : IEnumerable | -| GlobalDataFlow.cs:80:23:80:65 | (...) ... : IEnumerable | GlobalDataFlow.cs:80:22:80:85 | call to method SelectEven : IEnumerable | -| GlobalDataFlow.cs:82:23:82:74 | (...) ... : IEnumerable | GlobalDataFlow.cs:82:84:82:94 | [output] delegate creation of type Func : T | -| GlobalDataFlow.cs:82:23:82:74 | (...) ... : IEnumerable | GlobalDataFlow.cs:292:31:292:40 | sinkParam8 : IEnumerable | +| GlobalDataFlow.cs:80:22:80:85 | call to method SelectEven : IEnumerable | GlobalDataFlow.cs:82:23:82:74 | (...) ... : String[] | +| GlobalDataFlow.cs:80:23:80:65 | (...) ... : String[] | GlobalDataFlow.cs:80:22:80:85 | call to method SelectEven : IEnumerable | +| GlobalDataFlow.cs:82:23:82:74 | (...) ... : String[] | GlobalDataFlow.cs:82:84:82:94 | [output] delegate creation of type Func : T | +| GlobalDataFlow.cs:82:23:82:74 | (...) ... : String[] | GlobalDataFlow.cs:292:31:292:40 | sinkParam8 : String[] | | GlobalDataFlow.cs:82:84:82:94 | [output] delegate creation of type Func : T | GlobalDataFlow.cs:83:15:83:20 | access to local variable sink14 | -| GlobalDataFlow.cs:82:84:82:94 | [output] delegate creation of type Func : T | GlobalDataFlow.cs:84:23:84:74 | (...) ... : IEnumerable | +| GlobalDataFlow.cs:82:84:82:94 | [output] delegate creation of type Func : T | GlobalDataFlow.cs:84:23:84:74 | (...) ... : String[] | | GlobalDataFlow.cs:82:84:82:94 | [output] delegate creation of type Func : T | GlobalDataFlow.cs:88:22:88:27 | access to local variable sink14 : IEnumerable | | GlobalDataFlow.cs:82:84:82:94 | [output] delegate creation of type Func : T | GlobalDataFlow.cs:90:75:90:88 | call to method First : String | -| GlobalDataFlow.cs:84:23:84:74 | (...) ... : IEnumerable | GlobalDataFlow.cs:84:125:84:135 | [output] (...) => ... : String | +| GlobalDataFlow.cs:84:23:84:74 | (...) ... : String[] | GlobalDataFlow.cs:84:125:84:135 | [output] (...) => ... : String | | GlobalDataFlow.cs:84:125:84:135 | [output] (...) => ... : String | GlobalDataFlow.cs:85:15:85:20 | access to local variable sink15 | -| GlobalDataFlow.cs:84:125:84:135 | [output] (...) => ... : String | GlobalDataFlow.cs:86:70:86:121 | (...) ... : IEnumerable | -| GlobalDataFlow.cs:86:70:86:121 | (...) ... : IEnumerable | GlobalDataFlow.cs:86:125:86:135 | [output] (...) => ... : String | +| GlobalDataFlow.cs:84:125:84:135 | [output] (...) => ... : String | GlobalDataFlow.cs:86:70:86:121 | (...) ... : String[] | +| GlobalDataFlow.cs:86:70:86:121 | (...) ... : String[] | GlobalDataFlow.cs:86:125:86:135 | [output] (...) => ... : String | | GlobalDataFlow.cs:86:125:86:135 | [output] (...) => ... : String | GlobalDataFlow.cs:87:15:87:20 | access to local variable sink16 | | GlobalDataFlow.cs:88:22:88:27 | access to local variable sink14 : IEnumerable | GlobalDataFlow.cs:88:43:88:61 | [output] (...) => ... : String | | GlobalDataFlow.cs:88:43:88:61 | [output] (...) => ... : String | GlobalDataFlow.cs:88:64:88:69 | [output] (...) => ... : String | @@ -199,7 +199,7 @@ edges | GlobalDataFlow.cs:255:26:255:35 | sinkParam5 : String | GlobalDataFlow.cs:257:15:257:24 | access to parameter sinkParam5 | | GlobalDataFlow.cs:260:26:260:35 | sinkParam6 : String | GlobalDataFlow.cs:262:15:262:24 | access to parameter sinkParam6 | | GlobalDataFlow.cs:265:26:265:35 | sinkParam7 : String | GlobalDataFlow.cs:267:15:267:24 | access to parameter sinkParam7 | -| GlobalDataFlow.cs:292:31:292:40 | sinkParam8 : IEnumerable | GlobalDataFlow.cs:294:15:294:24 | access to parameter sinkParam8 | +| GlobalDataFlow.cs:292:31:292:40 | sinkParam8 : String[] | GlobalDataFlow.cs:294:15:294:24 | access to parameter sinkParam8 | | GlobalDataFlow.cs:298:32:298:41 | sinkParam9 : String | GlobalDataFlow.cs:300:15:300:24 | access to parameter sinkParam9 | | GlobalDataFlow.cs:304:32:304:42 | sinkParam11 : IQueryable | GlobalDataFlow.cs:306:15:306:25 | access to parameter sinkParam11 | | GlobalDataFlow.cs:318:16:318:29 | "taint source" : String | GlobalDataFlow.cs:153:21:153:25 | call to method Out : String | @@ -331,15 +331,15 @@ nodes | GlobalDataFlow.cs:78:30:78:34 | SSA def(sink3) : String | semmle.label | SSA def(sink3) : String | | GlobalDataFlow.cs:79:15:79:19 | access to local variable sink3 | semmle.label | access to local variable sink3 | | GlobalDataFlow.cs:80:22:80:85 | call to method SelectEven : IEnumerable | semmle.label | call to method SelectEven : IEnumerable | -| GlobalDataFlow.cs:80:23:80:65 | (...) ... : IEnumerable | semmle.label | (...) ... : IEnumerable | +| GlobalDataFlow.cs:80:23:80:65 | (...) ... : String[] | semmle.label | (...) ... : String[] | | GlobalDataFlow.cs:81:15:81:20 | access to local variable sink13 | semmle.label | access to local variable sink13 | -| GlobalDataFlow.cs:82:23:82:74 | (...) ... : IEnumerable | semmle.label | (...) ... : IEnumerable | +| GlobalDataFlow.cs:82:23:82:74 | (...) ... : String[] | semmle.label | (...) ... : String[] | | GlobalDataFlow.cs:82:84:82:94 | [output] delegate creation of type Func : T | semmle.label | [output] delegate creation of type Func : T | | GlobalDataFlow.cs:83:15:83:20 | access to local variable sink14 | semmle.label | access to local variable sink14 | -| GlobalDataFlow.cs:84:23:84:74 | (...) ... : IEnumerable | semmle.label | (...) ... : IEnumerable | +| GlobalDataFlow.cs:84:23:84:74 | (...) ... : String[] | semmle.label | (...) ... : String[] | | GlobalDataFlow.cs:84:125:84:135 | [output] (...) => ... : String | semmle.label | [output] (...) => ... : String | | GlobalDataFlow.cs:85:15:85:20 | access to local variable sink15 | semmle.label | access to local variable sink15 | -| GlobalDataFlow.cs:86:70:86:121 | (...) ... : IEnumerable | semmle.label | (...) ... : IEnumerable | +| GlobalDataFlow.cs:86:70:86:121 | (...) ... : String[] | semmle.label | (...) ... : String[] | | GlobalDataFlow.cs:86:125:86:135 | [output] (...) => ... : String | semmle.label | [output] (...) => ... : String | | GlobalDataFlow.cs:87:15:87:20 | access to local variable sink16 | semmle.label | access to local variable sink16 | | GlobalDataFlow.cs:88:22:88:27 | access to local variable sink14 : IEnumerable | semmle.label | access to local variable sink14 : IEnumerable | @@ -404,7 +404,7 @@ nodes | GlobalDataFlow.cs:262:15:262:24 | access to parameter sinkParam6 | semmle.label | access to parameter sinkParam6 | | GlobalDataFlow.cs:265:26:265:35 | sinkParam7 : String | semmle.label | sinkParam7 : String | | GlobalDataFlow.cs:267:15:267:24 | access to parameter sinkParam7 | semmle.label | access to parameter sinkParam7 | -| GlobalDataFlow.cs:292:31:292:40 | sinkParam8 : IEnumerable | semmle.label | sinkParam8 : IEnumerable | +| GlobalDataFlow.cs:292:31:292:40 | sinkParam8 : String[] | semmle.label | sinkParam8 : String[] | | GlobalDataFlow.cs:294:15:294:24 | access to parameter sinkParam8 | semmle.label | access to parameter sinkParam8 | | GlobalDataFlow.cs:298:32:298:41 | sinkParam9 : String | semmle.label | sinkParam9 : String | | GlobalDataFlow.cs:300:15:300:24 | access to parameter sinkParam9 | semmle.label | access to parameter sinkParam9 | diff --git a/docs/experimental.md b/docs/experimental.md new file mode 100644 index 00000000000..e1f56bcf7d4 --- /dev/null +++ b/docs/experimental.md @@ -0,0 +1,41 @@ +# Experimental CodeQL queries and libraries + +In addition to our standard CodeQL queries and libraries, this repository may also contain queries and libraries of a more experimental nature. Experimental queries and libraries can be improved incrementally and may eventually reach a sufficient maturity to be included in our standard libraries and queries. + +Experimental queries and libraries may not be actively maintained as the standard libraries evolve. They may also be changed in backwards-incompatible ways or may be removed entirely in the future without deprecation warnings. + +## Requirements + +1. **Directory structure** + + - Experimental queries and libraries are stored in the `experimental` subdirectory within each language-specific directory in the [CodeQL repository](https://github.com/Semmle/ql). For example, experimental Java queries and libraries are stored in `ql/java/ql/src/experimental` and any corresponding tests in `ql/java/ql/test/experimental`. + - The structure of an `experimental` subdirectory mirrors the structure of standard queries and libraries (or tests) in the parent directory. + +2. **Query metadata** + + - The query `@id` must not clash with any other queries in the repository. + - The query must have a `@name` and `@description` to explain its purpose. + - The query must have a `@kind` and `@problem.severity` as required by CodeQL tools. + + For details, see the [guide on query metadata](https://github.com/Semmle/ql/blob/master/docs/query-metadata-style-guide.md). + +3. **Formatting** + + - The queries and libraries must be [autoformatted](https://help.semmle.com/codeql/codeql-for-vscode/reference/editor.html#autoformatting). + +4. **Compilation** + + - Compilation of the query and any associated libraries and tests must be resilient to future development of the standard libraries. This means that the functionality cannot use internal APIs, cannot depend on the output of `getAQlClass`, and cannot make use of regexp matching on `toString`. + - The query and any associated libraries and tests must not cause any compiler warnings to be emitted (such as use of deprecated functionality or missing `override` annotations). + +5. **Results** + + - The query must have at least one true positive result on some revision of a real project. + +6. **Contributor License Agreement** + + - The contributor can satisfy the [CLA](CONTRIBUTING.md#contributor-license-agreement). + +## Non-requirements + +Other criteria typically required for our standard queries and libraries are not required for experimental queries and libraries. In particular, fully disciplined query [metadata](docs/query-metadata-style-guide.md), query [help](docs/query-help-style-guide.md), tests, a low false positive rate and performance tuning are not required (but nonetheless recommended). diff --git a/docs/language/learn-ql/index.rst b/docs/language/learn-ql/index.rst index f25bf8bb644..12ed9cef8a9 100644 --- a/docs/language/learn-ql/index.rst +++ b/docs/language/learn-ql/index.rst @@ -1,7 +1,7 @@ Learning CodeQL ############### -CodeQL is the code analysis platform used by security researchers to automate `variant analysis `__. +CodeQL is the code analysis platform used by security researchers to automate variant analysis. You can use CodeQL queries to explore code and quickly find variants of security vulnerabilities and bugs. These queries are easy to write and share–visit the topics below and `our open source repository on GitHub `__ to learn more. You can also try out CodeQL in the `query console `__ on `LGTM.com `__. diff --git a/docs/language/learn-ql/ql-training.rst b/docs/language/learn-ql/ql-training.rst index fcb14476d88..55c3d33f705 100644 --- a/docs/language/learn-ql/ql-training.rst +++ b/docs/language/learn-ql/ql-training.rst @@ -4,9 +4,9 @@ CodeQL training and variant analysis examples CodeQL and variant analysis --------------------------- -`Variant analysis `__ is the process of using a known vulnerability as a seed to find similar problems in your code. Security engineers typically perform variant analysis to identify possible vulnerabilities and to ensure that these threats are properly fixed across multiple code bases. +Variant analysis is the process of using a known vulnerability as a seed to find similar problems in your code. Security engineers typically perform variant analysis to identify possible vulnerabilities and to ensure that these threats are properly fixed across multiple code bases. -`CodeQL `__ is the code analysis engine that underpins LGTM, Semmle's community driven security analysis platform. Together, CodeQL and LGTM provide continuous monitoring and scalable variant analysis for your projects, even if you don’t have your own team of dedicated security engineers. You can read more about using CodeQL and LGTM in variant analysis on the `Security Lab research page `__. +CodeQL is the code analysis engine that underpins LGTM, the community driven security analysis platform. Together, CodeQL and LGTM provide continuous monitoring and scalable variant analysis for your projects, even if you don’t have your own team of dedicated security engineers. You can read more about using CodeQL and LGTM in variant analysis on the `Security Lab research page `__. CodeQL is easy to learn, and exploring code using CodeQL is the most efficient way to perform variant analysis. diff --git a/docs/language/ql-training/cpp/bad-overflow-guard.rst b/docs/language/ql-training/cpp/bad-overflow-guard.rst index 1ae193b10f0..0630bdcb039 100644 --- a/docs/language/ql-training/cpp/bad-overflow-guard.rst +++ b/docs/language/ql-training/cpp/bad-overflow-guard.rst @@ -141,7 +141,7 @@ Let’s look for overflow guards of the form ``v + b < v``, using the classes .. note:: - - When performing `variant analysis `__, it is usually helpful to write a simple query that finds the simple syntactic pattern, before trying to go on to describe the cases where it goes wrong. + - When performing variant analysis, it is usually helpful to write a simple query that finds the simple syntactic pattern, before trying to go on to describe the cases where it goes wrong. - In this case, we start by looking for all the *overflow* checks, before trying to refine the query to find all *bad overflow* checks. - The ``select`` clause defines what this query is looking for: diff --git a/docs/language/ql-training/java/query-injection-java.rst b/docs/language/ql-training/java/query-injection-java.rst index f9911de137b..ce215069b8d 100644 --- a/docs/language/ql-training/java/query-injection-java.rst +++ b/docs/language/ql-training/java/query-injection-java.rst @@ -77,7 +77,7 @@ Let’s start by looking for calls to methods with names of the form ``sparql*Qu .. note:: - - When performing `variant analysis `__, it is usually helpful to write a simple query that finds the simple syntactic pattern, before trying to go on to describe the cases where it goes wrong. + - When performing variant analysis, it is usually helpful to write a simple query that finds the simple syntactic pattern, before trying to go on to describe the cases where it goes wrong. - In this case, we start by looking for all the method calls that appear to run, before trying to refine the query to find cases which are vulnerable to query injection. - The ``select`` clause defines what this query is looking for: diff --git a/docs/language/ql-training/slide-snippets/intro-ql-general.rst b/docs/language/ql-training/slide-snippets/intro-ql-general.rst index b53c6bbb015..8d6a25afad7 100644 --- a/docs/language/ql-training/slide-snippets/intro-ql-general.rst +++ b/docs/language/ql-training/slide-snippets/intro-ql-general.rst @@ -81,8 +81,6 @@ Find all instances! - All were fixed with a mid-flight patch. -- For more detail on the collaboration between Semmle and NASA, see our case study: `Semmle at NASA: Landing Curiosity safely on Mars `__. - .. note:: The JPL team ran the query across the full Curiosity control software–it identified the original problem, and more than 30 other variants, of which three were in the critical Entry, Descent, and Landing module. @@ -107,7 +105,7 @@ Analysis overview Once the extraction finishes, all this information is collected into a single `CodeQL database `__, which is then ready to query, possibly on a different machine. A copy of the source files, made at the time the database was created, is also included in the CodeQL database so analysis results can be displayed at the correct location in the code. The database schema is (source) language specific. - Queries are written in `QL `__ and usually depend on one or more of the `standard CodeQL libraries `__ (and of course you can write your own custom libraries). They are compiled into an efficiently executable format by the QL compiler and then run on a CodeQL database by the QL evaluator, either on a remote worker machine or locally on a developer’s machine. + Queries are written in QL and usually depend on one or more of the `standard CodeQL libraries `__ (and of course you can write your own custom libraries). They are compiled into an efficiently executable format by the QL compiler and then run on a CodeQL database by the QL evaluator, either on a remote worker machine or locally on a developer’s machine. Query results can be interpreted and presented in a variety of ways, including displaying them in an `IDE extension `__ such as CodeQL for Visual Studio Code, or in a web dashboard as on `LGTM `__. diff --git a/java/ql/src/Customizations.qll b/java/ql/src/Customizations.qll new file mode 100644 index 00000000000..1f5716726e3 --- /dev/null +++ b/java/ql/src/Customizations.qll @@ -0,0 +1,12 @@ +/** + * Contains customizations to the standard library. + * + * This module is imported by `java.qll`, so any customizations defined here automatically + * apply to all queries. + * + * Typical examples of customizations include adding new subclasses of abstract classes such as + * the `RemoteFlowSource` and `AdditionalTaintStep` classes associated with the security queries + * to model frameworks that are not covered by the standard library. + */ + +import java diff --git a/java/ql/src/experimental/README.md b/java/ql/src/experimental/README.md new file mode 100644 index 00000000000..fc07363b24f --- /dev/null +++ b/java/ql/src/experimental/README.md @@ -0,0 +1 @@ +This directory contains [experimental](../../../../docs/experimental.md) CodeQL queries and libraries. diff --git a/java/ql/src/java.qll b/java/ql/src/java.qll index c8e3b71f18b..6454a0d282b 100644 --- a/java/ql/src/java.qll +++ b/java/ql/src/java.qll @@ -1,5 +1,6 @@ /** Provides all default Java QL imports. */ +import Customizations import semmle.code.FileSystem import semmle.code.Location import semmle.code.java.Annotation diff --git a/java/ql/src/semmle/code/java/dataflow/TypeFlow.qll b/java/ql/src/semmle/code/java/dataflow/TypeFlow.qll index e9f32aadbbe..24cf918a9b7 100644 --- a/java/ql/src/semmle/code/java/dataflow/TypeFlow.qll +++ b/java/ql/src/semmle/code/java/dataflow/TypeFlow.qll @@ -364,13 +364,45 @@ private predicate typeFlow(TypeFlowNode n, RefType t) { typeFlowJoin(lastRank(n), n, t) } +pragma[nomagic] +predicate erasedTypeBound(RefType t) { + exists(RefType t0 | typeFlow(_, t0) and t = t0.getErasure()) +} + +pragma[nomagic] +predicate typeBound(RefType t) { typeFlow(_, t) } + +/** + * Holds if we have a bound for `n` that is better than `t`, taking only erased + * types into account. + */ +pragma[nomagic] +private predicate irrelevantErasedBound(TypeFlowNode n, RefType t) { + exists(RefType bound | + typeFlow(n, bound) + or + n.getType() = bound and typeFlow(n, _) + | + t = bound.getErasure().(RefType).getASourceSupertype+() and + erasedTypeBound(t) + ) +} + /** * Holds if we have a bound for `n` that is better than `t`. */ -pragma[noinline] +pragma[nomagic] private predicate irrelevantBound(TypeFlowNode n, RefType t) { - exists(RefType bound | typeFlow(n, bound) or n.getType() = bound | - t = bound.getErasure().(RefType).getASourceSupertype+() + exists(RefType bound | + typeFlow(n, bound) and + t = bound.getASupertype+() and + typeBound(t) and + typeFlow(n, t) and + not t.getASupertype*() = bound + or + n.getType() = bound and + typeFlow(n, t) and + t = bound.getASupertype*() ) } @@ -380,7 +412,8 @@ private predicate irrelevantBound(TypeFlowNode n, RefType t) { */ private predicate bestTypeFlow(TypeFlowNode n, RefType t) { typeFlow(n, t) and - not irrelevantBound(n, t.getErasure()) + not irrelevantErasedBound(n, t.getErasure()) and + not irrelevantBound(n, t) } cached 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 5eb98aefe4e..e3f1da355ef 100644 --- a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll +++ b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll @@ -1307,15 +1307,20 @@ private predicate localFlowExit(Node node, Configuration config) { */ pragma[nomagic] private predicate localFlowStepPlus( - Node node1, Node node2, boolean preservesValue, Configuration config, LocalCallContext cc + Node node1, Node node2, boolean preservesValue, DataFlowType t, Configuration config, + LocalCallContext cc ) { not isUnreachableInCall(node2, cc.(LocalCallContextSpecificCall).getCall()) and ( localFlowEntry(node1, config) and ( - localFlowStep(node1, node2, config) and preservesValue = true + localFlowStep(node1, node2, config) and + preservesValue = true and + t = getErasedNodeTypeBound(node1) or - additionalLocalFlowStep(node1, node2, config) and preservesValue = false + additionalLocalFlowStep(node1, node2, config) and + preservesValue = false and + t = getErasedNodeTypeBound(node2) ) and node1 != node2 and cc.relevantFor(node1.getEnclosingCallable()) and @@ -1323,17 +1328,18 @@ private predicate localFlowStepPlus( nodeCand(TNormalNode(node2), unbind(config)) or exists(Node mid | - localFlowStepPlus(node1, mid, preservesValue, config, cc) and + localFlowStepPlus(node1, mid, preservesValue, t, config, cc) and localFlowStep(mid, node2, config) and not mid instanceof CastNode and nodeCand(TNormalNode(node2), unbind(config)) ) or exists(Node mid | - localFlowStepPlus(node1, mid, _, config, cc) and + localFlowStepPlus(node1, mid, _, _, config, cc) and additionalLocalFlowStep(mid, node2, config) and not mid instanceof CastNode and preservesValue = false and + t = getErasedNodeTypeBound(node2) and nodeCand(TNormalNode(node2), unbind(config)) ) ) @@ -1345,17 +1351,18 @@ private predicate localFlowStepPlus( */ pragma[nomagic] private predicate localFlowBigStep( - Node node1, Node node2, boolean preservesValue, Configuration config, LocalCallContext callContext + Node node1, Node node2, boolean preservesValue, DataFlowType t, Configuration config, + LocalCallContext callContext ) { - localFlowStepPlus(node1, node2, preservesValue, config, callContext) and + localFlowStepPlus(node1, node2, preservesValue, t, config, callContext) and localFlowExit(node2, config) } pragma[nomagic] private predicate localFlowBigStepExt( - NodeExt node1, NodeExt node2, boolean preservesValue, Configuration config + NodeExt node1, NodeExt node2, boolean preservesValue, AccessPathFrontNil apf, Configuration config ) { - localFlowBigStep(node1.getNode(), node2.getNode(), preservesValue, config, _) + localFlowBigStep(node1.getNode(), node2.getNode(), preservesValue, apf.getType(), config, _) } private newtype TAccessPathFront = @@ -1395,46 +1402,24 @@ private predicate flowCandFwd( else any() } -/** - * A node that requires an empty access path and should have its tracked type - * (re-)computed. This is either a source or a node reached through an - * additional step. - */ -private class AccessPathFrontNilNode extends NormalNodeExt { - AccessPathFrontNilNode() { - nodeCand(this, _) and - ( - any(Configuration c).isSource(this.getNode()) - or - localFlowBigStepExt(_, this, false, _) - or - additionalJumpStepExt(_, this, _) - ) - } - - /** Gets the `nil` path front for this node. */ - AccessPathFrontNil getApf() { result = TFrontNil(this.getErasedNodeTypeBound()) } -} - private predicate flowCandFwd0( NodeExt node, boolean fromArg, AccessPathFront apf, Configuration config ) { nodeCand2(node, _, false, config) and config.isSource(node.getNode()) and fromArg = false and - apf = node.(AccessPathFrontNilNode).getApf() + apf = TFrontNil(node.getErasedNodeTypeBound()) or nodeCand(node, unbind(config)) and ( exists(NodeExt mid | flowCandFwd(mid, fromArg, apf, config) and - localFlowBigStepExt(mid, node, true, config) + localFlowBigStepExt(mid, node, true, _, config) ) or exists(NodeExt mid, AccessPathFrontNil nil | flowCandFwd(mid, fromArg, nil, config) and - localFlowBigStepExt(mid, node, false, config) and - apf = node.(AccessPathFrontNilNode).getApf() + localFlowBigStepExt(mid, node, false, apf, config) ) or exists(NodeExt mid | @@ -1447,7 +1432,7 @@ private predicate flowCandFwd0( flowCandFwd(mid, _, nil, config) and additionalJumpStepExt(mid, node, config) and fromArg = false and - apf = node.(AccessPathFrontNilNode).getApf() + apf = TFrontNil(node.getErasedNodeTypeBound()) ) or exists(NodeExt mid, boolean allowsFieldFlow | @@ -1589,13 +1574,13 @@ private predicate flowCand0( apf instanceof AccessPathFrontNil or exists(NodeExt mid | - localFlowBigStepExt(node, mid, true, config) and + localFlowBigStepExt(node, mid, true, _, config) and flowCand(mid, toReturn, apf, config) ) or exists(NodeExt mid, AccessPathFrontNil nil | flowCandFwd(node, _, apf, config) and - localFlowBigStepExt(node, mid, false, config) and + localFlowBigStepExt(node, mid, false, _, config) and flowCand(mid, toReturn, nil, config) and apf instanceof AccessPathFrontNil ) @@ -1810,18 +1795,6 @@ private predicate popWithFront(AccessPath ap0, Content f, AccessPathFront apf, A /** Gets the access path obtained by pushing `f` onto `ap`. */ private AccessPath push(Content f, AccessPath ap) { ap = pop(f, result) } -/** - * A node that requires an empty access path and should have its tracked type - * (re-)computed. This is either a source or a node reached through an - * additional step. - */ -private class AccessPathNilNode extends NormalNodeExt { - AccessPathNilNode() { flowCand(this.(AccessPathFrontNilNode), _, _, _) } - - /** Gets the `nil` path for this node. */ - AccessPathNil getAp() { result = TNil(this.getErasedNodeTypeBound()) } -} - /** * Holds if data can flow from a source to `node` with the given `ap`. */ @@ -1838,20 +1811,19 @@ private predicate flowFwd0( flowCand(node, _, _, config) and config.isSource(node.getNode()) and fromArg = false and - ap = node.(AccessPathNilNode).getAp() and + ap = TNil(node.getErasedNodeTypeBound()) and apf = ap.(AccessPathNil).getFront() or flowCand(node, _, _, unbind(config)) and ( exists(NodeExt mid | flowFwd(mid, fromArg, apf, ap, config) and - localFlowBigStepExt(mid, node, true, config) + localFlowBigStepExt(mid, node, true, _, config) ) or exists(NodeExt mid, AccessPathNil nil | flowFwd(mid, fromArg, _, nil, config) and - localFlowBigStepExt(mid, node, false, config) and - ap = node.(AccessPathNilNode).getAp() and + localFlowBigStepExt(mid, node, false, apf, config) and apf = ap.(AccessPathNil).getFront() ) or @@ -1865,7 +1837,7 @@ private predicate flowFwd0( flowFwd(mid, _, _, nil, config) and additionalJumpStepExt(mid, node, config) and fromArg = false and - ap = node.(AccessPathNilNode).getAp() and + ap = TNil(node.getErasedNodeTypeBound()) and apf = ap.(AccessPathNil).getFront() ) or @@ -1982,13 +1954,13 @@ private predicate flow0(NodeExt node, boolean toReturn, AccessPath ap, Configura ap instanceof AccessPathNil or exists(NodeExt mid | - localFlowBigStepExt(node, mid, true, config) and + localFlowBigStepExt(node, mid, true, _, config) and flow(mid, toReturn, ap, config) ) or exists(NodeExt mid, AccessPathNil nil | flowFwd(node, _, _, ap, config) and - localFlowBigStepExt(node, mid, false, config) and + localFlowBigStepExt(node, mid, false, _, config) and flow(mid, toReturn, nil, config) and ap instanceof AccessPathNil ) @@ -2164,7 +2136,7 @@ private newtype TPathNode = config.isSource(node) and cc instanceof CallContextAny and sc instanceof SummaryCtxNone and - ap = any(AccessPathNilNode nil | nil.getNode() = node).getAp() + ap = TNil(getErasedNodeTypeBound(node)) or // ... or a step from an existing PathNode to another node. exists(PathNodeMid mid | @@ -2357,12 +2329,11 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt pathIntoLocalStep(mid, midnode, cc, enclosing, sc, ap0, conf) and localCC = getLocalCallContext(cc, enclosing) | - localFlowBigStep(midnode, node, true, conf, localCC) and + localFlowBigStep(midnode, node, true, _, conf, localCC) and ap = ap0 or - localFlowBigStep(midnode, node, false, conf, localCC) and - ap0 instanceof AccessPathNil and - ap = any(AccessPathNilNode nil | nil.getNode() = node).getAp() + localFlowBigStep(midnode, node, false, ap.(AccessPathNil).getType(), conf, localCC) and + ap0 instanceof AccessPathNil ) or jumpStep(mid.getNode(), node, mid.getConfiguration()) and @@ -2374,7 +2345,7 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt cc instanceof CallContextAny and sc instanceof SummaryCtxNone and mid.getAp() instanceof AccessPathNil and - ap = any(AccessPathNilNode nil | nil.getNode() = node).getAp() + ap = TNil(getErasedNodeTypeBound(node)) or exists(Content f, AccessPath ap0 | pathReadStep(mid, node, ap0, f, cc) and ap = pop(f, ap0)) and sc = mid.getSummaryCtx() @@ -2397,7 +2368,7 @@ private predicate pathIntoLocalStep( midnode = mid.getNode() and cc = mid.getCallContext() and conf = mid.getConfiguration() and - localFlowBigStep(midnode, _, _, conf, _) and + localFlowBigStep(midnode, _, _, _, conf, _) and enclosing = midnode.getEnclosingCallable() and sc = mid.getSummaryCtx() and ap0 = mid.getAp() @@ -2949,7 +2920,7 @@ private module FlowExploration { config = mid.getConfiguration() } - pragma[noinline] + pragma[nomagic] private predicate partialPathOutOfCallable1( PartialPathNodePriv mid, DataFlowCall call, ReturnKindExt kind, CallContext cc, PartialAccessPath ap, Configuration config 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 5eb98aefe4e..e3f1da355ef 100644 --- a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl2.qll +++ b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl2.qll @@ -1307,15 +1307,20 @@ private predicate localFlowExit(Node node, Configuration config) { */ pragma[nomagic] private predicate localFlowStepPlus( - Node node1, Node node2, boolean preservesValue, Configuration config, LocalCallContext cc + Node node1, Node node2, boolean preservesValue, DataFlowType t, Configuration config, + LocalCallContext cc ) { not isUnreachableInCall(node2, cc.(LocalCallContextSpecificCall).getCall()) and ( localFlowEntry(node1, config) and ( - localFlowStep(node1, node2, config) and preservesValue = true + localFlowStep(node1, node2, config) and + preservesValue = true and + t = getErasedNodeTypeBound(node1) or - additionalLocalFlowStep(node1, node2, config) and preservesValue = false + additionalLocalFlowStep(node1, node2, config) and + preservesValue = false and + t = getErasedNodeTypeBound(node2) ) and node1 != node2 and cc.relevantFor(node1.getEnclosingCallable()) and @@ -1323,17 +1328,18 @@ private predicate localFlowStepPlus( nodeCand(TNormalNode(node2), unbind(config)) or exists(Node mid | - localFlowStepPlus(node1, mid, preservesValue, config, cc) and + localFlowStepPlus(node1, mid, preservesValue, t, config, cc) and localFlowStep(mid, node2, config) and not mid instanceof CastNode and nodeCand(TNormalNode(node2), unbind(config)) ) or exists(Node mid | - localFlowStepPlus(node1, mid, _, config, cc) and + localFlowStepPlus(node1, mid, _, _, config, cc) and additionalLocalFlowStep(mid, node2, config) and not mid instanceof CastNode and preservesValue = false and + t = getErasedNodeTypeBound(node2) and nodeCand(TNormalNode(node2), unbind(config)) ) ) @@ -1345,17 +1351,18 @@ private predicate localFlowStepPlus( */ pragma[nomagic] private predicate localFlowBigStep( - Node node1, Node node2, boolean preservesValue, Configuration config, LocalCallContext callContext + Node node1, Node node2, boolean preservesValue, DataFlowType t, Configuration config, + LocalCallContext callContext ) { - localFlowStepPlus(node1, node2, preservesValue, config, callContext) and + localFlowStepPlus(node1, node2, preservesValue, t, config, callContext) and localFlowExit(node2, config) } pragma[nomagic] private predicate localFlowBigStepExt( - NodeExt node1, NodeExt node2, boolean preservesValue, Configuration config + NodeExt node1, NodeExt node2, boolean preservesValue, AccessPathFrontNil apf, Configuration config ) { - localFlowBigStep(node1.getNode(), node2.getNode(), preservesValue, config, _) + localFlowBigStep(node1.getNode(), node2.getNode(), preservesValue, apf.getType(), config, _) } private newtype TAccessPathFront = @@ -1395,46 +1402,24 @@ private predicate flowCandFwd( else any() } -/** - * A node that requires an empty access path and should have its tracked type - * (re-)computed. This is either a source or a node reached through an - * additional step. - */ -private class AccessPathFrontNilNode extends NormalNodeExt { - AccessPathFrontNilNode() { - nodeCand(this, _) and - ( - any(Configuration c).isSource(this.getNode()) - or - localFlowBigStepExt(_, this, false, _) - or - additionalJumpStepExt(_, this, _) - ) - } - - /** Gets the `nil` path front for this node. */ - AccessPathFrontNil getApf() { result = TFrontNil(this.getErasedNodeTypeBound()) } -} - private predicate flowCandFwd0( NodeExt node, boolean fromArg, AccessPathFront apf, Configuration config ) { nodeCand2(node, _, false, config) and config.isSource(node.getNode()) and fromArg = false and - apf = node.(AccessPathFrontNilNode).getApf() + apf = TFrontNil(node.getErasedNodeTypeBound()) or nodeCand(node, unbind(config)) and ( exists(NodeExt mid | flowCandFwd(mid, fromArg, apf, config) and - localFlowBigStepExt(mid, node, true, config) + localFlowBigStepExt(mid, node, true, _, config) ) or exists(NodeExt mid, AccessPathFrontNil nil | flowCandFwd(mid, fromArg, nil, config) and - localFlowBigStepExt(mid, node, false, config) and - apf = node.(AccessPathFrontNilNode).getApf() + localFlowBigStepExt(mid, node, false, apf, config) ) or exists(NodeExt mid | @@ -1447,7 +1432,7 @@ private predicate flowCandFwd0( flowCandFwd(mid, _, nil, config) and additionalJumpStepExt(mid, node, config) and fromArg = false and - apf = node.(AccessPathFrontNilNode).getApf() + apf = TFrontNil(node.getErasedNodeTypeBound()) ) or exists(NodeExt mid, boolean allowsFieldFlow | @@ -1589,13 +1574,13 @@ private predicate flowCand0( apf instanceof AccessPathFrontNil or exists(NodeExt mid | - localFlowBigStepExt(node, mid, true, config) and + localFlowBigStepExt(node, mid, true, _, config) and flowCand(mid, toReturn, apf, config) ) or exists(NodeExt mid, AccessPathFrontNil nil | flowCandFwd(node, _, apf, config) and - localFlowBigStepExt(node, mid, false, config) and + localFlowBigStepExt(node, mid, false, _, config) and flowCand(mid, toReturn, nil, config) and apf instanceof AccessPathFrontNil ) @@ -1810,18 +1795,6 @@ private predicate popWithFront(AccessPath ap0, Content f, AccessPathFront apf, A /** Gets the access path obtained by pushing `f` onto `ap`. */ private AccessPath push(Content f, AccessPath ap) { ap = pop(f, result) } -/** - * A node that requires an empty access path and should have its tracked type - * (re-)computed. This is either a source or a node reached through an - * additional step. - */ -private class AccessPathNilNode extends NormalNodeExt { - AccessPathNilNode() { flowCand(this.(AccessPathFrontNilNode), _, _, _) } - - /** Gets the `nil` path for this node. */ - AccessPathNil getAp() { result = TNil(this.getErasedNodeTypeBound()) } -} - /** * Holds if data can flow from a source to `node` with the given `ap`. */ @@ -1838,20 +1811,19 @@ private predicate flowFwd0( flowCand(node, _, _, config) and config.isSource(node.getNode()) and fromArg = false and - ap = node.(AccessPathNilNode).getAp() and + ap = TNil(node.getErasedNodeTypeBound()) and apf = ap.(AccessPathNil).getFront() or flowCand(node, _, _, unbind(config)) and ( exists(NodeExt mid | flowFwd(mid, fromArg, apf, ap, config) and - localFlowBigStepExt(mid, node, true, config) + localFlowBigStepExt(mid, node, true, _, config) ) or exists(NodeExt mid, AccessPathNil nil | flowFwd(mid, fromArg, _, nil, config) and - localFlowBigStepExt(mid, node, false, config) and - ap = node.(AccessPathNilNode).getAp() and + localFlowBigStepExt(mid, node, false, apf, config) and apf = ap.(AccessPathNil).getFront() ) or @@ -1865,7 +1837,7 @@ private predicate flowFwd0( flowFwd(mid, _, _, nil, config) and additionalJumpStepExt(mid, node, config) and fromArg = false and - ap = node.(AccessPathNilNode).getAp() and + ap = TNil(node.getErasedNodeTypeBound()) and apf = ap.(AccessPathNil).getFront() ) or @@ -1982,13 +1954,13 @@ private predicate flow0(NodeExt node, boolean toReturn, AccessPath ap, Configura ap instanceof AccessPathNil or exists(NodeExt mid | - localFlowBigStepExt(node, mid, true, config) and + localFlowBigStepExt(node, mid, true, _, config) and flow(mid, toReturn, ap, config) ) or exists(NodeExt mid, AccessPathNil nil | flowFwd(node, _, _, ap, config) and - localFlowBigStepExt(node, mid, false, config) and + localFlowBigStepExt(node, mid, false, _, config) and flow(mid, toReturn, nil, config) and ap instanceof AccessPathNil ) @@ -2164,7 +2136,7 @@ private newtype TPathNode = config.isSource(node) and cc instanceof CallContextAny and sc instanceof SummaryCtxNone and - ap = any(AccessPathNilNode nil | nil.getNode() = node).getAp() + ap = TNil(getErasedNodeTypeBound(node)) or // ... or a step from an existing PathNode to another node. exists(PathNodeMid mid | @@ -2357,12 +2329,11 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt pathIntoLocalStep(mid, midnode, cc, enclosing, sc, ap0, conf) and localCC = getLocalCallContext(cc, enclosing) | - localFlowBigStep(midnode, node, true, conf, localCC) and + localFlowBigStep(midnode, node, true, _, conf, localCC) and ap = ap0 or - localFlowBigStep(midnode, node, false, conf, localCC) and - ap0 instanceof AccessPathNil and - ap = any(AccessPathNilNode nil | nil.getNode() = node).getAp() + localFlowBigStep(midnode, node, false, ap.(AccessPathNil).getType(), conf, localCC) and + ap0 instanceof AccessPathNil ) or jumpStep(mid.getNode(), node, mid.getConfiguration()) and @@ -2374,7 +2345,7 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt cc instanceof CallContextAny and sc instanceof SummaryCtxNone and mid.getAp() instanceof AccessPathNil and - ap = any(AccessPathNilNode nil | nil.getNode() = node).getAp() + ap = TNil(getErasedNodeTypeBound(node)) or exists(Content f, AccessPath ap0 | pathReadStep(mid, node, ap0, f, cc) and ap = pop(f, ap0)) and sc = mid.getSummaryCtx() @@ -2397,7 +2368,7 @@ private predicate pathIntoLocalStep( midnode = mid.getNode() and cc = mid.getCallContext() and conf = mid.getConfiguration() and - localFlowBigStep(midnode, _, _, conf, _) and + localFlowBigStep(midnode, _, _, _, conf, _) and enclosing = midnode.getEnclosingCallable() and sc = mid.getSummaryCtx() and ap0 = mid.getAp() @@ -2949,7 +2920,7 @@ private module FlowExploration { config = mid.getConfiguration() } - pragma[noinline] + pragma[nomagic] private predicate partialPathOutOfCallable1( PartialPathNodePriv mid, DataFlowCall call, ReturnKindExt kind, CallContext cc, PartialAccessPath ap, Configuration config 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 5eb98aefe4e..e3f1da355ef 100644 --- a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl3.qll +++ b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl3.qll @@ -1307,15 +1307,20 @@ private predicate localFlowExit(Node node, Configuration config) { */ pragma[nomagic] private predicate localFlowStepPlus( - Node node1, Node node2, boolean preservesValue, Configuration config, LocalCallContext cc + Node node1, Node node2, boolean preservesValue, DataFlowType t, Configuration config, + LocalCallContext cc ) { not isUnreachableInCall(node2, cc.(LocalCallContextSpecificCall).getCall()) and ( localFlowEntry(node1, config) and ( - localFlowStep(node1, node2, config) and preservesValue = true + localFlowStep(node1, node2, config) and + preservesValue = true and + t = getErasedNodeTypeBound(node1) or - additionalLocalFlowStep(node1, node2, config) and preservesValue = false + additionalLocalFlowStep(node1, node2, config) and + preservesValue = false and + t = getErasedNodeTypeBound(node2) ) and node1 != node2 and cc.relevantFor(node1.getEnclosingCallable()) and @@ -1323,17 +1328,18 @@ private predicate localFlowStepPlus( nodeCand(TNormalNode(node2), unbind(config)) or exists(Node mid | - localFlowStepPlus(node1, mid, preservesValue, config, cc) and + localFlowStepPlus(node1, mid, preservesValue, t, config, cc) and localFlowStep(mid, node2, config) and not mid instanceof CastNode and nodeCand(TNormalNode(node2), unbind(config)) ) or exists(Node mid | - localFlowStepPlus(node1, mid, _, config, cc) and + localFlowStepPlus(node1, mid, _, _, config, cc) and additionalLocalFlowStep(mid, node2, config) and not mid instanceof CastNode and preservesValue = false and + t = getErasedNodeTypeBound(node2) and nodeCand(TNormalNode(node2), unbind(config)) ) ) @@ -1345,17 +1351,18 @@ private predicate localFlowStepPlus( */ pragma[nomagic] private predicate localFlowBigStep( - Node node1, Node node2, boolean preservesValue, Configuration config, LocalCallContext callContext + Node node1, Node node2, boolean preservesValue, DataFlowType t, Configuration config, + LocalCallContext callContext ) { - localFlowStepPlus(node1, node2, preservesValue, config, callContext) and + localFlowStepPlus(node1, node2, preservesValue, t, config, callContext) and localFlowExit(node2, config) } pragma[nomagic] private predicate localFlowBigStepExt( - NodeExt node1, NodeExt node2, boolean preservesValue, Configuration config + NodeExt node1, NodeExt node2, boolean preservesValue, AccessPathFrontNil apf, Configuration config ) { - localFlowBigStep(node1.getNode(), node2.getNode(), preservesValue, config, _) + localFlowBigStep(node1.getNode(), node2.getNode(), preservesValue, apf.getType(), config, _) } private newtype TAccessPathFront = @@ -1395,46 +1402,24 @@ private predicate flowCandFwd( else any() } -/** - * A node that requires an empty access path and should have its tracked type - * (re-)computed. This is either a source or a node reached through an - * additional step. - */ -private class AccessPathFrontNilNode extends NormalNodeExt { - AccessPathFrontNilNode() { - nodeCand(this, _) and - ( - any(Configuration c).isSource(this.getNode()) - or - localFlowBigStepExt(_, this, false, _) - or - additionalJumpStepExt(_, this, _) - ) - } - - /** Gets the `nil` path front for this node. */ - AccessPathFrontNil getApf() { result = TFrontNil(this.getErasedNodeTypeBound()) } -} - private predicate flowCandFwd0( NodeExt node, boolean fromArg, AccessPathFront apf, Configuration config ) { nodeCand2(node, _, false, config) and config.isSource(node.getNode()) and fromArg = false and - apf = node.(AccessPathFrontNilNode).getApf() + apf = TFrontNil(node.getErasedNodeTypeBound()) or nodeCand(node, unbind(config)) and ( exists(NodeExt mid | flowCandFwd(mid, fromArg, apf, config) and - localFlowBigStepExt(mid, node, true, config) + localFlowBigStepExt(mid, node, true, _, config) ) or exists(NodeExt mid, AccessPathFrontNil nil | flowCandFwd(mid, fromArg, nil, config) and - localFlowBigStepExt(mid, node, false, config) and - apf = node.(AccessPathFrontNilNode).getApf() + localFlowBigStepExt(mid, node, false, apf, config) ) or exists(NodeExt mid | @@ -1447,7 +1432,7 @@ private predicate flowCandFwd0( flowCandFwd(mid, _, nil, config) and additionalJumpStepExt(mid, node, config) and fromArg = false and - apf = node.(AccessPathFrontNilNode).getApf() + apf = TFrontNil(node.getErasedNodeTypeBound()) ) or exists(NodeExt mid, boolean allowsFieldFlow | @@ -1589,13 +1574,13 @@ private predicate flowCand0( apf instanceof AccessPathFrontNil or exists(NodeExt mid | - localFlowBigStepExt(node, mid, true, config) and + localFlowBigStepExt(node, mid, true, _, config) and flowCand(mid, toReturn, apf, config) ) or exists(NodeExt mid, AccessPathFrontNil nil | flowCandFwd(node, _, apf, config) and - localFlowBigStepExt(node, mid, false, config) and + localFlowBigStepExt(node, mid, false, _, config) and flowCand(mid, toReturn, nil, config) and apf instanceof AccessPathFrontNil ) @@ -1810,18 +1795,6 @@ private predicate popWithFront(AccessPath ap0, Content f, AccessPathFront apf, A /** Gets the access path obtained by pushing `f` onto `ap`. */ private AccessPath push(Content f, AccessPath ap) { ap = pop(f, result) } -/** - * A node that requires an empty access path and should have its tracked type - * (re-)computed. This is either a source or a node reached through an - * additional step. - */ -private class AccessPathNilNode extends NormalNodeExt { - AccessPathNilNode() { flowCand(this.(AccessPathFrontNilNode), _, _, _) } - - /** Gets the `nil` path for this node. */ - AccessPathNil getAp() { result = TNil(this.getErasedNodeTypeBound()) } -} - /** * Holds if data can flow from a source to `node` with the given `ap`. */ @@ -1838,20 +1811,19 @@ private predicate flowFwd0( flowCand(node, _, _, config) and config.isSource(node.getNode()) and fromArg = false and - ap = node.(AccessPathNilNode).getAp() and + ap = TNil(node.getErasedNodeTypeBound()) and apf = ap.(AccessPathNil).getFront() or flowCand(node, _, _, unbind(config)) and ( exists(NodeExt mid | flowFwd(mid, fromArg, apf, ap, config) and - localFlowBigStepExt(mid, node, true, config) + localFlowBigStepExt(mid, node, true, _, config) ) or exists(NodeExt mid, AccessPathNil nil | flowFwd(mid, fromArg, _, nil, config) and - localFlowBigStepExt(mid, node, false, config) and - ap = node.(AccessPathNilNode).getAp() and + localFlowBigStepExt(mid, node, false, apf, config) and apf = ap.(AccessPathNil).getFront() ) or @@ -1865,7 +1837,7 @@ private predicate flowFwd0( flowFwd(mid, _, _, nil, config) and additionalJumpStepExt(mid, node, config) and fromArg = false and - ap = node.(AccessPathNilNode).getAp() and + ap = TNil(node.getErasedNodeTypeBound()) and apf = ap.(AccessPathNil).getFront() ) or @@ -1982,13 +1954,13 @@ private predicate flow0(NodeExt node, boolean toReturn, AccessPath ap, Configura ap instanceof AccessPathNil or exists(NodeExt mid | - localFlowBigStepExt(node, mid, true, config) and + localFlowBigStepExt(node, mid, true, _, config) and flow(mid, toReturn, ap, config) ) or exists(NodeExt mid, AccessPathNil nil | flowFwd(node, _, _, ap, config) and - localFlowBigStepExt(node, mid, false, config) and + localFlowBigStepExt(node, mid, false, _, config) and flow(mid, toReturn, nil, config) and ap instanceof AccessPathNil ) @@ -2164,7 +2136,7 @@ private newtype TPathNode = config.isSource(node) and cc instanceof CallContextAny and sc instanceof SummaryCtxNone and - ap = any(AccessPathNilNode nil | nil.getNode() = node).getAp() + ap = TNil(getErasedNodeTypeBound(node)) or // ... or a step from an existing PathNode to another node. exists(PathNodeMid mid | @@ -2357,12 +2329,11 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt pathIntoLocalStep(mid, midnode, cc, enclosing, sc, ap0, conf) and localCC = getLocalCallContext(cc, enclosing) | - localFlowBigStep(midnode, node, true, conf, localCC) and + localFlowBigStep(midnode, node, true, _, conf, localCC) and ap = ap0 or - localFlowBigStep(midnode, node, false, conf, localCC) and - ap0 instanceof AccessPathNil and - ap = any(AccessPathNilNode nil | nil.getNode() = node).getAp() + localFlowBigStep(midnode, node, false, ap.(AccessPathNil).getType(), conf, localCC) and + ap0 instanceof AccessPathNil ) or jumpStep(mid.getNode(), node, mid.getConfiguration()) and @@ -2374,7 +2345,7 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt cc instanceof CallContextAny and sc instanceof SummaryCtxNone and mid.getAp() instanceof AccessPathNil and - ap = any(AccessPathNilNode nil | nil.getNode() = node).getAp() + ap = TNil(getErasedNodeTypeBound(node)) or exists(Content f, AccessPath ap0 | pathReadStep(mid, node, ap0, f, cc) and ap = pop(f, ap0)) and sc = mid.getSummaryCtx() @@ -2397,7 +2368,7 @@ private predicate pathIntoLocalStep( midnode = mid.getNode() and cc = mid.getCallContext() and conf = mid.getConfiguration() and - localFlowBigStep(midnode, _, _, conf, _) and + localFlowBigStep(midnode, _, _, _, conf, _) and enclosing = midnode.getEnclosingCallable() and sc = mid.getSummaryCtx() and ap0 = mid.getAp() @@ -2949,7 +2920,7 @@ private module FlowExploration { config = mid.getConfiguration() } - pragma[noinline] + pragma[nomagic] private predicate partialPathOutOfCallable1( PartialPathNodePriv mid, DataFlowCall call, ReturnKindExt kind, CallContext cc, PartialAccessPath ap, Configuration config 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 5eb98aefe4e..e3f1da355ef 100644 --- a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl4.qll +++ b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl4.qll @@ -1307,15 +1307,20 @@ private predicate localFlowExit(Node node, Configuration config) { */ pragma[nomagic] private predicate localFlowStepPlus( - Node node1, Node node2, boolean preservesValue, Configuration config, LocalCallContext cc + Node node1, Node node2, boolean preservesValue, DataFlowType t, Configuration config, + LocalCallContext cc ) { not isUnreachableInCall(node2, cc.(LocalCallContextSpecificCall).getCall()) and ( localFlowEntry(node1, config) and ( - localFlowStep(node1, node2, config) and preservesValue = true + localFlowStep(node1, node2, config) and + preservesValue = true and + t = getErasedNodeTypeBound(node1) or - additionalLocalFlowStep(node1, node2, config) and preservesValue = false + additionalLocalFlowStep(node1, node2, config) and + preservesValue = false and + t = getErasedNodeTypeBound(node2) ) and node1 != node2 and cc.relevantFor(node1.getEnclosingCallable()) and @@ -1323,17 +1328,18 @@ private predicate localFlowStepPlus( nodeCand(TNormalNode(node2), unbind(config)) or exists(Node mid | - localFlowStepPlus(node1, mid, preservesValue, config, cc) and + localFlowStepPlus(node1, mid, preservesValue, t, config, cc) and localFlowStep(mid, node2, config) and not mid instanceof CastNode and nodeCand(TNormalNode(node2), unbind(config)) ) or exists(Node mid | - localFlowStepPlus(node1, mid, _, config, cc) and + localFlowStepPlus(node1, mid, _, _, config, cc) and additionalLocalFlowStep(mid, node2, config) and not mid instanceof CastNode and preservesValue = false and + t = getErasedNodeTypeBound(node2) and nodeCand(TNormalNode(node2), unbind(config)) ) ) @@ -1345,17 +1351,18 @@ private predicate localFlowStepPlus( */ pragma[nomagic] private predicate localFlowBigStep( - Node node1, Node node2, boolean preservesValue, Configuration config, LocalCallContext callContext + Node node1, Node node2, boolean preservesValue, DataFlowType t, Configuration config, + LocalCallContext callContext ) { - localFlowStepPlus(node1, node2, preservesValue, config, callContext) and + localFlowStepPlus(node1, node2, preservesValue, t, config, callContext) and localFlowExit(node2, config) } pragma[nomagic] private predicate localFlowBigStepExt( - NodeExt node1, NodeExt node2, boolean preservesValue, Configuration config + NodeExt node1, NodeExt node2, boolean preservesValue, AccessPathFrontNil apf, Configuration config ) { - localFlowBigStep(node1.getNode(), node2.getNode(), preservesValue, config, _) + localFlowBigStep(node1.getNode(), node2.getNode(), preservesValue, apf.getType(), config, _) } private newtype TAccessPathFront = @@ -1395,46 +1402,24 @@ private predicate flowCandFwd( else any() } -/** - * A node that requires an empty access path and should have its tracked type - * (re-)computed. This is either a source or a node reached through an - * additional step. - */ -private class AccessPathFrontNilNode extends NormalNodeExt { - AccessPathFrontNilNode() { - nodeCand(this, _) and - ( - any(Configuration c).isSource(this.getNode()) - or - localFlowBigStepExt(_, this, false, _) - or - additionalJumpStepExt(_, this, _) - ) - } - - /** Gets the `nil` path front for this node. */ - AccessPathFrontNil getApf() { result = TFrontNil(this.getErasedNodeTypeBound()) } -} - private predicate flowCandFwd0( NodeExt node, boolean fromArg, AccessPathFront apf, Configuration config ) { nodeCand2(node, _, false, config) and config.isSource(node.getNode()) and fromArg = false and - apf = node.(AccessPathFrontNilNode).getApf() + apf = TFrontNil(node.getErasedNodeTypeBound()) or nodeCand(node, unbind(config)) and ( exists(NodeExt mid | flowCandFwd(mid, fromArg, apf, config) and - localFlowBigStepExt(mid, node, true, config) + localFlowBigStepExt(mid, node, true, _, config) ) or exists(NodeExt mid, AccessPathFrontNil nil | flowCandFwd(mid, fromArg, nil, config) and - localFlowBigStepExt(mid, node, false, config) and - apf = node.(AccessPathFrontNilNode).getApf() + localFlowBigStepExt(mid, node, false, apf, config) ) or exists(NodeExt mid | @@ -1447,7 +1432,7 @@ private predicate flowCandFwd0( flowCandFwd(mid, _, nil, config) and additionalJumpStepExt(mid, node, config) and fromArg = false and - apf = node.(AccessPathFrontNilNode).getApf() + apf = TFrontNil(node.getErasedNodeTypeBound()) ) or exists(NodeExt mid, boolean allowsFieldFlow | @@ -1589,13 +1574,13 @@ private predicate flowCand0( apf instanceof AccessPathFrontNil or exists(NodeExt mid | - localFlowBigStepExt(node, mid, true, config) and + localFlowBigStepExt(node, mid, true, _, config) and flowCand(mid, toReturn, apf, config) ) or exists(NodeExt mid, AccessPathFrontNil nil | flowCandFwd(node, _, apf, config) and - localFlowBigStepExt(node, mid, false, config) and + localFlowBigStepExt(node, mid, false, _, config) and flowCand(mid, toReturn, nil, config) and apf instanceof AccessPathFrontNil ) @@ -1810,18 +1795,6 @@ private predicate popWithFront(AccessPath ap0, Content f, AccessPathFront apf, A /** Gets the access path obtained by pushing `f` onto `ap`. */ private AccessPath push(Content f, AccessPath ap) { ap = pop(f, result) } -/** - * A node that requires an empty access path and should have its tracked type - * (re-)computed. This is either a source or a node reached through an - * additional step. - */ -private class AccessPathNilNode extends NormalNodeExt { - AccessPathNilNode() { flowCand(this.(AccessPathFrontNilNode), _, _, _) } - - /** Gets the `nil` path for this node. */ - AccessPathNil getAp() { result = TNil(this.getErasedNodeTypeBound()) } -} - /** * Holds if data can flow from a source to `node` with the given `ap`. */ @@ -1838,20 +1811,19 @@ private predicate flowFwd0( flowCand(node, _, _, config) and config.isSource(node.getNode()) and fromArg = false and - ap = node.(AccessPathNilNode).getAp() and + ap = TNil(node.getErasedNodeTypeBound()) and apf = ap.(AccessPathNil).getFront() or flowCand(node, _, _, unbind(config)) and ( exists(NodeExt mid | flowFwd(mid, fromArg, apf, ap, config) and - localFlowBigStepExt(mid, node, true, config) + localFlowBigStepExt(mid, node, true, _, config) ) or exists(NodeExt mid, AccessPathNil nil | flowFwd(mid, fromArg, _, nil, config) and - localFlowBigStepExt(mid, node, false, config) and - ap = node.(AccessPathNilNode).getAp() and + localFlowBigStepExt(mid, node, false, apf, config) and apf = ap.(AccessPathNil).getFront() ) or @@ -1865,7 +1837,7 @@ private predicate flowFwd0( flowFwd(mid, _, _, nil, config) and additionalJumpStepExt(mid, node, config) and fromArg = false and - ap = node.(AccessPathNilNode).getAp() and + ap = TNil(node.getErasedNodeTypeBound()) and apf = ap.(AccessPathNil).getFront() ) or @@ -1982,13 +1954,13 @@ private predicate flow0(NodeExt node, boolean toReturn, AccessPath ap, Configura ap instanceof AccessPathNil or exists(NodeExt mid | - localFlowBigStepExt(node, mid, true, config) and + localFlowBigStepExt(node, mid, true, _, config) and flow(mid, toReturn, ap, config) ) or exists(NodeExt mid, AccessPathNil nil | flowFwd(node, _, _, ap, config) and - localFlowBigStepExt(node, mid, false, config) and + localFlowBigStepExt(node, mid, false, _, config) and flow(mid, toReturn, nil, config) and ap instanceof AccessPathNil ) @@ -2164,7 +2136,7 @@ private newtype TPathNode = config.isSource(node) and cc instanceof CallContextAny and sc instanceof SummaryCtxNone and - ap = any(AccessPathNilNode nil | nil.getNode() = node).getAp() + ap = TNil(getErasedNodeTypeBound(node)) or // ... or a step from an existing PathNode to another node. exists(PathNodeMid mid | @@ -2357,12 +2329,11 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt pathIntoLocalStep(mid, midnode, cc, enclosing, sc, ap0, conf) and localCC = getLocalCallContext(cc, enclosing) | - localFlowBigStep(midnode, node, true, conf, localCC) and + localFlowBigStep(midnode, node, true, _, conf, localCC) and ap = ap0 or - localFlowBigStep(midnode, node, false, conf, localCC) and - ap0 instanceof AccessPathNil and - ap = any(AccessPathNilNode nil | nil.getNode() = node).getAp() + localFlowBigStep(midnode, node, false, ap.(AccessPathNil).getType(), conf, localCC) and + ap0 instanceof AccessPathNil ) or jumpStep(mid.getNode(), node, mid.getConfiguration()) and @@ -2374,7 +2345,7 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt cc instanceof CallContextAny and sc instanceof SummaryCtxNone and mid.getAp() instanceof AccessPathNil and - ap = any(AccessPathNilNode nil | nil.getNode() = node).getAp() + ap = TNil(getErasedNodeTypeBound(node)) or exists(Content f, AccessPath ap0 | pathReadStep(mid, node, ap0, f, cc) and ap = pop(f, ap0)) and sc = mid.getSummaryCtx() @@ -2397,7 +2368,7 @@ private predicate pathIntoLocalStep( midnode = mid.getNode() and cc = mid.getCallContext() and conf = mid.getConfiguration() and - localFlowBigStep(midnode, _, _, conf, _) and + localFlowBigStep(midnode, _, _, _, conf, _) and enclosing = midnode.getEnclosingCallable() and sc = mid.getSummaryCtx() and ap0 = mid.getAp() @@ -2949,7 +2920,7 @@ private module FlowExploration { config = mid.getConfiguration() } - pragma[noinline] + pragma[nomagic] private predicate partialPathOutOfCallable1( PartialPathNodePriv mid, DataFlowCall call, ReturnKindExt kind, CallContext cc, PartialAccessPath ap, Configuration config 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 5eb98aefe4e..e3f1da355ef 100644 --- a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl5.qll +++ b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl5.qll @@ -1307,15 +1307,20 @@ private predicate localFlowExit(Node node, Configuration config) { */ pragma[nomagic] private predicate localFlowStepPlus( - Node node1, Node node2, boolean preservesValue, Configuration config, LocalCallContext cc + Node node1, Node node2, boolean preservesValue, DataFlowType t, Configuration config, + LocalCallContext cc ) { not isUnreachableInCall(node2, cc.(LocalCallContextSpecificCall).getCall()) and ( localFlowEntry(node1, config) and ( - localFlowStep(node1, node2, config) and preservesValue = true + localFlowStep(node1, node2, config) and + preservesValue = true and + t = getErasedNodeTypeBound(node1) or - additionalLocalFlowStep(node1, node2, config) and preservesValue = false + additionalLocalFlowStep(node1, node2, config) and + preservesValue = false and + t = getErasedNodeTypeBound(node2) ) and node1 != node2 and cc.relevantFor(node1.getEnclosingCallable()) and @@ -1323,17 +1328,18 @@ private predicate localFlowStepPlus( nodeCand(TNormalNode(node2), unbind(config)) or exists(Node mid | - localFlowStepPlus(node1, mid, preservesValue, config, cc) and + localFlowStepPlus(node1, mid, preservesValue, t, config, cc) and localFlowStep(mid, node2, config) and not mid instanceof CastNode and nodeCand(TNormalNode(node2), unbind(config)) ) or exists(Node mid | - localFlowStepPlus(node1, mid, _, config, cc) and + localFlowStepPlus(node1, mid, _, _, config, cc) and additionalLocalFlowStep(mid, node2, config) and not mid instanceof CastNode and preservesValue = false and + t = getErasedNodeTypeBound(node2) and nodeCand(TNormalNode(node2), unbind(config)) ) ) @@ -1345,17 +1351,18 @@ private predicate localFlowStepPlus( */ pragma[nomagic] private predicate localFlowBigStep( - Node node1, Node node2, boolean preservesValue, Configuration config, LocalCallContext callContext + Node node1, Node node2, boolean preservesValue, DataFlowType t, Configuration config, + LocalCallContext callContext ) { - localFlowStepPlus(node1, node2, preservesValue, config, callContext) and + localFlowStepPlus(node1, node2, preservesValue, t, config, callContext) and localFlowExit(node2, config) } pragma[nomagic] private predicate localFlowBigStepExt( - NodeExt node1, NodeExt node2, boolean preservesValue, Configuration config + NodeExt node1, NodeExt node2, boolean preservesValue, AccessPathFrontNil apf, Configuration config ) { - localFlowBigStep(node1.getNode(), node2.getNode(), preservesValue, config, _) + localFlowBigStep(node1.getNode(), node2.getNode(), preservesValue, apf.getType(), config, _) } private newtype TAccessPathFront = @@ -1395,46 +1402,24 @@ private predicate flowCandFwd( else any() } -/** - * A node that requires an empty access path and should have its tracked type - * (re-)computed. This is either a source or a node reached through an - * additional step. - */ -private class AccessPathFrontNilNode extends NormalNodeExt { - AccessPathFrontNilNode() { - nodeCand(this, _) and - ( - any(Configuration c).isSource(this.getNode()) - or - localFlowBigStepExt(_, this, false, _) - or - additionalJumpStepExt(_, this, _) - ) - } - - /** Gets the `nil` path front for this node. */ - AccessPathFrontNil getApf() { result = TFrontNil(this.getErasedNodeTypeBound()) } -} - private predicate flowCandFwd0( NodeExt node, boolean fromArg, AccessPathFront apf, Configuration config ) { nodeCand2(node, _, false, config) and config.isSource(node.getNode()) and fromArg = false and - apf = node.(AccessPathFrontNilNode).getApf() + apf = TFrontNil(node.getErasedNodeTypeBound()) or nodeCand(node, unbind(config)) and ( exists(NodeExt mid | flowCandFwd(mid, fromArg, apf, config) and - localFlowBigStepExt(mid, node, true, config) + localFlowBigStepExt(mid, node, true, _, config) ) or exists(NodeExt mid, AccessPathFrontNil nil | flowCandFwd(mid, fromArg, nil, config) and - localFlowBigStepExt(mid, node, false, config) and - apf = node.(AccessPathFrontNilNode).getApf() + localFlowBigStepExt(mid, node, false, apf, config) ) or exists(NodeExt mid | @@ -1447,7 +1432,7 @@ private predicate flowCandFwd0( flowCandFwd(mid, _, nil, config) and additionalJumpStepExt(mid, node, config) and fromArg = false and - apf = node.(AccessPathFrontNilNode).getApf() + apf = TFrontNil(node.getErasedNodeTypeBound()) ) or exists(NodeExt mid, boolean allowsFieldFlow | @@ -1589,13 +1574,13 @@ private predicate flowCand0( apf instanceof AccessPathFrontNil or exists(NodeExt mid | - localFlowBigStepExt(node, mid, true, config) and + localFlowBigStepExt(node, mid, true, _, config) and flowCand(mid, toReturn, apf, config) ) or exists(NodeExt mid, AccessPathFrontNil nil | flowCandFwd(node, _, apf, config) and - localFlowBigStepExt(node, mid, false, config) and + localFlowBigStepExt(node, mid, false, _, config) and flowCand(mid, toReturn, nil, config) and apf instanceof AccessPathFrontNil ) @@ -1810,18 +1795,6 @@ private predicate popWithFront(AccessPath ap0, Content f, AccessPathFront apf, A /** Gets the access path obtained by pushing `f` onto `ap`. */ private AccessPath push(Content f, AccessPath ap) { ap = pop(f, result) } -/** - * A node that requires an empty access path and should have its tracked type - * (re-)computed. This is either a source or a node reached through an - * additional step. - */ -private class AccessPathNilNode extends NormalNodeExt { - AccessPathNilNode() { flowCand(this.(AccessPathFrontNilNode), _, _, _) } - - /** Gets the `nil` path for this node. */ - AccessPathNil getAp() { result = TNil(this.getErasedNodeTypeBound()) } -} - /** * Holds if data can flow from a source to `node` with the given `ap`. */ @@ -1838,20 +1811,19 @@ private predicate flowFwd0( flowCand(node, _, _, config) and config.isSource(node.getNode()) and fromArg = false and - ap = node.(AccessPathNilNode).getAp() and + ap = TNil(node.getErasedNodeTypeBound()) and apf = ap.(AccessPathNil).getFront() or flowCand(node, _, _, unbind(config)) and ( exists(NodeExt mid | flowFwd(mid, fromArg, apf, ap, config) and - localFlowBigStepExt(mid, node, true, config) + localFlowBigStepExt(mid, node, true, _, config) ) or exists(NodeExt mid, AccessPathNil nil | flowFwd(mid, fromArg, _, nil, config) and - localFlowBigStepExt(mid, node, false, config) and - ap = node.(AccessPathNilNode).getAp() and + localFlowBigStepExt(mid, node, false, apf, config) and apf = ap.(AccessPathNil).getFront() ) or @@ -1865,7 +1837,7 @@ private predicate flowFwd0( flowFwd(mid, _, _, nil, config) and additionalJumpStepExt(mid, node, config) and fromArg = false and - ap = node.(AccessPathNilNode).getAp() and + ap = TNil(node.getErasedNodeTypeBound()) and apf = ap.(AccessPathNil).getFront() ) or @@ -1982,13 +1954,13 @@ private predicate flow0(NodeExt node, boolean toReturn, AccessPath ap, Configura ap instanceof AccessPathNil or exists(NodeExt mid | - localFlowBigStepExt(node, mid, true, config) and + localFlowBigStepExt(node, mid, true, _, config) and flow(mid, toReturn, ap, config) ) or exists(NodeExt mid, AccessPathNil nil | flowFwd(node, _, _, ap, config) and - localFlowBigStepExt(node, mid, false, config) and + localFlowBigStepExt(node, mid, false, _, config) and flow(mid, toReturn, nil, config) and ap instanceof AccessPathNil ) @@ -2164,7 +2136,7 @@ private newtype TPathNode = config.isSource(node) and cc instanceof CallContextAny and sc instanceof SummaryCtxNone and - ap = any(AccessPathNilNode nil | nil.getNode() = node).getAp() + ap = TNil(getErasedNodeTypeBound(node)) or // ... or a step from an existing PathNode to another node. exists(PathNodeMid mid | @@ -2357,12 +2329,11 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt pathIntoLocalStep(mid, midnode, cc, enclosing, sc, ap0, conf) and localCC = getLocalCallContext(cc, enclosing) | - localFlowBigStep(midnode, node, true, conf, localCC) and + localFlowBigStep(midnode, node, true, _, conf, localCC) and ap = ap0 or - localFlowBigStep(midnode, node, false, conf, localCC) and - ap0 instanceof AccessPathNil and - ap = any(AccessPathNilNode nil | nil.getNode() = node).getAp() + localFlowBigStep(midnode, node, false, ap.(AccessPathNil).getType(), conf, localCC) and + ap0 instanceof AccessPathNil ) or jumpStep(mid.getNode(), node, mid.getConfiguration()) and @@ -2374,7 +2345,7 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt cc instanceof CallContextAny and sc instanceof SummaryCtxNone and mid.getAp() instanceof AccessPathNil and - ap = any(AccessPathNilNode nil | nil.getNode() = node).getAp() + ap = TNil(getErasedNodeTypeBound(node)) or exists(Content f, AccessPath ap0 | pathReadStep(mid, node, ap0, f, cc) and ap = pop(f, ap0)) and sc = mid.getSummaryCtx() @@ -2397,7 +2368,7 @@ private predicate pathIntoLocalStep( midnode = mid.getNode() and cc = mid.getCallContext() and conf = mid.getConfiguration() and - localFlowBigStep(midnode, _, _, conf, _) and + localFlowBigStep(midnode, _, _, _, conf, _) and enclosing = midnode.getEnclosingCallable() and sc = mid.getSummaryCtx() and ap0 = mid.getAp() @@ -2949,7 +2920,7 @@ private module FlowExploration { config = mid.getConfiguration() } - pragma[noinline] + pragma[nomagic] private predicate partialPathOutOfCallable1( PartialPathNodePriv mid, DataFlowCall call, ReturnKindExt kind, CallContext cc, PartialAccessPath ap, Configuration config diff --git a/java/ql/src/semmle/code/java/dispatch/ObjFlow.qll b/java/ql/src/semmle/code/java/dispatch/ObjFlow.qll index 4d97bfdc3cc..11537ac144b 100644 --- a/java/ql/src/semmle/code/java/dispatch/ObjFlow.qll +++ b/java/ql/src/semmle/code/java/dispatch/ObjFlow.qll @@ -305,6 +305,7 @@ private module Unification { arg2 = t2.getTypeArgument(pos) } + pragma[nomagic] predicate failsUnification(Type t1, Type t2) { unificationTargets(t1, t2) and ( diff --git a/java/ql/test/experimental/README.md b/java/ql/test/experimental/README.md new file mode 100644 index 00000000000..2a9db304b9f --- /dev/null +++ b/java/ql/test/experimental/README.md @@ -0,0 +1 @@ +This directory contains tests for [experimental](../../../../docs/experimental.md) CodeQL queries and libraries. diff --git a/java/ql/test/library-tests/dataflow/taintgettersetter/A.java b/java/ql/test/library-tests/dataflow/taintgettersetter/A.java index 7128072977a..b92128981c8 100644 --- a/java/ql/test/library-tests/dataflow/taintgettersetter/A.java +++ b/java/ql/test/library-tests/dataflow/taintgettersetter/A.java @@ -34,4 +34,38 @@ public class A { Box b4 = Box.mk(taint()); sink(b4.getS1()); } + + static class Box2 { + String s; + String getS() { return s; } + void setS(String s) { this.s = s; } + + Box2(String s) { + setS(s + "1"); + } + String getS1() { return getS() + "2"; } + String getS2() { return step(getS() + "_") + "2"; } + void setS1(String s) { setS("3" + s); } + void setS2(String s) { setS("3" + step("_" + s)); } + static Box2 mk(String s) { + Box2 b = new Box2(""); + b.setS(step(s)); + return b; + } + } + + void foo2(Box2 b1, Box2 b2) { + b1.setS1(taint()); + sink(b1.getS1()); + + b2.setS2(taint()); + sink(b2.getS2()); + + String t3 = taint(); + Box2 b3 = new Box2(step(t3)); + sink(b3.s); + + Box2 b4 = Box2.mk(taint()); + sink(b4.getS1()); + } } diff --git a/java/ql/test/library-tests/dataflow/taintgettersetter/taintgettersetter.expected b/java/ql/test/library-tests/dataflow/taintgettersetter/taintgettersetter.expected index 6eb9d3aa505..0617ef3ede7 100644 --- a/java/ql/test/library-tests/dataflow/taintgettersetter/taintgettersetter.expected +++ b/java/ql/test/library-tests/dataflow/taintgettersetter/taintgettersetter.expected @@ -2,3 +2,7 @@ | A.java:27:14:27:20 | taint(...) | A.java:28:10:28:19 | getS2(...) | | A.java:30:17:30:23 | taint(...) | A.java:32:10:32:13 | b3.s | | A.java:34:21:34:27 | taint(...) | A.java:35:10:35:19 | getS1(...) | +| A.java:58:14:58:20 | taint(...) | A.java:59:10:59:19 | getS1(...) | +| A.java:61:14:61:20 | taint(...) | A.java:62:10:62:19 | getS2(...) | +| A.java:64:17:64:23 | taint(...) | A.java:66:10:66:13 | b3.s | +| A.java:68:23:68:29 | taint(...) | A.java:69:10:69:19 | getS1(...) | diff --git a/java/ql/test/library-tests/dataflow/typepruning/A.java b/java/ql/test/library-tests/dataflow/typepruning/A.java new file mode 100644 index 00000000000..656a602ba54 --- /dev/null +++ b/java/ql/test/library-tests/dataflow/typepruning/A.java @@ -0,0 +1,18 @@ +public class A { + Object customStep(Object o) { return null; } + + Object source() { return null; } + + void sink(Object o) { } + + Object through1(Object o) { + Object o2 = customStep(o); + String s = (String)o2; + return s; + } + + void foo() { + Object x = through1(source()); + sink(x); + } +} diff --git a/java/ql/test/library-tests/dataflow/typepruning/test.expected b/java/ql/test/library-tests/dataflow/typepruning/test.expected new file mode 100644 index 00000000000..4c92d2af6f0 --- /dev/null +++ b/java/ql/test/library-tests/dataflow/typepruning/test.expected @@ -0,0 +1 @@ +| A.java:15:25:15:32 | source(...) | A.java:16:10:16:10 | x | A.java:14:8:14:10 | foo | diff --git a/java/ql/test/library-tests/dataflow/typepruning/test.ql b/java/ql/test/library-tests/dataflow/typepruning/test.ql new file mode 100644 index 00000000000..c86476afeed --- /dev/null +++ b/java/ql/test/library-tests/dataflow/typepruning/test.ql @@ -0,0 +1,27 @@ +import java +import semmle.code.java.dataflow.DataFlow +import DataFlow + +class Conf extends Configuration { + Conf() { this = "test types" } + + override predicate isSource(Node n) { n.asExpr().(MethodAccess).getMethod().hasName("source") } + + override predicate isSink(Node n) { + exists(MethodAccess sink | + sink.getAnArgument() = n.asExpr() and sink.getMethod().hasName("sink") + ) + } + + override predicate isAdditionalFlowStep(Node n1, Node n2) { + exists(MethodAccess ma | + ma.getMethod().hasName("customStep") and + ma.getAnArgument() = n1.asExpr() and + ma = n2.asExpr() + ) + } +} + +from Node src, Node sink, Conf conf +where conf.hasFlow(src, sink) +select src, sink, sink.getEnclosingCallable() diff --git a/java/ql/test/library-tests/typeflow/typeflow.expected b/java/ql/test/library-tests/typeflow/typeflow.expected index 0a8947fc39d..a9bf42bc5ad 100644 --- a/java/ql/test/library-tests/typeflow/typeflow.expected +++ b/java/ql/test/library-tests/typeflow/typeflow.expected @@ -1,9 +1,7 @@ | A.java:9:10:9:11 | f1 | ArrayList | true | -| A.java:10:10:10:10 | l | List | false | | A.java:11:19:11:20 | f2 | List | false | | A.java:12:16:12:16 | x | List | false | | A.java:14:9:14:9 | y | A | false | -| A.java:18:11:18:12 | l2 | List | false | | A.java:19:9:19:9 | y | List | false | | A.java:26:14:26:14 | o | List | false | | A.java:34:11:34:11 | x | Integer | false | diff --git a/java/ql/test/query-tests/Nullness/C.java b/java/ql/test/query-tests/Nullness/C.java index 78df15756c4..48d7799c9a1 100644 --- a/java/ql/test/query-tests/Nullness/C.java +++ b/java/ql/test/query-tests/Nullness/C.java @@ -208,4 +208,16 @@ public class C { } } } + + public void ex15(Object o1, Object o2) { + if (o1 == null && o2 != null) { + return; + } + if (o1 == o2) { + return; + } + if (o1.equals(o2)) { // NPE - false positive + return; + } + } } diff --git a/java/ql/test/query-tests/Nullness/NullMaybe.expected b/java/ql/test/query-tests/Nullness/NullMaybe.expected index be68a274c25..2ddb51dfe4c 100644 --- a/java/ql/test/query-tests/Nullness/NullMaybe.expected +++ b/java/ql/test/query-tests/Nullness/NullMaybe.expected @@ -30,5 +30,6 @@ | C.java:144:15:144:15 | a | Variable $@ may be null here as suggested by $@ null guard. | C.java:141:20:141:26 | a | a | C.java:142:13:142:21 | ... == ... | this | | C.java:188:9:188:11 | obj | Variable $@ may be null here because of $@ assignment. | C.java:181:5:181:22 | Object obj | obj | C.java:181:12:181:21 | obj | this | | C.java:207:9:207:11 | obj | Variable $@ may be null here because of $@ assignment. | C.java:201:5:201:22 | Object obj | obj | C.java:201:12:201:21 | obj | this | +| C.java:219:9:219:10 | o1 | Variable $@ may be null here as suggested by $@ null guard. | C.java:212:20:212:28 | o1 | o1 | C.java:213:9:213:18 | ... == ... | this | | F.java:11:5:11:7 | obj | Variable $@ may be null here as suggested by $@ null guard. | F.java:8:18:8:27 | obj | obj | F.java:9:9:9:19 | ... == ... | this | | F.java:17:5:17:7 | obj | Variable $@ may be null here as suggested by $@ null guard. | F.java:14:18:14:27 | obj | obj | F.java:15:9:15:19 | ... == ... | this | diff --git a/javascript/config/suites/javascript/flow-summaries b/javascript/config/suites/javascript/flow-summaries index 21573fd9bf3..481c69829ad 100644 --- a/javascript/config/suites/javascript/flow-summaries +++ b/javascript/config/suites/javascript/flow-summaries @@ -1,2 +1,2 @@ -+ semmlecode-javascript-queries/Security/Summaries/ExtractSourceSummaries.ql -+ semmlecode-javascript-queries/Security/Summaries/ExtractSinkSummaries.ql ++ semmlecode-javascript-queries/experimental/Summaries/ExtractSourceSummaries.ql ++ semmlecode-javascript-queries/experimental/Summaries/ExtractSinkSummaries.ql diff --git a/javascript/documentation/flow-summaries.rst b/javascript/documentation/flow-summaries.rst index a1e97cb7815..edd6cb027b0 100644 --- a/javascript/documentation/flow-summaries.rst +++ b/javascript/documentation/flow-summaries.rst @@ -14,6 +14,10 @@ added as external data), and are picked up by the standard security queries, all about flow into, out of and through the npm packages as though they had been included as part of the build. +Note that flow summaries are an experimental technology, and not ready to be used in production +queries or libraries. Also note that flow summaries do not currently work with CodeQL, but require +the legacy Semmle Core toolchain. + Motivating example ------------------ @@ -87,11 +91,11 @@ package. (Note that this requires a working installation of Semmle Core.) There are three default summary extraction queries: - Extract flow step summaries (``js/step-summary-extraction``, - ``Security/Summaries/ExtractSourceSummaries.ql``) + ``experimental/Summaries/ExtractSourceSummaries.ql``) - Extract sink summaries (``js/sink-summary-extraction``, - ``Security/Summaries/ExtractSinkSummaries.ql``) + ``experimental/Summaries/ExtractSinkSummaries.ql``) - Extract source summaries (``js/source-summary-extraction``, - ``Security/Summaries/ExtractSourceSummaries.ql``) + ``experimental/Summaries/ExtractSourceSummaries.ql``) You can run these queries individually against a snapshot of the npm package you want to create flow summaries for using ``odasa runQuery``, and store the output as CSV files named @@ -103,7 +107,7 @@ project, we can extract sink summaries using the command .. code-block:: bash odasa runQuery \ - --query $SEMMLE_DIST/queries/semmlecode-javascript-queries/Security/Summaries/ExtractSinkSummaries.ql \ + --query $SEMMLE_DIST/queries/semmlecode-javascript-queries/experimental/Summaries/ExtractSinkSummaries.ql \ --output-file additional-sinks.csv --snapshot mkdirp-snapshot diff --git a/javascript/extractor/lib/typescript/src/main.ts b/javascript/extractor/lib/typescript/src/main.ts index 188544a3792..3aa1d6f0f4e 100644 --- a/javascript/extractor/lib/typescript/src/main.ts +++ b/javascript/extractor/lib/typescript/src/main.ts @@ -86,6 +86,8 @@ class State { } let state = new State(); +const reloadMemoryThresholdMb = getEnvironmentVariable("SEMMLE_TYPESCRIPT_MEMORY_THRESHOLD", Number, 1000); + /** * Debugging method for finding cycles in the TypeScript AST. Should not be used in production. * @@ -161,6 +163,7 @@ function extractFile(filename: string): string { function prepareNextFile() { if (state.pendingResponse != null) return; if (state.pendingFileIndex < state.pendingFiles.length) { + checkMemoryUsage(); let nextFilename = state.pendingFiles[state.pendingFileIndex]; state.pendingResponse = extractFile(nextFilename); } @@ -529,26 +532,40 @@ function getEnvironmentVariable(name: string, parse: (x: string) => T, defaul return value != null ? parse(value) : defaultValue; } +/** + * Whether the memory usage was last observed to be above the threshold for restarting the TypeScript compiler. + * + * This is to prevent repeatedly restarting the compiler if the GC does not immediately bring us below the + * threshold again. + */ +let hasReloadedSinceExceedingThreshold = false; + +/** + * If memory usage has moved above a the threshold, reboot the TypeScript compiler instance. + * + * Make sure to call this only when stdout has been flushed. + */ +function checkMemoryUsage() { + let bytesUsed = process.memoryUsage().heapUsed; + let megabytesUsed = bytesUsed / 1000000; + if (!hasReloadedSinceExceedingThreshold && megabytesUsed > reloadMemoryThresholdMb && state.project != null) { + console.warn('Restarting TypeScript compiler due to memory usage'); + state.project.reload(); + hasReloadedSinceExceedingThreshold = true; + } + else if (hasReloadedSinceExceedingThreshold && megabytesUsed < reloadMemoryThresholdMb) { + hasReloadedSinceExceedingThreshold = false; + } +} + function runReadLineInterface() { reset(); - let reloadMemoryThresholdMb = getEnvironmentVariable("SEMMLE_TYPESCRIPT_MEMORY_THRESHOLD", Number, 1000); - let isAboveReloadThreshold = false; let rl = readline.createInterface({ input: process.stdin, output: process.stdout }); rl.on("line", (line: string) => { let req: Command = JSON.parse(line); switch (req.command) { case "parse": handleParseCommand(req); - // If memory usage has moved above the threshold, reboot the TypeScript compiler instance. - let bytesUsed = process.memoryUsage().heapUsed; - let megabytesUsed = bytesUsed / 1000000; - if (!isAboveReloadThreshold && megabytesUsed > reloadMemoryThresholdMb && state.project != null) { - console.warn('Restarting TypeScript compiler due to memory usage'); - state.project.reload(); - isAboveReloadThreshold = true; - } else if (isAboveReloadThreshold && megabytesUsed < reloadMemoryThresholdMb) { - isAboveReloadThreshold = false; - } break; case "open-project": handleOpenProjectCommand(req); diff --git a/javascript/extractor/src/com/semmle/js/parser/TypeScriptParser.java b/javascript/extractor/src/com/semmle/js/parser/TypeScriptParser.java index 0f43a2da0b4..dfef971b76e 100644 --- a/javascript/extractor/src/com/semmle/js/parser/TypeScriptParser.java +++ b/javascript/extractor/src/com/semmle/js/parser/TypeScriptParser.java @@ -17,6 +17,7 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Map; +import java.util.concurrent.TimeUnit; import com.google.gson.JsonArray; import com.google.gson.JsonElement; @@ -37,7 +38,6 @@ import com.semmle.util.exception.Exceptions; import com.semmle.util.exception.InterruptedError; import com.semmle.util.exception.ResourceError; import com.semmle.util.exception.UserError; -import com.semmle.util.io.WholeIO; import com.semmle.util.logging.LogbackUtils; import com.semmle.util.process.AbstractProcessBuilder; import com.semmle.util.process.Builder; @@ -114,6 +114,18 @@ public class TypeScriptParser { */ public static final String TYPESCRIPT_NODE_FLAGS = "SEMMLE_TYPESCRIPT_NODE_FLAGS"; + /** + * Exit code for Node.js in case of a fatal error from V8. This exit code sometimes occurs + * when the process runs out of memory. + */ + private static final int NODEJS_EXIT_CODE_FATAL_ERROR = 5; + + /** + * Exit code for Node.js in case it exits due to SIGABRT. This exit code sometimes occurs + * when the process runs out of memory. + */ + private static final int NODEJS_EXIT_CODE_SIG_ABORT = 128 + 6; + /** The Node.js parser wrapper process, if it has been started already. */ private Process parserWrapperProcess; @@ -250,7 +262,7 @@ public class TypeScriptParser { int mainMemoryMb = typescriptRam != 0 ? typescriptRam - : getMegabyteCountFromPrefixedEnv(TYPESCRIPT_RAM_SUFFIX, 1000); + : getMegabyteCountFromPrefixedEnv(TYPESCRIPT_RAM_SUFFIX, 2000); int reserveMemoryMb = getMegabyteCountFromPrefixedEnv(TYPESCRIPT_RAM_RESERVE_SUFFIX, 400); File parserWrapper = getParserWrapper(); @@ -318,15 +330,7 @@ public class TypeScriptParser { if (parserWrapperProcess == null) setupParserWrapper(); if (!parserWrapperProcess.isAlive()) { - int exitCode = 0; - try { - exitCode = parserWrapperProcess.waitFor(); - } catch (InterruptedException e) { - Exceptions.ignore(e, "This is for diagnostic purposes only."); - } - String err = new WholeIO().strictReadString(parserWrapperProcess.getErrorStream()); - throw new CatastrophicError( - "TypeScript parser wrapper terminated with exit code " + exitCode + "; stderr: " + err); + throw getExceptionFromMalformedResponse(null, null); } String response = null; @@ -335,13 +339,14 @@ public class TypeScriptParser { toParserWrapper.newLine(); toParserWrapper.flush(); response = fromParserWrapper.readLine(); - if (response == null) - throw new CatastrophicError( - "Could not communicate with TypeScript parser wrapper " - + "(command: " - + parserWrapperCommand - + ")."); - return new JsonParser().parse(response).getAsJsonObject(); + if (response == null || response.isEmpty()) { + throw getExceptionFromMalformedResponse(response, null); + } + try { + return new JsonParser().parse(response).getAsJsonObject(); + } catch (JsonParseException | IllegalStateException e) { + throw getExceptionFromMalformedResponse(response, e); + } } catch (IOException e) { throw new CatastrophicError( "Could not communicate with TypeScript parser wrapper " @@ -349,17 +354,37 @@ public class TypeScriptParser { + parserWrapperCommand + ").", e); - } catch (JsonParseException | IllegalStateException e) { - throw new CatastrophicError( - "TypeScript parser wrapper sent unexpected response: " - + response - + " (command: " - + parserWrapperCommand - + ").", - e); } } + /** + * Creates an exception object describing the best known reason for the TypeScript parser wrapper + * failing to behave as expected. + * + * Note that the stderr stream is redirected to our stderr so a more descriptive error is likely + * to be found in the log, but we try to make the Java exception descriptive as well. + */ + private RuntimeException getExceptionFromMalformedResponse(String response, Exception e) { + try { + Integer exitCode = null; + if (parserWrapperProcess.waitFor(1L, TimeUnit.SECONDS)) { + exitCode = parserWrapperProcess.waitFor(); + } + if (exitCode != null && (exitCode == NODEJS_EXIT_CODE_FATAL_ERROR || exitCode == NODEJS_EXIT_CODE_SIG_ABORT)) { + return new ResourceError("The TypeScript parser wrapper crashed, possibly from running out of memory.", e); + } + if (exitCode != null) { + return new CatastrophicError("The TypeScript parser wrapper crashed with exit code " + exitCode); + } + } catch (InterruptedException e1) { + Exceptions.ignore(e, "This is for diagnostic purposes only."); + } + if (response == null) { + return new CatastrophicError("No response from TypeScript parser wrapper", e); + } + return new CatastrophicError("Unexpected response from TypeScript parser wrapper:\n" + response, e); + } + /** * Returns the AST for a given source file. * diff --git a/javascript/ql/examples/queries/dataflow/StoredXss/StoredXss.ql b/javascript/ql/examples/queries/dataflow/StoredXss/StoredXss.ql index 5d7234ef504..2a00a49c020 100644 --- a/javascript/ql/examples/queries/dataflow/StoredXss/StoredXss.ql +++ b/javascript/ql/examples/queries/dataflow/StoredXss/StoredXss.ql @@ -22,7 +22,8 @@ import DataFlow::PathGraph */ class MysqlSource extends StoredXss::Source { MysqlSource() { - this = moduleImport("mysql") + this = + moduleImport("mysql") .getAMemberCall("createConnection") .getAMethodCall("query") .getCallback(1) diff --git a/javascript/ql/src/AngularJS/DependencyMismatch.ql b/javascript/ql/src/AngularJS/DependencyMismatch.ql index 314c0a12320..bbdb042bf19 100644 --- a/javascript/ql/src/AngularJS/DependencyMismatch.ql +++ b/javascript/ql/src/AngularJS/DependencyMismatch.ql @@ -24,8 +24,9 @@ where exists(string n | p = f.getDependencyParameter(n) | p.getName() != n and exists(f.getDependencyParameter(p.getName())) and - msg = "This parameter is named '" + p.getName() + "', " + - "but actually refers to dependency '" + n + "'." + msg = + "This parameter is named '" + p.getName() + "', " + "but actually refers to dependency '" + + n + "'." ) ) select p, msg diff --git a/javascript/ql/src/AngularJS/DoubleCompilation.ql b/javascript/ql/src/AngularJS/DoubleCompilation.ql index 74624e51c6e..a281ef5500f 100644 --- a/javascript/ql/src/AngularJS/DoubleCompilation.ql +++ b/javascript/ql/src/AngularJS/DoubleCompilation.ql @@ -15,7 +15,8 @@ import javascript from AngularJS::ServiceReference compile, SimpleParameter elem, CallExpr c where compile.getName() = "$compile" and - elem = any(AngularJS::CustomDirective d) + elem = + any(AngularJS::CustomDirective d) .getALinkFunction() .(AngularJS::LinkFunction) .getElementParameter() and diff --git a/javascript/ql/src/AngularJS/IncompatibleService.ql b/javascript/ql/src/AngularJS/IncompatibleService.ql index dcbc0d2ceaf..7e6daafd72b 100644 --- a/javascript/ql/src/AngularJS/IncompatibleService.ql +++ b/javascript/ql/src/AngularJS/IncompatibleService.ql @@ -130,7 +130,8 @@ where kind = getServiceKind(request, name) and exists(request.getAServiceDefinition(name)) and // ignore unknown/undefined services not isCompatibleRequestedService(request, kind) and - compatibleWithString = concat(string compatibleKind | + compatibleWithString = + concat(string compatibleKind | isCompatibleRequestedService(request, compatibleKind) and not isWildcardKind(compatibleKind) | diff --git a/javascript/ql/src/AngularJS/UnusedAngularDependency.ql b/javascript/ql/src/AngularJS/UnusedAngularDependency.ql index 8358b2382c0..8e0baa00fab 100644 --- a/javascript/ql/src/AngularJS/UnusedAngularDependency.ql +++ b/javascript/ql/src/AngularJS/UnusedAngularDependency.ql @@ -34,8 +34,9 @@ predicate isMissingParameter(AngularJS::InjectableFunction f, string msg, ASTNod then dependenciesAreString = "dependency is" else dependenciesAreString = "dependencies are" ) and - msg = "This function has " + paramCount + " " + parametersString + ", but " + injectionCount + - " " + dependenciesAreString + " injected into it." + msg = + "This function has " + paramCount + " " + parametersString + ", but " + injectionCount + " " + + dependenciesAreString + " injected into it." ) ) } diff --git a/javascript/ql/src/Declarations/Declarations.qll b/javascript/ql/src/Declarations/Declarations.qll index 1ebf94f711a..4fb60140af0 100644 --- a/javascript/ql/src/Declarations/Declarations.qll +++ b/javascript/ql/src/Declarations/Declarations.qll @@ -31,7 +31,8 @@ VarRef refInContainer(Variable var, RefKind kind, StmtContainer sc) { * declaration of `var` (if `kind` is `Decl()`) in `sc`. */ VarRef firstRefInContainer(Variable var, RefKind kind, StmtContainer sc) { - result = min(refInContainer(var, kind, sc) as ref + result = + min(refInContainer(var, kind, sc) as ref order by ref.getLocation().getStartLine(), ref.getLocation().getStartColumn() ) @@ -52,7 +53,8 @@ VarRef refInTopLevel(Variable var, RefKind kind, TopLevel tl) { * declaration of `var` (if `kind` is `Decl()`) in `tl`. */ VarRef firstRefInTopLevel(Variable var, RefKind kind, TopLevel tl) { - result = min(refInTopLevel(var, kind, tl) as ref + result = + min(refInTopLevel(var, kind, tl) as ref order by ref.getLocation().getStartLine(), ref.getLocation().getStartColumn() ) diff --git a/javascript/ql/src/Declarations/MissingVarDecl.ql b/javascript/ql/src/Declarations/MissingVarDecl.ql index 8b72adcd618..8dd40cb064a 100644 --- a/javascript/ql/src/Declarations/MissingVarDecl.ql +++ b/javascript/ql/src/Declarations/MissingVarDecl.ql @@ -57,7 +57,8 @@ GlobalVarAccess getAccessIn(GlobalVariable v, Function f) { * Gets the (lexically) first access to variable `v` in function `f`. */ GlobalVarAccess getFirstAccessIn(GlobalVariable v, Function f) { - result = min(getAccessIn(v, f) as gva + result = + min(getAccessIn(v, f) as gva order by gva.getLocation().getStartLine(), gva.getLocation().getStartColumn() ) diff --git a/javascript/ql/src/Declarations/SuspiciousMethodNameDeclaration.ql b/javascript/ql/src/Declarations/SuspiciousMethodNameDeclaration.ql index febdd7ab5ea..efa824ae7b0 100644 --- a/javascript/ql/src/Declarations/SuspiciousMethodNameDeclaration.ql +++ b/javascript/ql/src/Declarations/SuspiciousMethodNameDeclaration.ql @@ -1,7 +1,7 @@ /** * @name Suspicious method name declaration - * @description A method declaration with a name that is a special keyword in another - * context is suspicious. + * @description A method declaration with a name that is a special keyword in another + * context is suspicious. * @kind problem * @problem.severity warning * @id js/suspicious-method-name-declaration @@ -19,7 +19,7 @@ import javascript predicate isSuspiciousMethodName(string name, ClassOrInterface container) { name = "function" or - // "constructor" is only suspicious outside a class. + // "constructor" is only suspicious outside a class. name = "constructor" and not container instanceof ClassDefinition or // "new" is only suspicious inside a class. @@ -31,7 +31,6 @@ where container.getLocation().getFile().getFileType().isTypeScript() and container.getMember(name) = member and isSuspiciousMethodName(name, container) and - // Cases to ignore. not ( // Assume that a "new" method is intentional if the class has an explicit constructor. @@ -39,31 +38,34 @@ where container instanceof ClassDefinition and exists(ConstructorDeclaration constructor | container.getMember("constructor") = constructor and - not constructor.isSynthetic() - ) + not constructor.isSynthetic() + ) or // Explicitly declared static methods are fine. container instanceof ClassDefinition and member.isStatic() or - // Only looking for declared methods. Methods with a body are OK. + // Only looking for declared methods. Methods with a body are OK. exists(member.getBody().getBody()) or // The developer was not confused about "function" when there are other methods in the interface. - name = "function" and + name = "function" and exists(MethodDeclaration other | other = container.getAMethod() | other.getName() != "function" and not other.(ConstructorDeclaration).isSynthetic() ) - ) - - and - + ) and ( - name = "constructor" and msg = "The member name 'constructor' does not declare a constructor in interfaces, but it does in classes." + name = "constructor" and + msg = + "The member name 'constructor' does not declare a constructor in interfaces, but it does in classes." or - name = "new" and msg = "The member name 'new' does not declare a constructor, but 'constructor' does in class declarations." + name = "new" and + msg = + "The member name 'new' does not declare a constructor, but 'constructor' does in class declarations." or - name = "function" and msg = "The member name 'function' does not declare a function, it declares a method named 'function'." + name = "function" and + msg = + "The member name 'function' does not declare a function, it declares a method named 'function'." ) select member, msg diff --git a/javascript/ql/src/Declarations/UnreachableMethodOverloads.ql b/javascript/ql/src/Declarations/UnreachableMethodOverloads.ql index bcfd5d810e7..1f131270363 100644 --- a/javascript/ql/src/Declarations/UnreachableMethodOverloads.ql +++ b/javascript/ql/src/Declarations/UnreachableMethodOverloads.ql @@ -44,43 +44,39 @@ string getKind(MemberDeclaration m) { * A call-signature that originates from a MethodSignature in the AST. */ private class MethodCallSig extends CallSignatureType { - string name; - - MethodCallSig() { - exists(MethodSignature sig | - this = sig.getBody().getCallSignature() and - name = sig.getName() - ) - } - - /** - * Gets the name of any member that has this signature. - */ - string getName() { - result = name - } + string name; + + MethodCallSig() { + exists(MethodSignature sig | + this = sig.getBody().getCallSignature() and + name = sig.getName() + ) + } + + /** + * Gets the name of any member that has this signature. + */ + string getName() { result = name } } /** * Holds if the two call signatures could be overloads of each other and have the same parameter types. */ predicate matchingCallSignature(MethodCallSig method, MethodCallSig other) { - method.getName() = other.getName() and - + method.getName() = other.getName() and method.getNumOptionalParameter() = other.getNumOptionalParameter() and method.getNumParameter() = other.getNumParameter() and method.getNumRequiredParameter() = other.getNumRequiredParameter() and - // purposely not looking at number of type arguments. - - method.getKind() = other.getKind() and - - - forall(int i | i in [0 .. -1 + method.getNumParameter()] | + // purposely not looking at number of type arguments. + method.getKind() = other.getKind() and + forall(int i | i in [0 .. -1 + method.getNumParameter()] | method.getParameter(i) = other.getParameter(i) // This is sometimes imprecise, so it is still a good idea to compare type annotations. - ) and - - // shared type parameters are equal. - forall(int i | i in [0 .. -1 + min(int num | num = method.getNumTypeParameter() or num = other.getNumTypeParameter())] | + ) and + // shared type parameters are equal. + forall(int i | + i in [0 .. -1 + + min(int num | num = method.getNumTypeParameter() or num = other.getNumTypeParameter())] + | method.getTypeParameterBound(i) = other.getTypeParameterBound(i) ) } @@ -89,7 +85,7 @@ predicate matchingCallSignature(MethodCallSig method, MethodCallSig other) { * Gets which overload index the MethodSignature has among the overloads of the same name. */ int getOverloadIndex(MethodSignature sig) { - sig.getDeclaringType().getMethodOverload(sig.getName(), result) = sig + sig.getDeclaringType().getMethodOverload(sig.getName(), result) = sig } /** @@ -100,18 +96,22 @@ predicate signaturesMatch(MethodSignature method, MethodSignature other) { method.getDeclaringType() = other.getDeclaringType() and // same static modifier. getKind(method) = getKind(other) and - // same name. method.getName() = other.getName() and - // same number of parameters. method.getBody().getNumParameter() = other.getBody().getNumParameter() and - + // same this parameter (if exists) + ( + not exists(method.getBody().getThisTypeAnnotation()) and + not exists(other.getBody().getThisTypeAnnotation()) + or + method.getBody().getThisTypeAnnotation().getType() = + other.getBody().getThisTypeAnnotation().getType() + ) and // The types are compared in matchingCallSignature. This is sanity-check that the textual representation of the type-annotations are somewhat similar. forall(int i | i in [0 .. -1 + method.getBody().getNumParameter()] | - getParameterTypeAnnotation(method, i) = getParameterTypeAnnotation(other, i) + getParameterTypeAnnotation(method, i) = getParameterTypeAnnotation(other, i) ) and - matchingCallSignature(method.getBody().getCallSignature(), other.getBody().getCallSignature()) } @@ -119,22 +119,19 @@ from ClassOrInterface decl, string name, MethodSignature previous, MethodSignatu where previous = decl.getMethod(name) and unreachable = getOtherMatchingSignatures(previous) and - // If the method is part of inheritance between classes/interfaces, then there can sometimes be reasons for having this pattern. - not exists(decl.getASuperTypeDeclaration().getMethod(name)) and - not exists(ClassOrInterface sub | + not exists(decl.getASuperTypeDeclaration().getMethod(name)) and + not exists(ClassOrInterface sub | decl = sub.getASuperTypeDeclaration() and exists(sub.getMethod(name)) ) and - - // If a later method overload has more type parameters, then that overload can be selected by explicitly declaring the type arguments at the callsite. // This comparison removes those cases. unreachable.getBody().getNumTypeParameter() <= previous.getBody().getNumTypeParameter() and - // We always select the first of the overloaded methods. not exists(MethodSignature later | later = getOtherMatchingSignatures(previous) | getOverloadIndex(later) < getOverloadIndex(previous) ) select unreachable, - "This overload of " + name + "() is unreachable, the $@ overload will always be selected.", previous, "previous" + "This overload of " + name + "() is unreachable, the $@ overload will always be selected.", + previous, "previous" diff --git a/javascript/ql/src/Declarations/UnstableCyclicImport.ql b/javascript/ql/src/Declarations/UnstableCyclicImport.ql index 51006e42ad7..2e4ee9bdbf9 100644 --- a/javascript/ql/src/Declarations/UnstableCyclicImport.ql +++ b/javascript/ql/src/Declarations/UnstableCyclicImport.ql @@ -74,7 +74,8 @@ class CandidateVarAccess extends VarAccess { * We use this to avoid duplicate alerts about the same underlying cyclic import. */ VarAccess getFirstCandidateAccess(ImportDeclaration decl) { - result = min(decl.getASpecifier().getLocal().getVariable().getAnAccess().(CandidateVarAccess) as p + result = + min(decl.getASpecifier().getLocal().getVariable().getAnAccess().(CandidateVarAccess) as p order by p.getFirstToken().getIndex() ) @@ -133,14 +134,15 @@ string pathToModule(Module source, Module destination, int steps) { steps > 1 and exists(Module next | // Only extend the path to one of the potential successors, as we only need one example. - next = min(Module mod | + next = + min(Module mod | isImportedAtRuntime(source, mod) and numberOfStepsToModule(mod, destination, steps - 1) | mod order by mod.getName() ) and - result = repr(getARuntimeImport(source, next)) + " => " + - pathToModule(next, destination, steps - 1) + result = + repr(getARuntimeImport(source, next)) + " => " + pathToModule(next, destination, steps - 1) ) ) } diff --git a/javascript/ql/src/Expressions/ExprHasNoEffect.ql b/javascript/ql/src/Expressions/ExprHasNoEffect.ql index 917ab81a3e7..5ef6be7ba14 100644 --- a/javascript/ql/src/Expressions/ExprHasNoEffect.ql +++ b/javascript/ql/src/Expressions/ExprHasNoEffect.ql @@ -16,7 +16,6 @@ import javascript import ExprHasNoEffect import semmle.javascript.RestrictedLocations - from Expr e where hasNoEffect(e) select e.(FirstLineOf), "This expression has no effect." diff --git a/javascript/ql/src/Expressions/HeterogeneousComparison.ql b/javascript/ql/src/Expressions/HeterogeneousComparison.ql index 3f1eb083a4a..a736fa5cdc9 100644 --- a/javascript/ql/src/Expressions/HeterogeneousComparison.ql +++ b/javascript/ql/src/Expressions/HeterogeneousComparison.ql @@ -200,10 +200,12 @@ where rightExprDescription = getDescription(right.asExpr(), "an expression") and leftTypeCount = strictcount(left.getAType()) and rightTypeCount = strictcount(right.getAType()) and - leftTypeDescription = getTypeDescription("is of type " + leftTypes, - "cannot be of type " + rightTypes, leftTypeCount, rightTypeCount) and - rightTypeDescription = getTypeDescription("of type " + rightTypes, - ", which cannot be of type " + leftTypes, rightTypeCount, leftTypeCount) + leftTypeDescription = + getTypeDescription("is of type " + leftTypes, "cannot be of type " + rightTypes, leftTypeCount, + rightTypeCount) and + rightTypeDescription = + getTypeDescription("of type " + rightTypes, ", which cannot be of type " + leftTypes, + rightTypeCount, leftTypeCount) select left, leftExprDescription + " " + leftTypeDescription + ", but it is compared to $@ " + rightTypeDescription + ".", right, rightExprDescription diff --git a/javascript/ql/src/Expressions/MissingSpaceInAppend.ql b/javascript/ql/src/Expressions/MissingSpaceInAppend.ql index 2df7940646c..7589c0d54f3 100644 --- a/javascript/ql/src/Expressions/MissingSpaceInAppend.ql +++ b/javascript/ql/src/Expressions/MissingSpaceInAppend.ql @@ -23,48 +23,40 @@ Expr leftChild(Expr e) { } predicate isInConcat(Expr e) { - exists(ParExpr par | isInConcat(par) and par.getExpression() = e) - or - exists(AddExpr a | a.getAnOperand() = e) + exists(ParExpr par | isInConcat(par) and par.getExpression() = e) + or + exists(AddExpr a | a.getAnOperand() = e) } class ConcatenationLiteral extends Expr { ConcatenationLiteral() { ( - this instanceof TemplateLiteral + this instanceof TemplateLiteral or this instanceof Literal - ) - and isInConcat(this) + ) and + isInConcat(this) } } Expr getConcatChild(Expr e) { - result = rightChild(e) or - result = leftChild(e) + result = rightChild(e) or + result = leftChild(e) } -Expr getConcatParent(Expr e) { - e = getConcatChild(result) -} +Expr getConcatParent(Expr e) { e = getConcatChild(result) } predicate isWordLike(ConcatenationLiteral lit) { - lit.getStringValue().regexpMatch("(?i).*[a-z]{3,}.*") + lit.getStringValue().regexpMatch("(?i).*[a-z]{3,}.*") } class ConcatRoot extends AddExpr { - ConcatRoot() { - not isInConcat(this) - } + ConcatRoot() { not isInConcat(this) } } -ConcatRoot getAddRoot(AddExpr e) { - result = getConcatParent*(e) -} +ConcatRoot getAddRoot(AddExpr e) { result = getConcatParent*(e) } -predicate hasWordLikeFragment(AddExpr e) { - isWordLike(getConcatChild*(getAddRoot(e))) -} +predicate hasWordLikeFragment(AddExpr e) { isWordLike(getConcatChild*(getAddRoot(e))) } from AddExpr e, ConcatenationLiteral l, ConcatenationLiteral r, string word where @@ -79,7 +71,6 @@ where word = l.getStringValue().regexpCapture(".* (([-A-Za-z/'\\.:,]*[a-zA-Z]|[0-9]+)[\\.:,!?']*)", 1) and r.getStringValue().regexpMatch("[a-zA-Z].*") and not word.regexpMatch(".*[,\\.:].*[a-zA-Z].*[^a-zA-Z]") and - // There must be a constant-string in the concatenation that looks like a word. hasWordLikeFragment(e) select l, "This string appears to be missing a space after '" + word + "'." diff --git a/javascript/ql/src/Expressions/Misspelling.qll b/javascript/ql/src/Expressions/Misspelling.qll index d143c0394be..e7c35260edb 100644 --- a/javascript/ql/src/Expressions/Misspelling.qll +++ b/javascript/ql/src/Expressions/Misspelling.qll @@ -91,7 +91,8 @@ private string replaceATypoAndLowerCase(Identifier wrong) { idPart(wrong, wrongPart, offset) | normalized_typos(wrongPart, rightPart, _, _, _, _) and - rightName = wrong.getName().substring(0, offset) + rightPart + + rightName = + wrong.getName().substring(0, offset) + rightPart + wrong.getName().suffix(offset + wrongPart.length()) and result = rightName.toLowerCase() ) diff --git a/javascript/ql/src/Expressions/UnboundEventHandlerReceiver.ql b/javascript/ql/src/Expressions/UnboundEventHandlerReceiver.ql index bbb5e80dc25..4ae3504d843 100644 --- a/javascript/ql/src/Expressions/UnboundEventHandlerReceiver.ql +++ b/javascript/ql/src/Expressions/UnboundEventHandlerReceiver.ql @@ -36,7 +36,8 @@ private predicate isBoundInMethod(MethodDeclaration method) { mod = "react-autobind" | thiz.flowsTo(DataFlow::moduleImport(mod).getACall().getArgument(0)) - ) or + ) + or // heuristic reflective binders exists(DataFlow::CallNode binder, string calleeName | ( @@ -92,8 +93,8 @@ private DOM::AttributeDefinition getAnEventHandlerAttribute() { from MethodDeclaration callback, DOM::AttributeDefinition attribute, ThisExpr unbound where attribute = getAnEventHandlerAttribute() and - attribute.getValueNode().analyze().getAValue().(AbstractFunction).getFunction() = callback - .getBody() and + attribute.getValueNode().analyze().getAValue().(AbstractFunction).getFunction() = + callback.getBody() and unbound.getBinder() = callback.getBody() and not isBoundInMethod(callback) select attribute, diff --git a/javascript/ql/src/LanguageFeatures/IllegalInvocation.ql b/javascript/ql/src/LanguageFeatures/IllegalInvocation.ql index 467b80f4dac..4c8939c4180 100644 --- a/javascript/ql/src/LanguageFeatures/IllegalInvocation.ql +++ b/javascript/ql/src/LanguageFeatures/IllegalInvocation.ql @@ -59,7 +59,8 @@ where not isCallToFunction(cs) and // conservatively only flag call sites where _all_ callees are illegal forex(DataFlow::InvokeNode cs2, Function otherCallee | - cs2.getInvokeExpr() = cs.getInvokeExpr() and otherCallee = cs2.getACallee() | + cs2.getInvokeExpr() = cs.getInvokeExpr() and otherCallee = cs2.getACallee() + | illegalInvocation(cs, otherCallee, _, _) ) and // require that all callees are known diff --git a/javascript/ql/src/LanguageFeatures/InconsistentNew.ql b/javascript/ql/src/LanguageFeatures/InconsistentNew.ql index 1e07cfd72f8..8a14dbe37c1 100644 --- a/javascript/ql/src/LanguageFeatures/InconsistentNew.ql +++ b/javascript/ql/src/LanguageFeatures/InconsistentNew.ql @@ -35,7 +35,8 @@ predicate guardsAgainstMissingNew(Function f) { * `true` if `cs` is a `new` expression, and to `false` otherwise. */ Function getALikelyCallee(DataFlow::InvokeNode cs, boolean isNew) { - result = min(Function callee, int imprecision | + result = + min(Function callee, int imprecision | callee = cs.getACallee(imprecision) | callee order by imprecision @@ -84,7 +85,8 @@ predicate whitelistedCall(DataFlow::CallNode call) { * and start column. */ DataFlow::InvokeNode getFirstInvocation(Function f, boolean isNew) { - result = min(DataFlow::InvokeNode invk, string path, int line, int col | + result = + min(DataFlow::InvokeNode invk, string path, int line, int col | f = getALikelyCallee(invk, isNew) and invk.hasLocationInfo(path, line, col, _, _) | invk order by path, line, col diff --git a/javascript/ql/src/LanguageFeatures/SyntaxError.ql b/javascript/ql/src/LanguageFeatures/SyntaxError.ql index 4d0a21a9661..6f6ac10a8cc 100644 --- a/javascript/ql/src/LanguageFeatures/SyntaxError.ql +++ b/javascript/ql/src/LanguageFeatures/SyntaxError.ql @@ -2,12 +2,12 @@ * @name Syntax error * @description A piece of code could not be parsed due to syntax errors. * @kind problem - * @problem.severity error + * @problem.severity recommendation * @id js/syntax-error * @tags reliability * correctness * language-features - * @precision high + * @precision very-high */ import javascript diff --git a/javascript/ql/src/Metrics/FCyclomaticComplexity.ql b/javascript/ql/src/Metrics/FCyclomaticComplexity.ql index a737832f395..5c34a1919b7 100644 --- a/javascript/ql/src/Metrics/FCyclomaticComplexity.ql +++ b/javascript/ql/src/Metrics/FCyclomaticComplexity.ql @@ -14,7 +14,8 @@ import javascript from File f, float n where - n = avg(Function fun, int toAvg | + n = + avg(Function fun, int toAvg | fun.getTopLevel().getFile() = f and toAvg = fun.getCyclomaticComplexity() | toAvg diff --git a/javascript/ql/src/Performance/PolynomialReDoS.qhelp b/javascript/ql/src/Performance/PolynomialReDoS.qhelp new file mode 100644 index 00000000000..b0e92d4ea5f --- /dev/null +++ b/javascript/ql/src/Performance/PolynomialReDoS.qhelp @@ -0,0 +1,108 @@ + + + + + + + +

+ + Consider this use of a regular expression, which removes + all leading and trailing whitespace in a string: + +

+ + + text.replace(/^\s+|\s+$/g, ''); // BAD + + +

+ + The sub-expression "\s+$" will match the + whitespace characters in text from left to right, but it + can start matching anywhere within a whitespace sequence. This is + problematic for strings that do not end with a whitespace + character. Such a string will force the regular expression engine to + process each whitespace sequence once per whitespace character in the + sequence. + +

+ +

+ + This ultimately means that the time cost of trimming a + string is quadratic in the length of the string. So a string like + "a b" will take milliseconds to process, but a similar + string with a million spaces instead of just one will take several + minutes. + +

+ +

+ + Avoid this problem by rewriting the regular expression to + not contain the ambiguity about when to start matching whitespace + sequences. For instance, by using a negative look-behind + (/^\s+|(?<!\s)\s+$/g), or just by using the built-in trim + method (text.trim()). + +

+ +

+ + Note that the sub-expression "^\s+" is + not problematic as the ^ anchor restricts + when that sub-expression can start matching, and as the regular + expression engine matches from left to right. + +

+ +
+ + + +

+ + As a similar, but slightly subtler problem, consider the + regular expression that matches lines with numbers, possibly written + using scientific notation: +

+ + + ^0\.\d+E?\d+$ // BAD + + +

+ + The problem with this regular expression is in the + sub-expression \d+E?\d+ because the second + \d+ can start matching digits anywhere after the first + match of the first \d+ if there is no E in + the input string. + +

+ +

+ + This is problematic for strings that do not + end with a digit. Such a string will force the regular expression + engine to process each digit sequence once per digit in the sequence, + again leading to a quadratic time complexity. + +

+ +

+ + To make the processing faster, the regular expression + should be rewritten such that the two \d+ sub-expressions + do not have overlapping matches: ^0\.\d+(E\d+)?$. + +

+ +
+ + + +
diff --git a/javascript/ql/src/Performance/PolynomialReDoS.ql b/javascript/ql/src/Performance/PolynomialReDoS.ql new file mode 100644 index 00000000000..a4c6b971aa7 --- /dev/null +++ b/javascript/ql/src/Performance/PolynomialReDoS.ql @@ -0,0 +1,22 @@ +/** + * @name Polynomial regular expression used on uncontrolled data + * @description A regular expression that can require polynomial time + * to match user-provided values may be + * vulnerable to denial-of-service attacks. + * @kind path-problem + * @problem.severity warning + * @precision high + * @id js/polynomial-redos + * @tags security + * external/cwe/cwe-730 + * external/cwe/cwe-400 + */ + +import javascript +import semmle.javascript.security.performance.PolynomialReDoS::PolynomialReDoS +import DataFlow::PathGraph + +from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink +where cfg.hasFlowPath(source, sink) +select sink.getNode(), source, sink, "This expensive $@ use depends on $@.", + sink.getNode().(Sink).getRegExp(), "regular expression", source.getNode(), "a user-provided value" diff --git a/javascript/ql/src/Performance/ReDoS.qhelp b/javascript/ql/src/Performance/ReDoS.qhelp index b226eef11b8..48fa8393cf4 100644 --- a/javascript/ql/src/Performance/ReDoS.qhelp +++ b/javascript/ql/src/Performance/ReDoS.qhelp @@ -1,70 +1,34 @@ +"-//Semmle//qhelp//EN" +"qhelp.dtd"> + - -

-Some regular expressions take a very long time to match certain input strings to the point where -the time it takes to match a string of length n is proportional to 2n. -Such regular expressions can negatively affect performance, or even allow a malicious user to -perform a Denial of Service ("DoS") attack by crafting an expensive input string for the regular -expression to match. -

-

-The regular expression engines provided by many popular JavaScript platforms use backtracking -non-deterministic finite automata to implement regular expression matching. While this approach -is space-efficient and allows supporting advanced features like capture groups, it is not -time-efficient in general. The worst-case time complexity of such an automaton can be exponential, -meaning that for strings of a certain shape, increasing the input length by ten characters may -make the automaton about 1000 times slower. -

-

-Typically, a regular expression is affected by this problem if it contains a repetition of the -form r* or r+ where the sub-expression r is ambiguous in -the sense that it can match some string in multiple ways. More information about the precise -circumstances can be found in the references. -

-
+ - -

-Modify the regular expression to remove the ambiguity. -

-
+ +

+ Consider this regular expression: +

+ + /^_(__|.)+_$/ + +

+ Its sub-expression "(__|.)+?" can match the string "__" either by the + first alternative "__" to the left of the "|" operator, or by two + repetitions of the second alternative "." to the right. Thus, a string consisting + of an odd number of underscores followed by some other character will cause the regular + expression engine to run for an exponential amount of time before rejecting the input. +

+

+ This problem can be avoided by rewriting the regular expression to remove the ambiguity between + the two branches of the alternative inside the repetition: +

+ + /^_(__|[^_])+_$/ + +
- -

-Consider this regular expression: -

- -/^_(__|.)+_$/ - -

-Its sub-expression "(__|.)+?" can match the string "__" either by the -first alternative "__" to the left of the "|" operator, or by two -repetitions of the second alternative "." to the right. Thus, a string consisting -of an odd number of underscores followed by some other character will cause the regular -expression engine to run for an exponential amount of time before rejecting the input. -

-

-This problem can be avoided by rewriting the regular expression to remove the ambiguity between -the two branches of the alternative inside the repetition: -

- -/^_(__|[^_])+_$/ - -
+ - -
  • -OWASP: -Regular expression Denial of Service - ReDoS. -
  • -
  • Wikipedia: ReDoS.
  • -
  • Wikipedia: Time complexity.
  • -
  • James Kirrage, Asiri Rathnayake, Hayo Thielecke: -Static Analysis for Regular Expression Denial-of-Service Attack. -
  • -
    diff --git a/javascript/ql/src/Performance/ReDoS.ql b/javascript/ql/src/Performance/ReDoS.ql index 7f74dcb47d5..fb5fb6aa856 100644 --- a/javascript/ql/src/Performance/ReDoS.ql +++ b/javascript/ql/src/Performance/ReDoS.ql @@ -292,9 +292,7 @@ class EdgeLabel extends TInputSymbol { * Gets the state before matching `t`. */ pragma[inline] -State before(RegExpTerm t) { - result = Match(t, 0) -} +State before(RegExpTerm t) { result = Match(t, 0) } /** * Gets a state the NFA may be in after matching `t`. @@ -337,9 +335,7 @@ predicate delta(State q1, EdgeLabel lbl, State q2) { ) ) or - exists(RegExpDot dot | - q1 = before(dot) and q2 = after(dot) - | + exists(RegExpDot dot | q1 = before(dot) and q2 = after(dot) | if dot.getLiteral().isDotAll() then lbl = Any() else lbl = Dot() ) or @@ -545,7 +541,8 @@ string intersect(InputSymbol c, InputSymbol d) { * Gets a character matched by character class `cc`. */ string choose(RegExpCharacterClass cc) { - result = min(string c | + result = + min(string c | exists(RegExpTerm child | child = cc.getAChild() | c = child.(RegExpConstant).getValue() or child.(RegExpCharacterRange).isRange(c, _) diff --git a/javascript/ql/src/Performance/ReDoSIntroduction.qhelp b/javascript/ql/src/Performance/ReDoSIntroduction.qhelp new file mode 100644 index 00000000000..4e3d3a06f65 --- /dev/null +++ b/javascript/ql/src/Performance/ReDoSIntroduction.qhelp @@ -0,0 +1,55 @@ + + + +

    + + Some regular expressions take a long time to match certain + input strings to the point where the time it takes to match a string + of length n is proportional to nk or even + 2n. Such regular expressions can negatively affect + performance, or even allow a malicious user to perform a Denial of + Service ("DoS") attack by crafting an expensive input string for the + regular expression to match. + +

    + +

    + + The regular expression engines provided by many popular + JavaScript platforms use backtracking non-deterministic finite + automata to implement regular expression matching. While this approach + is space-efficient and allows supporting advanced features like + capture groups, it is not time-efficient in general. The worst-case + time complexity of such an automaton can be polynomial or even + exponential, meaning that for strings of a certain shape, increasing + the input length by ten characters may make the automaton about 1000 + times slower. + +

    + +

    + + Typically, a regular expression is affected by this + problem if it contains a repetition of the form r* or + r+ where the sub-expression r is ambiguous + in the sense that it can match some string in multiple ways. More + information about the precise circumstances can be found in the + references. + +

    +
    + + + +

    + + Modify the regular expression to remove the ambiguity, or + ensure that the strings matched with the regular expression are short + enough that the time-complexity does not matter. + +

    + +
    +
    diff --git a/javascript/ql/src/Performance/ReDoSReferences.qhelp b/javascript/ql/src/Performance/ReDoSReferences.qhelp new file mode 100644 index 00000000000..2b3e5f17c62 --- /dev/null +++ b/javascript/ql/src/Performance/ReDoSReferences.qhelp @@ -0,0 +1,16 @@ + + + +
  • + OWASP: + Regular expression Denial of Service - ReDoS. +
  • +
  • Wikipedia: ReDoS.
  • +
  • Wikipedia: Time complexity.
  • +
  • James Kirrage, Asiri Rathnayake, Hayo Thielecke: + Static Analysis for Regular Expression Denial-of-Service Attack. +
  • +
    +
    diff --git a/javascript/ql/src/RegExp/BackspaceEscape.ql b/javascript/ql/src/RegExp/BackspaceEscape.ql index 4de176ada2f..054f46f06b6 100644 --- a/javascript/ql/src/RegExp/BackspaceEscape.ql +++ b/javascript/ql/src/RegExp/BackspaceEscape.ql @@ -14,6 +14,7 @@ import javascript from RegExpCharEscape rece -where rece.toString() = "\\b" - and rece.isPartOfRegExpLiteral() +where + rece.toString() = "\\b" and + rece.isPartOfRegExpLiteral() select rece, "Backspace escape in regular expression." diff --git a/javascript/ql/src/RegExp/DuplicateCharacterInCharacterClass.ql b/javascript/ql/src/RegExp/DuplicateCharacterInCharacterClass.ql index 874f973fe1e..75141f556f4 100644 --- a/javascript/ql/src/RegExp/DuplicateCharacterInCharacterClass.ql +++ b/javascript/ql/src/RegExp/DuplicateCharacterInCharacterClass.ql @@ -18,7 +18,8 @@ import javascript * Indexing is 1-based. */ predicate constantInCharacterClass(RegExpCharacterClass recc, int i, RegExpConstant cc, string val) { - cc = rank[i](RegExpConstant cc2, int j | + cc = + rank[i](RegExpConstant cc2, int j | cc2 = recc.getChild(j) and cc2.isCharacter() and cc2.getValue() = val | cc2 order by j diff --git a/javascript/ql/src/RegExp/IdentityReplacement.ql b/javascript/ql/src/RegExp/IdentityReplacement.ql index 27057708401..f8c62a1ab3a 100644 --- a/javascript/ql/src/RegExp/IdentityReplacement.ql +++ b/javascript/ql/src/RegExp/IdentityReplacement.ql @@ -58,7 +58,8 @@ predicate regExpMatchesString(RegExpTerm t, string s) { or // sequences match the concatenation of their elements exists(RegExpSequence seq | seq = t | - s = concat(int i, RegExpTerm child | + s = + concat(int i, RegExpTerm child | child = seq.getChild(i) | any(string subs | regExpMatchesString(child, subs)) order by i diff --git a/javascript/ql/src/RegExp/RegExpAlwaysMatches.ql b/javascript/ql/src/RegExp/RegExpAlwaysMatches.ql index dd18ce3f866..e352617337f 100644 --- a/javascript/ql/src/RegExp/RegExpAlwaysMatches.ql +++ b/javascript/ql/src/RegExp/RegExpAlwaysMatches.ql @@ -73,13 +73,9 @@ abstract class RegExpQuery extends DataFlow::CallNode { class RegExpTestCall extends DataFlow::MethodCallNode, RegExpQuery { DataFlow::RegExpCreationNode regexp; - RegExpTestCall() { - this = regexp.getAReference().getAMethodCall("test") - } + RegExpTestCall() { this = regexp.getAReference().getAMethodCall("test") } - override RegExpTerm getRegExp() { - result = regexp.getRoot() - } + override RegExpTerm getRegExp() { result = regexp.getRoot() } } /** @@ -93,9 +89,7 @@ class RegExpSearchCall extends DataFlow::MethodCallNode, RegExpQuery { regexp.getAReference().flowsTo(getArgument(0)) } - override RegExpTerm getRegExp() { - result = regexp.getRoot() - } + override RegExpTerm getRegExp() { result = regexp.getRoot() } } /** @@ -116,10 +110,12 @@ where ( call instanceof RegExpTestCall and not isPossiblyAnchoredOnBothEnds(term) and - message = "This regular expression always matches when used in a test $@, as it can match an empty substring." + message = + "This regular expression always matches when used in a test $@, as it can match an empty substring." or call instanceof RegExpSearchCall and not term.getAChild*() instanceof RegExpDollar and - message = "This regular expression always the matches at index 0 when used $@, as it matches the empty substring." + message = + "This regular expression always the matches at index 0 when used $@, as it matches the empty substring." ) select term, message, call, "here" diff --git a/javascript/ql/src/Security/CWE-020/HostnameRegexpShared.qll b/javascript/ql/src/Security/CWE-020/HostnameRegexpShared.qll index 337d46b7145..1afd522ac7c 100644 --- a/javascript/ql/src/Security/CWE-020/HostnameRegexpShared.qll +++ b/javascript/ql/src/Security/CWE-020/HostnameRegexpShared.qll @@ -2,6 +2,7 @@ * Provides predicates for reasoning about regular expressions * that match URLs and hostname patterns. */ + import javascript /** @@ -39,9 +40,7 @@ predicate isDotLike(RegExpTerm term) { predicate matchesBeginningOfString(RegExpTerm term) { term.isRootTerm() or - exists(RegExpTerm parent | - matchesBeginningOfString(parent) - | + exists(RegExpTerm parent | matchesBeginningOfString(parent) | term = parent.(RegExpSequence).getChild(0) or parent.(RegExpSequence).getChild(0) instanceof RegExpCaret and @@ -60,7 +59,11 @@ predicate matchesBeginningOfString(RegExpTerm term) { * `i` is bound to the index of the last child in the top-level domain part. */ predicate hasTopLevelDomainEnding(RegExpSequence seq, int i) { - seq.getChild(i).(RegExpConstant).getValue().regexpMatch("(?i)" + RegExpPatterns::commonTLD() + "(:\\d+)?([/?#].*)?") and + seq + .getChild(i) + .(RegExpConstant) + .getValue() + .regexpMatch("(?i)" + RegExpPatterns::commonTLD() + "(:\\d+)?([/?#].*)?") and isDotLike(seq.getChild(i - 1)) and not (i = 1 and matchesBeginningOfString(seq)) } @@ -69,9 +72,7 @@ predicate hasTopLevelDomainEnding(RegExpSequence seq, int i) { * Holds if the given regular expression term contains top-level domain preceded by a dot, * such as `.com`. */ -predicate hasTopLevelDomainEnding(RegExpSequence seq) { - hasTopLevelDomainEnding(seq, _) -} +predicate hasTopLevelDomainEnding(RegExpSequence seq) { hasTopLevelDomainEnding(seq, _) } /** * Holds if `term` will always match a hostname, that is, all disjunctions contain diff --git a/javascript/ql/src/Security/CWE-020/IncompleteHostnameRegExp.ql b/javascript/ql/src/Security/CWE-020/IncompleteHostnameRegExp.ql index 3b6ce8838e1..7cdef3c983b 100644 --- a/javascript/ql/src/Security/CWE-020/IncompleteHostnameRegExp.ql +++ b/javascript/ql/src/Security/CWE-020/IncompleteHostnameRegExp.ql @@ -61,9 +61,12 @@ predicate isIncompleteHostNameRegExpPattern(RegExpTerm regexp, RegExpSequence se unescapedDot = seq.getChild([0 .. i - 1]).getAChild*() and unescapedDot != seq.getChild(i - 1) and // Should not be the '.' immediately before the TLD not hasConsecutiveDots(unescapedDot.getParent()) and - hostname = seq.getChild(i - 2).getRawValue() + seq.getChild(i - 1).getRawValue() + seq.getChild(i).getRawValue() + hostname = + seq.getChild(i - 2).getRawValue() + seq.getChild(i - 1).getRawValue() + + seq.getChild(i).getRawValue() | - if unescapedDot.getParent() instanceof RegExpQuantifier then ( + if unescapedDot.getParent() instanceof RegExpQuantifier + then // `.*\.example.com` can match `evil.com/?x=.example.com` // // This problem only occurs when the pattern is applied against a full URL, not just a hostname/origin. @@ -73,16 +76,20 @@ predicate isIncompleteHostNameRegExpPattern(RegExpTerm regexp, RegExpSequence se seq.getChild(0) instanceof RegExpCaret and not seq.getAChild() instanceof RegExpDollar and seq.getChild([i .. i + 1]).(RegExpConstant).getValue().regexpMatch(".*[/?#].*") and - msg = "has an unrestricted wildcard '" + - unescapedDot.getParent().(RegExpQuantifier).getRawValue() + - "' which may cause '" + hostname + "' to be matched anywhere in the URL, outside the hostname." - ) else ( - msg = "has an unescaped '.' before '" + hostname + "', so it might match more hosts than expected." - ) + msg = + "has an unrestricted wildcard '" + unescapedDot.getParent().(RegExpQuantifier).getRawValue() + + "' which may cause '" + hostname + + "' to be matched anywhere in the URL, outside the hostname." + else + msg = + "has an unescaped '.' before '" + hostname + + "', so it might match more hosts than expected." ) } -from RegExpPatternSource re, RegExpTerm regexp, RegExpSequence hostSequence, string msg, string kind, DataFlow::Node aux +from + RegExpPatternSource re, RegExpTerm regexp, RegExpSequence hostSequence, string msg, string kind, + DataFlow::Node aux where regexp = re.getRegExpTerm() and isIncompleteHostNameRegExpPattern(regexp, hostSequence, msg) and @@ -96,5 +103,4 @@ where ) ) and not CharacterEscapes::hasALikelyRegExpPatternMistake(re) -select hostSequence, - "This " + kind + " " + msg, aux, "here" +select hostSequence, "This " + kind + " " + msg, aux, "here" diff --git a/javascript/ql/src/Security/CWE-020/IncompleteUrlSchemeCheck.ql b/javascript/ql/src/Security/CWE-020/IncompleteUrlSchemeCheck.ql index 8be00495b0f..86ab1744086 100644 --- a/javascript/ql/src/Security/CWE-020/IncompleteUrlSchemeCheck.ql +++ b/javascript/ql/src/Security/CWE-020/IncompleteUrlSchemeCheck.ql @@ -20,9 +20,7 @@ class DangerousScheme extends string { } /** Gets a data-flow node that checks `nd` against the given `scheme`. */ -DataFlow::Node schemeCheck( - DataFlow::Node nd, DangerousScheme scheme -) { +DataFlow::Node schemeCheck(DataFlow::Node nd, DangerousScheme scheme) { // check of the form `nd.startsWith(scheme)` exists(StringOps::StartsWith sw | sw = result | sw.getBaseString() = nd and diff --git a/javascript/ql/src/Security/CWE-020/MissingRegExpAnchor.ql b/javascript/ql/src/Security/CWE-020/MissingRegExpAnchor.ql index 7f8acd14030..972ca02ceb9 100644 --- a/javascript/ql/src/Security/CWE-020/MissingRegExpAnchor.ql +++ b/javascript/ql/src/Security/CWE-020/MissingRegExpAnchor.ql @@ -46,9 +46,7 @@ predicate isInteriorAnchor(RegExpAnchor term) { * Holds if `term` contains an anchor that is not the first or last node * in its tree, such as `(foo|bar$|baz)`. */ -predicate containsInteriorAnchor(RegExpTerm term) { - isInteriorAnchor(term.getAChild*()) -} +predicate containsInteriorAnchor(RegExpTerm term) { isInteriorAnchor(term.getAChild*()) } /** * Holds if `term` starts with a word boundary or lookbehind assertion, @@ -78,9 +76,7 @@ predicate containsTrailingPseudoAnchor(RegExpSequence term) { * Holds if `term` is an empty sequence, usually arising from * literals with a trailing alternative such as `foo|`. */ -predicate isEmpty(RegExpSequence term) { - term.getNumChild() = 0 -} +predicate isEmpty(RegExpSequence term) { term.getNumChild() = 0 } /** * Holds if `term` contains a letter constant. @@ -131,14 +127,14 @@ predicate hasMisleadingAnchorPrecedence(RegExpPatternSource src, string msg) { ( anchoredTerm = root.getChild(0) and anchoredTerm.getChild(0) instanceof RegExpCaret and - not containsLeadingPseudoAnchor(root.getChild([ 1 .. root.getNumChild() - 1 ])) and - containsLetters(root.getChild([ 1 .. root.getNumChild() - 1 ])) and + not containsLeadingPseudoAnchor(root.getChild([1 .. root.getNumChild() - 1])) and + containsLetters(root.getChild([1 .. root.getNumChild() - 1])) and direction = "beginning" or anchoredTerm = root.getLastChild() and anchoredTerm.getLastChild() instanceof RegExpDollar and - not containsTrailingPseudoAnchor(root.getChild([ 0 .. root.getNumChild() - 2 ])) and - containsLetters(root.getChild([ 0 .. root.getNumChild() - 2 ])) and + not containsTrailingPseudoAnchor(root.getChild([0 .. root.getNumChild() - 2])) and + containsLetters(root.getChild([0 .. root.getNumChild() - 2])) and direction = "end" ) and // is not used for replace @@ -146,8 +142,10 @@ predicate hasMisleadingAnchorPrecedence(RegExpPatternSource src, string msg) { replace.getMethodName() = "replace" and src.getARegExpObject().flowsTo(replace.getArgument(0)) ) and - msg = "Misleading operator precedence. The subexpression '" + anchoredTerm.getRawValue() + - "' is anchored at the " + direction + ", but the other parts of this regular expression are not" + msg = + "Misleading operator precedence. The subexpression '" + anchoredTerm.getRawValue() + + "' is anchored at the " + direction + + ", but the other parts of this regular expression are not" ) } @@ -181,7 +179,8 @@ predicate isSemiAnchoredHostnameRegExp(RegExpPatternSource src, string msg) { hasTopLevelDomainEnding(tld, i) and isFinalRegExpTerm(tld.getChild(i)) and // nothing is matched after the TLD tld.getChild(0) instanceof RegExpCaret and - msg = "This hostname pattern may match any domain name, as it is missing a '$' or '/' at the end." + msg = + "This hostname pattern may match any domain name, as it is missing a '$' or '/' at the end." ) } @@ -214,7 +213,8 @@ predicate isUnanchoredHostnameRegExp(RegExpPatternSource src, string msg) { name = "match" and exists(mcn.getAPropertyRead()) ) ) and - msg = "When this is used as a regular expression on a URL, it may match anywhere, and arbitrary hosts may come before or after it." + msg = + "When this is used as a regular expression on a URL, it may match anywhere, and arbitrary hosts may come before or after it." ) } diff --git a/javascript/ql/src/Security/CWE-020/UselessRegExpCharacterEscape.ql b/javascript/ql/src/Security/CWE-020/UselessRegExpCharacterEscape.ql index 7cace237bbb..062c0c2999c 100644 --- a/javascript/ql/src/Security/CWE-020/UselessRegExpCharacterEscape.ql +++ b/javascript/ql/src/Security/CWE-020/UselessRegExpCharacterEscape.ql @@ -105,13 +105,9 @@ class RegExpPatternMistake extends TRegExpPatternMistake { */ class IdentityEscapeInStringMistake extends RegExpPatternMistake, TIdentityEscapeInStringMistake { RegExpPatternSource src; - string char; - string mistake; - int index; - ASTNode rawStringNode; IdentityEscapeInStringMistake() { @@ -130,19 +126,19 @@ class IdentityEscapeInStringMistake extends RegExpPatternMistake, TIdentityEscap } /** - * A backspace as '\b' in a regular expression string, indicating - * programmer intent to use the word-boundary assertion '\b'. - */ + * A backspace as '\b' in a regular expression string, indicating + * programmer intent to use the word-boundary assertion '\b'. + */ class BackspaceInStringMistake extends RegExpPatternMistake, TBackspaceInStringMistake { RegExpPatternSource src; - int index; - ASTNode rawStringNode; BackspaceInStringMistake() { this = TBackspaceInStringMistake(src, rawStringNode, index) } - override string getMessage() { result = "'\\b' is a backspace, and not a word-boundary assertion" } + override string getMessage() { + result = "'\\b' is a backspace, and not a word-boundary assertion" + } override int getIndex() { result = index } diff --git a/javascript/ql/src/Security/CWE-078/CommandInjection.ql b/javascript/ql/src/Security/CWE-078/CommandInjection.ql index 62ca020753e..d1c22e33989 100644 --- a/javascript/ql/src/Security/CWE-078/CommandInjection.ql +++ b/javascript/ql/src/Security/CWE-078/CommandInjection.ql @@ -16,11 +16,16 @@ import javascript import semmle.javascript.security.dataflow.CommandInjection::CommandInjection import DataFlow::PathGraph -from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink, DataFlow::Node highlight, Source sourceNode +from + Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink, DataFlow::Node highlight, + Source sourceNode where cfg.hasFlowPath(source, sink) and - if cfg.isSinkWithHighlight(sink.getNode(), _) - then cfg.isSinkWithHighlight(sink.getNode(), highlight) - else highlight = sink.getNode() and + ( + if cfg.isSinkWithHighlight(sink.getNode(), _) + then cfg.isSinkWithHighlight(sink.getNode(), highlight) + else highlight = sink.getNode() + ) and sourceNode = source.getNode() -select highlight, source, sink, "This command depends on $@.", sourceNode, sourceNode.getSourceType() +select highlight, source, sink, "This command depends on $@.", sourceNode, + sourceNode.getSourceType() diff --git a/javascript/ql/src/Security/CWE-078/UselessUseOfCat.qhelp b/javascript/ql/src/Security/CWE-078/UselessUseOfCat.qhelp new file mode 100644 index 00000000000..5ef218bdf59 --- /dev/null +++ b/javascript/ql/src/Security/CWE-078/UselessUseOfCat.qhelp @@ -0,0 +1,45 @@ + + + +

    Using the unix command cat only to read a file is an +unnecessarily complex way to achieve something that can be done in a simpler +and safer manner using the Node.js fs.readFile API. +

    +

    +The use of cat for simple file reads leads to code that is +unportable, inefficient, complex, and can lead to subtle bugs or even +security vulnerabilities. +

    +
    + +

    +Use fs.readFile or fs.readFileSync to read files +from the file system. +

    +
    + + +

    The following example shows code that reads a file using cat:

    + + + +

    The code in the example will break if the input name contains +special characters (including space). Additionally, it does not work on Windows +and if the input is user-controlled, a command injection attack can happen.

    + +

    The fs.readFile API should be used to avoid these potential issues:

    + + + +
    + + +
  • OWASP: Command Injection.
  • +
  • Node.js: File System API.
  • +
  • The Useless Use of Cat Award.
  • + + +
    +
    diff --git a/javascript/ql/src/Security/CWE-078/UselessUseOfCat.ql b/javascript/ql/src/Security/CWE-078/UselessUseOfCat.ql new file mode 100644 index 00000000000..6b0ed59e632 --- /dev/null +++ b/javascript/ql/src/Security/CWE-078/UselessUseOfCat.ql @@ -0,0 +1,25 @@ +/** + * @name Unnecessary use of `cat` process + * @description Using the `cat` process to read a file is unnecessarily complex, inefficient, unportable, and can lead to subtle bugs, or even security vulnerabilities. + * @kind problem + * @problem.severity error + * @precision high + * @id js/unnecessary-use-of-cat + * @tags correctness + * security + * maintainability + */ + +import javascript +import semmle.javascript.security.UselessUseOfCat +import semmle.javascript.RestrictedLocations + +from UselessCat cat, string message +where + message = " Can be replaced with: " + PrettyPrintCatCall::createReadFileCall(cat) + or + not exists(PrettyPrintCatCall::createReadFileCall(cat)) and + if cat.isSync() + then message = " Can be replaced with a call to fs.readFileSync(..)." + else message = " Can be replaced with a call to fs.readFile(..)." +select cat.asExpr().(FirstLineOf), "Unnecessary use of `cat` process." + message diff --git a/javascript/ql/src/Security/CWE-078/examples/useless-cat-fixed.js b/javascript/ql/src/Security/CWE-078/examples/useless-cat-fixed.js new file mode 100644 index 00000000000..225fa1f5869 --- /dev/null +++ b/javascript/ql/src/Security/CWE-078/examples/useless-cat-fixed.js @@ -0,0 +1,5 @@ +var fs = require('fs'); + +module.exports = function (name) { + return fs.readFileSync(name).toString(); +}; diff --git a/javascript/ql/src/Security/CWE-078/examples/useless-cat.js b/javascript/ql/src/Security/CWE-078/examples/useless-cat.js new file mode 100644 index 00000000000..78f099d0e4c --- /dev/null +++ b/javascript/ql/src/Security/CWE-078/examples/useless-cat.js @@ -0,0 +1,5 @@ +var child_process = require('child_process'); + +module.exports = function (name) { + return child_process.execSync("cat " + name).toString(); +}; diff --git a/javascript/ql/src/Security/CWE-079/ExceptionXss.ql b/javascript/ql/src/Security/CWE-079/ExceptionXss.ql index cf22b54e465..f9e8ebfa774 100644 --- a/javascript/ql/src/Security/CWE-079/ExceptionXss.ql +++ b/javascript/ql/src/Security/CWE-079/ExceptionXss.ql @@ -15,10 +15,8 @@ import javascript import semmle.javascript.security.dataflow.ExceptionXss::ExceptionXss import DataFlow::PathGraph -from - Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink -where - cfg.hasFlowPath(source, sink) +from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink +where cfg.hasFlowPath(source, sink) select sink.getNode(), source, sink, - sink.getNode().(Xss::Shared::Sink).getVulnerabilityKind() + " vulnerability due to $@.", source.getNode(), - "user-provided value" + sink.getNode().(Xss::Shared::Sink).getVulnerabilityKind() + " vulnerability due to $@.", + source.getNode(), "user-provided value" diff --git a/javascript/ql/src/Security/CWE-116/DoubleEscaping.ql b/javascript/ql/src/Security/CWE-116/DoubleEscaping.ql index f4c62fc84fa..cc1bb1290e5 100644 --- a/javascript/ql/src/Security/CWE-116/DoubleEscaping.ql +++ b/javascript/ql/src/Security/CWE-116/DoubleEscaping.ql @@ -44,23 +44,17 @@ predicate escapingScheme(string metachar, string regex) { * A call to `String.prototype.replace` that replaces all instances of a pattern. */ class Replacement extends StringReplaceCall { - Replacement() { - isGlobal() - } + Replacement() { isGlobal() } /** * Gets the input of this replacement. */ - DataFlow::Node getInput() { - result = this.getReceiver() - } + DataFlow::Node getInput() { result = this.getReceiver() } /** * Gets the output of this replacement. */ - DataFlow::SourceNode getOutput() { - result = this - } + DataFlow::SourceNode getOutput() { result = this } /** * Holds if this replacement escapes `char` using `metachar`. @@ -93,9 +87,7 @@ class Replacement extends StringReplaceCall { /** * Gets the previous replacement in this chain of replacements. */ - Replacement getPreviousReplacement() { - result.getOutput() = getASimplePredecessor*(getInput()) - } + Replacement getPreviousReplacement() { result.getOutput() = getASimplePredecessor*(getInput()) } /** * Gets an earlier replacement in this chain of replacements that diff --git a/javascript/ql/src/Security/CWE-116/IncompleteSanitization.ql b/javascript/ql/src/Security/CWE-116/IncompleteSanitization.ql index 987014036e6..e5f6e4c04f2 100644 --- a/javascript/ql/src/Security/CWE-116/IncompleteSanitization.ql +++ b/javascript/ql/src/Security/CWE-116/IncompleteSanitization.ql @@ -44,9 +44,7 @@ predicate isSimpleCharacterClass(RegExpCharacterClass t) { } /** Holds if `t` is an alternation of simple terms. */ -predicate isSimpleAlt(RegExpAlt t) { - forall(RegExpTerm ch | ch = t.getAChild() | isSimple(ch)) -} +predicate isSimpleAlt(RegExpAlt t) { forall(RegExpTerm ch | ch = t.getAChild() | isSimple(ch)) } /** * Holds if `mce` is of the form `x.replace(re, new)`, where `re` is a global diff --git a/javascript/ql/src/Security/CWE-352/MissingCsrfMiddleware.ql b/javascript/ql/src/Security/CWE-352/MissingCsrfMiddleware.ql index f7db881fdf0..9f29ebc1ea2 100644 --- a/javascript/ql/src/Security/CWE-352/MissingCsrfMiddleware.ql +++ b/javascript/ql/src/Security/CWE-352/MissingCsrfMiddleware.ql @@ -13,26 +13,20 @@ import javascript /** Gets a property name of `req` which refers to data usually derived from cookie data. */ -string cookieProperty() { - result = "session" or result = "cookies" or result = "user" -} +string cookieProperty() { result = "session" or result = "cookies" or result = "user" } /** Gets a data flow node that flows to the base of an access to `cookies`, `session`, or `user`. */ private DataFlow::SourceNode nodeLeadingToCookieAccess(DataFlow::TypeBackTracker t) { t.start() and exists(DataFlow::PropRead value | value = result.getAPropertyRead(cookieProperty()).getAPropertyRead() and - // Ignore accesses to values that are part of a CSRF or captcha check not value.getPropertyName().regexpMatch("(?i).*(csrf|xsrf|captcha).*") and - // Ignore calls like `req.session.save()` not value = any(DataFlow::InvokeNode call).getCalleeNode() ) or - exists(DataFlow::TypeBackTracker t2 | - result = nodeLeadingToCookieAccess(t2).backtrack(t2, t) - ) + exists(DataFlow::TypeBackTracker t2 | result = nodeLeadingToCookieAccess(t2).backtrack(t2, t)) } /** Gets a data flow node that flows to the base of an access to `cookies`, `session`, or `user`. */ @@ -52,9 +46,7 @@ private DataFlow::SourceNode getARouteUsingCookies(DataFlow::TypeTracker t) { t.start() and isRouteHandlerUsingCookies(result) or - exists(DataFlow::TypeTracker t2 | - result = getARouteUsingCookies(t2).track(t2, t) - ) + exists(DataFlow::TypeTracker t2 | result = getARouteUsingCookies(t2).track(t2, t)) } /** Gets a data flow node referring to a route handler that uses cookies. */ @@ -113,7 +105,6 @@ from where router = setup.getRouter() and handler = setup.getARouteHandlerExpr() and - // Require that the handler uses cookies and has cookie middleware. // // In practice, handlers that use cookies always have the cookie middleware or @@ -122,10 +113,8 @@ where // don't trust it to detect the presence of CSRF middleware either. getARouteUsingCookies().flowsToExpr(handler) and hasCookieMiddleware(handler, cookie) and - // Only flag the cookie parser registered first. not hasCookieMiddleware(cookie, _) and - not hasCsrfMiddleware(handler) and // Only warn for the last handler in a chain. handler.isLastHandler() and diff --git a/javascript/ql/src/Security/CWE-400/PrototypePollutionUtility.qhelp b/javascript/ql/src/Security/CWE-400/PrototypePollutionUtility.qhelp index 49a77a7445d..a6a86043db5 100644 --- a/javascript/ql/src/Security/CWE-400/PrototypePollutionUtility.qhelp +++ b/javascript/ql/src/Security/CWE-400/PrototypePollutionUtility.qhelp @@ -13,7 +13,8 @@

    One way to cause prototype pollution is through use of an unsafe merge or extend function - to recursively copy properties from one object to another. + to recursively copy properties from one object to another, or through the use of a deep assignment + function to assign to an unverified chain of property names. Such a function has the potential to modify any object reachable from the destination object, and the built-in Object.prototype is usually reachable through the special properties __proto__ and constructor.prototype. @@ -23,13 +24,13 @@

    The most effective place to guard against this is in the function that performs - the recursive copy. + the recursive copy or deep assignment.

    - Only merge a property recursively when it is an own property of the destination object. + Only merge or assign a property recursively when it is an own property of the destination object. Alternatively, blacklist the property names __proto__ and constructor - from being merged. + from being merged or assigned to.

    diff --git a/javascript/ql/src/Security/CWE-400/PrototypePollutionUtility.ql b/javascript/ql/src/Security/CWE-400/PrototypePollutionUtility.ql index 59f637e95ef..4198b0f4686 100644 --- a/javascript/ql/src/Security/CWE-400/PrototypePollutionUtility.ql +++ b/javascript/ql/src/Security/CWE-400/PrototypePollutionUtility.ql @@ -1,7 +1,7 @@ /** * @name Prototype pollution in utility function - * @description Recursively copying properties between objects may cause - accidental modification of a built-in prototype object. + * @description Recursively assigning properties on objects may cause + * accidental modification of a built-in prototype object. * @kind path-problem * @problem.severity warning * @precision high @@ -16,6 +16,81 @@ import DataFlow import PathGraph import semmle.javascript.DynamicPropertyAccess +/** + * A call of form `x.split(".")` where `x` is a parameter. + * + * We restrict this to parameter nodes to focus on "deep assignment" functions. + */ +class SplitCall extends MethodCallNode { + SplitCall() { + getMethodName() = "split" and + getArgument(0).mayHaveStringValue(".") and + getReceiver().getALocalSource() instanceof ParameterNode + } +} + +/** + * Holds if `pred -> succ` should preserve polluted property names. + */ +predicate copyArrayStep(SourceNode pred, SourceNode succ) { + // x -> [...x] + exists(SpreadElement spread | + pred.flowsTo(spread.getOperand().flow()) and + succ.asExpr().(ArrayExpr).getAnElement() = spread + ) + or + // `x -> y` in `y.push( x[i] )` + exists(MethodCallNode push | + push = succ.getAMethodCall("push") and + ( + getAnEnumeratedArrayElement(pred).flowsTo(push.getAnArgument()) + or + pred.flowsTo(push.getASpreadArgument()) + ) + ) + or + // x -> x.concat(...) + exists(MethodCallNode concat_ | + concat_.getMethodName() = "concat" and + (pred = concat_.getReceiver() or pred = concat_.getAnArgument()) and + succ = concat_ + ) +} + +/** + * Holds if `node` may refer to a `SplitCall` or a copy thereof, possibly + * returned through a function call. + */ +predicate isSplitArray(SourceNode node) { + node instanceof SplitCall + or + exists(SourceNode pred | isSplitArray(pred) | + copyArrayStep(pred, node) + or + pred.flowsToExpr(node.(CallNode).getACallee().getAReturnedExpr()) + ) +} + +/** + * A property name originating from a `x.split(".")` call. + */ +class SplitPropName extends SourceNode { + SourceNode array; + + SplitPropName() { + isSplitArray(array) and + this = getAnEnumeratedArrayElement(array) + } + + /** + * Gets the array from which this property name was obtained (the result from `split`). + */ + SourceNode getArray() { result = array } + + /** Gets an element accessed on the same underlying array. */ + SplitPropName getAnAlias() { result.getArray() = getArray() } +} + /** * Holds if the properties of `node` are enumerated locally. */ @@ -24,15 +99,23 @@ predicate arePropertiesEnumerated(DataFlow::SourceNode node) { } /** - * Holds if `node` may flow from an enumerated prop name, possibly - * into function calls (but not returns). + * Holds if `node` is a source of property names that we consider possible + * prototype pollution payloads. */ -predicate isEnumeratedPropName(Node node) { +predicate isPollutedPropNameSource(DataFlow::Node node) { node instanceof EnumeratedPropName or - exists(Node pred | - isEnumeratedPropName(pred) - | + node instanceof SplitPropName +} + +/** + * Holds if `node` may flow from a source of polluted propery names, possibly + * into function calls (but not returns). + */ +predicate isPollutedPropName(Node node) { + isPollutedPropNameSource(node) + or + exists(Node pred | isPollutedPropName(pred) | node = pred.getASuccessor() or argumentPassingStep(_, pred, _, node) @@ -53,8 +136,7 @@ predicate isEnumeratedPropName(Node node) { predicate isPotentiallyObjectPrototype(SourceNode node) { exists(Node base, Node key | dynamicPropReadStep(base, key, node) and - isEnumeratedPropName(key) and - + isPollutedPropName(key) and // Ignore cases where the properties of `base` are enumerated, to avoid FPs // where the key came from that enumeration (and thus will not return Object.prototype). // For example, `src[key]` in `for (let key in src) { ... src[key] ... }` will generally @@ -62,9 +144,7 @@ predicate isPotentiallyObjectPrototype(SourceNode node) { not arePropertiesEnumerated(base.getALocalSource()) ) or - exists(Node use | - isPotentiallyObjectPrototype(use.getALocalSource()) - | + exists(Node use | isPotentiallyObjectPrototype(use.getALocalSource()) | argumentPassingStep(_, use, _, node) ) } @@ -87,11 +167,16 @@ predicate dynamicPropWrite(DataFlow::Node base, DataFlow::Node prop, DataFlow::N rhs = write.getRhs().flow() and not exists(prop.getStringValue()) and not arePropertiesEnumerated(base.getALocalSource()) and - // Prune writes that are unlikely to modify Object.prototype. // This is mainly for performance, but may block certain results due to // not tracking out of function returns and into callbacks. - isPotentiallyObjectPrototype(base.getALocalSource()) + isPotentiallyObjectPrototype(base.getALocalSource()) and + // Ignore writes with an obviously safe RHS. + not exists(Expr e | e = rhs.asExpr() | + e instanceof Literal or + e instanceof ObjectExpr or + e instanceof ArrayExpr + ) ) } @@ -147,10 +232,10 @@ class PropNameTracking extends DataFlow::Configuration { override predicate isSource(DataFlow::Node node, FlowLabel label) { label instanceof UnsafePropLabel and - exists(EnumeratedPropName prop | - node = prop + ( + isPollutedPropNameSource(node) or - node = prop.getASourceProp() + node = any(EnumeratedPropName prop).getASourceProp() ) } @@ -410,22 +495,29 @@ string deriveExprName(DataFlow::Node node) { result = getExprName(node) or not exists(getExprName(node)) and - result = "this object" + result = "here" } /** * Holds if the dynamic property write `base[prop] = rhs` can pollute the prototype - * of `base` due to flow from `enum`. + * of `base` due to flow from `propNameSource`. * * In most cases this will result in an alert, the exception being the case where * `base` does not have a prototype at all. */ -predicate isPrototypePollutingAssignment(Node base, Node prop, Node rhs, EnumeratedPropName enum) { +predicate isPrototypePollutingAssignment(Node base, Node prop, Node rhs, Node propNameSource) { dynamicPropWrite(base, prop, rhs) and + isPollutedPropNameSource(propNameSource) and exists(PropNameTracking cfg | - cfg.hasFlow(enum, base) and - cfg.hasFlow(enum, prop) and - cfg.hasFlow(enum.getASourceProp(), rhs) + cfg.hasFlow(propNameSource, base) and + if propNameSource instanceof EnumeratedPropName + then + cfg.hasFlow(propNameSource, prop) and + cfg.hasFlow(propNameSource.(EnumeratedPropName).getASourceProp(), rhs) + else ( + cfg.hasFlow(propNameSource.(SplitPropName).getAnAlias(), prop) and + rhs.getALocalSource() instanceof ParameterNode + ) ) } @@ -435,9 +527,7 @@ private DataFlow::SourceNode getANodeLeadingToBase(DataFlow::TypeBackTracker t, isPrototypePollutingAssignment(base, _, _, _) and result = base.getALocalSource() or - exists(DataFlow::TypeBackTracker t2 | - result = getANodeLeadingToBase(t2, base).backtrack(t2, t) - ) + exists(DataFlow::TypeBackTracker t2 | result = getANodeLeadingToBase(t2, base).backtrack(t2, t)) } /** @@ -472,18 +562,29 @@ class ObjectCreateNullCall extends CallNode { } from - PropNameTracking cfg, DataFlow::PathNode source, DataFlow::PathNode sink, EnumeratedPropName enum, - Node base + PropNameTracking cfg, DataFlow::PathNode source, DataFlow::PathNode sink, Node prop, Node base, + string msg, Node col1, Node col2 where + isPollutedPropName(prop) and cfg.hasFlowPath(source, sink) and - isPrototypePollutingAssignment(base, _, _, enum) and + isPrototypePollutingAssignment(base, _, _, prop) and sink.getNode() = base and - source.getNode() = enum and + source.getNode() = prop and ( getANodeLeadingToBaseBase(base) instanceof ObjectLiteralNode or not getANodeLeadingToBaseBase(base) instanceof ObjectCreateNullCall + ) and + // Generate different messages for deep merge and deep assign cases. + if prop instanceof EnumeratedPropName + then ( + col1 = prop.(EnumeratedPropName).getSourceObject() and + col2 = base and + msg = "Properties are copied from $@ to $@ without guarding against prototype pollution." + ) else ( + col1 = prop and + col2 = base and + msg = + "The property chain $@ is recursively assigned to $@ without guarding against prototype pollution." ) -select base, source, sink, - "Properties are copied from $@ to $@ without guarding against prototype pollution.", - enum.getSourceObject(), deriveExprName(enum.getSourceObject()), base, deriveExprName(base) +select base, source, sink, msg, col1, deriveExprName(col1), col2, deriveExprName(col2) diff --git a/javascript/ql/src/Statements/IgnoreArrayResult.ql b/javascript/ql/src/Statements/IgnoreArrayResult.ql index 6f6698797f1..9123b520f6f 100644 --- a/javascript/ql/src/Statements/IgnoreArrayResult.ql +++ b/javascript/ql/src/Statements/IgnoreArrayResult.ql @@ -13,7 +13,7 @@ import javascript import Expressions.ExprHasNoEffect DataFlow::SourceNode callsArray(DataFlow::TypeBackTracker t, DataFlow::MethodCallNode call) { - isIgnoredPureArrayCall(call) and + isIgnoredPureArrayCall(call) and t.start() and result = call.getReceiver().getALocalSource() or @@ -39,7 +39,7 @@ predicate isIgnoredPureArrayCall(DataFlow::MethodCallNode call) { } from DataFlow::MethodCallNode call -where +where callsArray(call) instanceof DataFlow::ArrayCreationNode and not call.getReceiver().asExpr().(ArrayExpr).getSize() = 0 select call, "Result from call to " + call.getMethodName() + " ignored." diff --git a/javascript/ql/src/Statements/ImplicitReturn.ql b/javascript/ql/src/Statements/ImplicitReturn.ql index b729c678240..e9de1026737 100644 --- a/javascript/ql/src/Statements/ImplicitReturn.ql +++ b/javascript/ql/src/Statements/ImplicitReturn.ql @@ -32,7 +32,8 @@ class ValueReturn extends ReturnStmt { /** Gets the lexically first explicit return statement in function `f`. */ ValueReturn getFirstExplicitReturn(Function f) { - result = min(ValueReturn ret | + result = + min(ValueReturn ret | ret.getContainer() = f | ret order by ret.getLocation().getStartLine(), ret.getLocation().getStartColumn() diff --git a/javascript/ql/src/Statements/UseOfReturnlessFunction.ql b/javascript/ql/src/Statements/UseOfReturnlessFunction.ql index 35825b01bb4..834183ce914 100644 --- a/javascript/ql/src/Statements/UseOfReturnlessFunction.ql +++ b/javascript/ql/src/Statements/UseOfReturnlessFunction.ql @@ -21,40 +21,41 @@ predicate returnsVoid(Function f) { } predicate isStub(Function f) { - f.getBody().(BlockStmt).getNumChild() = 0 - or - f instanceof ExternalDecl + f.getBody().(BlockStmt).getNumChild() = 0 + or + f instanceof ExternalDecl } /** * Holds if `e` is in a syntactic context where it likely is fine that the value of `e` comes from a call to a returnless function. */ predicate benignContext(Expr e) { - inVoidContext(e) or - + inVoidContext(e) + or // A return statement is often used to just end the function. e = any(Function f).getBody() or e = any(ReturnStmt r).getExpr() - or - exists(ConditionalExpr cond | cond.getABranch() = e and benignContext(cond)) - or - exists(LogicalBinaryExpr bin | bin.getAnOperand() = e and benignContext(bin)) + or + exists(ConditionalExpr cond | cond.getABranch() = e and benignContext(cond)) + or + exists(LogicalBinaryExpr bin | bin.getAnOperand() = e and benignContext(bin)) or exists(Expr parent | parent.getUnderlyingValue() = e and benignContext(parent)) - or + or any(VoidExpr voidExpr).getOperand() = e or // weeds out calls inside HTML-attributes. - e.getParent().(ExprStmt).getParent() instanceof CodeInAttribute or + e.getParent().(ExprStmt).getParent() instanceof CodeInAttribute + or // and JSX-attributes. - e = any(JSXAttribute attr).getValue() or - - exists(AwaitExpr await | await.getOperand() = e and benignContext(await)) + e = any(JSXAttribute attr).getValue() + or + exists(AwaitExpr await | await.getOperand() = e and benignContext(await)) or // Avoid double reporting with js/trivial-conditional isExplicitConditional(_, e) - or + or // Avoid double reporting with js/comparison-between-incompatible-types any(Comparison binOp).getAnOperand() = e or @@ -62,12 +63,14 @@ predicate benignContext(Expr e) { any(PropAccess ac).getBase() = e or // Avoid double-reporting with js/unused-local-variable - exists(VariableDeclarator v | v.getInit() = e and v.getBindingPattern().getVariable() instanceof UnusedLocal) + exists(VariableDeclarator v | + v.getInit() = e and v.getBindingPattern().getVariable() instanceof UnusedLocal + ) or // Avoid double reporting with js/call-to-non-callable any(InvokeExpr invoke).getCallee() = e or - // arguments to Promise.resolve (and promise library variants) are benign. + // arguments to Promise.resolve (and promise library variants) are benign. e = any(PromiseCreationCall promise).getValue().asExpr() } @@ -86,15 +89,13 @@ predicate alwaysThrows(Function f) { /** * Holds if the last statement in the function is flagged by the js/useless-expression query. */ -predicate lastStatementHasNoEffect(Function f) { - hasNoEffect(f.getExit().getAPredecessor()) -} +predicate lastStatementHasNoEffect(Function f) { hasNoEffect(f.getExit().getAPredecessor()) } /** * Holds if `func` is a callee of `call`, and all possible callees of `call` never return a value. */ predicate callToVoidFunction(DataFlow::CallNode call, Function func) { - not call.isIncomplete() and + not call.isIncomplete() and func = call.getACallee() and forall(Function f | f = call.getACallee() | returnsVoid(f) and not isStub(f) and not alwaysThrows(f) @@ -122,22 +123,20 @@ predicate hasNonVoidCallbackMethod(string name) { DataFlow::SourceNode array(DataFlow::TypeTracker t) { t.start() and result instanceof DataFlow::ArrayCreationNode or - exists (DataFlow::TypeTracker t2 | - result = array(t2).track(t2, t) - ) + exists(DataFlow::TypeTracker t2 | result = array(t2).track(t2, t)) } DataFlow::SourceNode array() { result = array(DataFlow::TypeTracker::end()) } /** * Holds if `call` is an Array or Lodash method accepting a callback `func`, - * where the `call` expects a callback that returns an expression, - * but `func` does not return a value. + * where the `call` expects a callback that returns an expression, + * but `func` does not return a value. */ predicate voidArrayCallback(DataFlow::CallNode call, Function func) { hasNonVoidCallbackMethod(call.getCalleeName()) and - exists(int index | - index = min(int i | exists(call.getCallback(i))) and + exists(int index | + index = min(int i | exists(call.getCallback(i))) and func = call.getCallback(index).getFunction() ) and returnsVoid(func) and @@ -151,26 +150,23 @@ predicate voidArrayCallback(DataFlow::CallNode call, Function func) { } predicate hasNonVoidReturnType(Function f) { - exists(TypeAnnotation type | type = f.getReturnTypeAnnotation() | - not type.isVoid() - ) + exists(TypeAnnotation type | type = f.getReturnTypeAnnotation() | not type.isVoid()) } - /** - * Provides classes for working with various Deferred implementations. - * It is a heuristic. The heuristic assume that a class is a promise defintion + * Provides classes for working with various Deferred implementations. + * It is a heuristic. The heuristic assume that a class is a promise defintion * if the class is called "Deferred" and the method `resolve` is called on an instance. - * - * Removes some false positives in the js/use-of-returnless-function query. + * + * Removes some false positives in the js/use-of-returnless-function query. */ module Deferred { /** - * An instance of a `Deferred` class. + * An instance of a `Deferred` class. * For example the result from `new Deferred()` or `new $.Deferred()`. */ class DeferredInstance extends DataFlow::NewNode { - // Describes both `new Deferred()`, `new $.Deferred` and other variants. + // Describes both `new Deferred()`, `new $.Deferred` and other variants. DeferredInstance() { this.getCalleeName() = "Deferred" } private DataFlow::SourceNode ref(DataFlow::TypeTracker t) { @@ -179,7 +175,7 @@ module Deferred { or exists(DataFlow::TypeTracker t2 | result = ref(t2).track(t2, t)) } - + DataFlow::SourceNode ref() { result = ref(DataFlow::TypeTracker::end()) } } @@ -188,7 +184,7 @@ module Deferred { */ private class DeferredPromiseDefinition extends PromiseDefinition, DeferredInstance { DeferredPromiseDefinition() { - // hardening of the "Deferred" heuristic: a method call to `resolve`. + // hardening of the "Deferred" heuristic: a method call to `resolve`. exists(ref().getAMethodCall("resolve")) } @@ -210,12 +206,14 @@ module Deferred { from DataFlow::CallNode call, Function func, string name, string msg where ( - callToVoidFunction(call, func) and + callToVoidFunction(call, func) and msg = "the $@ does not return anything, yet the return value is used." and name = func.describe() or - voidArrayCallback(call, func) and - msg = "the $@ does not return anything, yet the return value from the call to " + call.getCalleeName() + " is used." and + voidArrayCallback(call, func) and + msg = + "the $@ does not return anything, yet the return value from the call to " + + call.getCalleeName() + " is used." and name = "callback function" ) and not benignContext(call.getEnclosingExpr()) and @@ -224,5 +222,4 @@ where not oneshotClosure(call) and not hasNonVoidReturnType(func) and not call.getEnclosingExpr() instanceof SuperCall -select - call, msg, func, name +select call, msg, func, name diff --git a/javascript/ql/src/Statements/UselessConditional.qll b/javascript/ql/src/Statements/UselessConditional.qll index bcb4d88a734..9197a2b8a0f 100644 --- a/javascript/ql/src/Statements/UselessConditional.qll +++ b/javascript/ql/src/Statements/UselessConditional.qll @@ -1,6 +1,6 @@ -/** - * Provides predicates for working with useless conditionals. - */ +/** + * Provides predicates for working with useless conditionals. + */ import javascript @@ -18,4 +18,4 @@ predicate isExplicitConditional(ASTNode cond, Expr e) { or isExplicitConditional(_, cond) and e = cond.(Expr).getUnderlyingValue().(LogicalBinaryExpr).getAnOperand() -} \ No newline at end of file +} diff --git a/javascript/ql/src/experimental/README.md b/javascript/ql/src/experimental/README.md new file mode 100644 index 00000000000..fc07363b24f --- /dev/null +++ b/javascript/ql/src/experimental/README.md @@ -0,0 +1 @@ +This directory contains [experimental](../../../../docs/experimental.md) CodeQL queries and libraries. diff --git a/javascript/ql/src/Security/Summaries/Configurations.qll b/javascript/ql/src/experimental/Summaries/Configurations.qll similarity index 100% rename from javascript/ql/src/Security/Summaries/Configurations.qll rename to javascript/ql/src/experimental/Summaries/Configurations.qll diff --git a/javascript/ql/src/Security/Summaries/ExtractFlowStepSummaries.ql b/javascript/ql/src/experimental/Summaries/ExtractFlowStepSummaries.ql similarity index 91% rename from javascript/ql/src/Security/Summaries/ExtractFlowStepSummaries.ql rename to javascript/ql/src/experimental/Summaries/ExtractFlowStepSummaries.ql index 1a3670013ed..14f51f07646 100644 --- a/javascript/ql/src/Security/Summaries/ExtractFlowStepSummaries.ql +++ b/javascript/ql/src/experimental/Summaries/ExtractFlowStepSummaries.ql @@ -14,8 +14,9 @@ import PortalExitSource import PortalEntrySink from - TaintTracking::Configuration cfg, DataFlow::SourcePathNode source, DataFlow::SinkPathNode sink, Portal p1, - Portal p2, DataFlow::FlowLabel lbl1, DataFlow::FlowLabel lbl2, DataFlow::MidPathNode last + TaintTracking::Configuration cfg, DataFlow::SourcePathNode source, DataFlow::SinkPathNode sink, + Portal p1, Portal p2, DataFlow::FlowLabel lbl1, DataFlow::FlowLabel lbl2, + DataFlow::MidPathNode last where cfg = source.getConfiguration() and last = source.getASuccessor*() and diff --git a/javascript/ql/src/Security/Summaries/ExtractSinkSummaries.ql b/javascript/ql/src/experimental/Summaries/ExtractSinkSummaries.ql similarity index 89% rename from javascript/ql/src/Security/Summaries/ExtractSinkSummaries.ql rename to javascript/ql/src/experimental/Summaries/ExtractSinkSummaries.ql index 588fe796811..ddc1c950740 100644 --- a/javascript/ql/src/Security/Summaries/ExtractSinkSummaries.ql +++ b/javascript/ql/src/experimental/Summaries/ExtractSinkSummaries.ql @@ -11,7 +11,8 @@ import Configurations import PortalExitSource import SinkFromAnnotation -from DataFlow::Configuration cfg, DataFlow::SourcePathNode source, DataFlow::SinkPathNode sink, +from + DataFlow::Configuration cfg, DataFlow::SourcePathNode source, DataFlow::SinkPathNode sink, Portal p, DataFlow::MidPathNode last where cfg = source.getConfiguration() and diff --git a/javascript/ql/src/Security/Summaries/ExtractSourceSummaries.ql b/javascript/ql/src/experimental/Summaries/ExtractSourceSummaries.ql similarity index 89% rename from javascript/ql/src/Security/Summaries/ExtractSourceSummaries.ql rename to javascript/ql/src/experimental/Summaries/ExtractSourceSummaries.ql index de7ddf04b3f..86741b692b8 100644 --- a/javascript/ql/src/Security/Summaries/ExtractSourceSummaries.ql +++ b/javascript/ql/src/experimental/Summaries/ExtractSourceSummaries.ql @@ -11,7 +11,8 @@ import Configurations import PortalEntrySink import SourceFromAnnotation -from DataFlow::Configuration cfg, DataFlow::SourcePathNode source, DataFlow::SinkPathNode sink, +from + DataFlow::Configuration cfg, DataFlow::SourcePathNode source, DataFlow::SinkPathNode sink, Portal p, DataFlow::MidPathNode last where cfg = source.getConfiguration() and diff --git a/javascript/ql/src/Security/Summaries/ImportFromCsv.qll b/javascript/ql/src/experimental/Summaries/ImportFromCsv.qll similarity index 97% rename from javascript/ql/src/Security/Summaries/ImportFromCsv.qll rename to javascript/ql/src/experimental/Summaries/ImportFromCsv.qll index 55018ef3ca5..461bdb27e8f 100644 --- a/javascript/ql/src/Security/Summaries/ImportFromCsv.qll +++ b/javascript/ql/src/experimental/Summaries/ImportFromCsv.qll @@ -127,7 +127,8 @@ class AdditionalStepSpec extends ExternalData { exists(string config | if getField(4) = "" then config = "any configuration" else config = getConfiguration() | - result = "edge from " + getStartPortal() + " to " + getEndPortal() + ", transforming " + + result = + "edge from " + getStartPortal() + " to " + getEndPortal() + ", transforming " + getStartFlowLabel() + " into " + getEndFlowLabel() + " for " + config ) } diff --git a/javascript/ql/src/Security/Summaries/ImportFromExternalPredicates.qll b/javascript/ql/src/experimental/Summaries/ImportFromExternalPredicates.qll similarity index 100% rename from javascript/ql/src/Security/Summaries/ImportFromExternalPredicates.qll rename to javascript/ql/src/experimental/Summaries/ImportFromExternalPredicates.qll diff --git a/javascript/ql/src/Security/Summaries/PortalEntrySink.qll b/javascript/ql/src/experimental/Summaries/PortalEntrySink.qll similarity index 100% rename from javascript/ql/src/Security/Summaries/PortalEntrySink.qll rename to javascript/ql/src/experimental/Summaries/PortalEntrySink.qll diff --git a/javascript/ql/src/Security/Summaries/PortalExitSource.qll b/javascript/ql/src/experimental/Summaries/PortalExitSource.qll similarity index 100% rename from javascript/ql/src/Security/Summaries/PortalExitSource.qll rename to javascript/ql/src/experimental/Summaries/PortalExitSource.qll diff --git a/javascript/ql/src/Security/Summaries/Shared.qll b/javascript/ql/src/experimental/Summaries/Shared.qll similarity index 100% rename from javascript/ql/src/Security/Summaries/Shared.qll rename to javascript/ql/src/experimental/Summaries/Shared.qll diff --git a/javascript/ql/src/Security/Summaries/SinkFromAnnotation.qll b/javascript/ql/src/experimental/Summaries/SinkFromAnnotation.qll similarity index 100% rename from javascript/ql/src/Security/Summaries/SinkFromAnnotation.qll rename to javascript/ql/src/experimental/Summaries/SinkFromAnnotation.qll diff --git a/javascript/ql/src/Security/Summaries/SourceFromAnnotation.qll b/javascript/ql/src/experimental/Summaries/SourceFromAnnotation.qll similarity index 100% rename from javascript/ql/src/Security/Summaries/SourceFromAnnotation.qll rename to javascript/ql/src/experimental/Summaries/SourceFromAnnotation.qll diff --git a/javascript/ql/src/external/CodeDuplication.qll b/javascript/ql/src/external/CodeDuplication.qll index 1573962265c..d0f9d97776a 100644 --- a/javascript/ql/src/external/CodeDuplication.qll +++ b/javascript/ql/src/external/CodeDuplication.qll @@ -266,7 +266,8 @@ predicate similarLines(File f, int line) { } private predicate similarLinesPerEquivalenceClass(int equivClass, int lines, File f) { - lines = strictsum(SimilarBlock b, int toSum | + lines = + strictsum(SimilarBlock b, int toSum | (b.sourceFile() = f and b.getEquivalenceClass() = equivClass) and toSum = b.sourceLines() | @@ -278,7 +279,8 @@ pragma[noopt] private predicate similarLinesCovered(File f, int coveredLines, File otherFile) { exists(int numLines | numLines = f.getNumberOfLines() | exists(int coveredApprox | - coveredApprox = strictsum(int num | + coveredApprox = + strictsum(int num | exists(int equivClass | similarLinesPerEquivalenceClass(equivClass, num, f) and similarLinesPerEquivalenceClass(equivClass, num, otherFile) and @@ -301,7 +303,8 @@ predicate duplicateLines(File f, int line) { } private predicate duplicateLinesPerEquivalenceClass(int equivClass, int lines, File f) { - lines = strictsum(DuplicateBlock b, int toSum | + lines = + strictsum(DuplicateBlock b, int toSum | (b.sourceFile() = f and b.getEquivalenceClass() = equivClass) and toSum = b.sourceLines() | @@ -313,7 +316,8 @@ pragma[noopt] private predicate duplicateLinesCovered(File f, int coveredLines, File otherFile) { exists(int numLines | numLines = f.getNumberOfLines() | exists(int coveredApprox | - coveredApprox = strictsum(int num | + coveredApprox = + strictsum(int num | exists(int equivClass | duplicateLinesPerEquivalenceClass(equivClass, num, f) and duplicateLinesPerEquivalenceClass(equivClass, num, otherFile) and diff --git a/javascript/ql/src/external/DefectFilter.qll b/javascript/ql/src/external/DefectFilter.qll index dcac0cf7b5e..d35e41d9afd 100644 --- a/javascript/ql/src/external/DefectFilter.qll +++ b/javascript/ql/src/external/DefectFilter.qll @@ -48,7 +48,8 @@ class DefectResult extends int { /** Gets the URL corresponding to the location of this query result. */ string getURL() { - result = "file://" + getFile().getAbsolutePath() + ":" + getStartLine() + ":" + getStartColumn() - + ":" + getEndLine() + ":" + getEndColumn() + result = + "file://" + getFile().getAbsolutePath() + ":" + getStartLine() + ":" + getStartColumn() + ":" + + getEndLine() + ":" + getEndColumn() } } diff --git a/javascript/ql/src/external/MetricFilter.qll b/javascript/ql/src/external/MetricFilter.qll index 43ac75ef889..5edecac75d8 100644 --- a/javascript/ql/src/external/MetricFilter.qll +++ b/javascript/ql/src/external/MetricFilter.qll @@ -66,7 +66,8 @@ class MetricResult extends int { /** Gets the URL corresponding to the location of this query result. */ string getURL() { - result = "file://" + getFile().getAbsolutePath() + ":" + getStartLine() + ":" + getStartColumn() - + ":" + getEndLine() + ":" + getEndColumn() + result = + "file://" + getFile().getAbsolutePath() + ":" + getStartLine() + ":" + getStartColumn() + ":" + + getEndLine() + ":" + getEndColumn() } } diff --git a/javascript/ql/src/meta/analysis-quality/CallGraphQuality.qll b/javascript/ql/src/meta/analysis-quality/CallGraphQuality.qll index 80c20a02143..3535ab5974c 100644 --- a/javascript/ql/src/meta/analysis-quality/CallGraphQuality.qll +++ b/javascript/ql/src/meta/analysis-quality/CallGraphQuality.qll @@ -86,9 +86,7 @@ SourceNode nodeLeadingToInvocation() { * Holds if there is a call edge `invoke -> f` between a relevant invocation * and a relevant function. */ -predicate relevantCall(RelevantInvoke invoke, RelevantFunction f) { - FlowSteps::calls(invoke, f) -} +predicate relevantCall(RelevantInvoke invoke, RelevantFunction f) { FlowSteps::calls(invoke, f) } /** * A call site that can be resolved to a function in the same project. @@ -105,9 +103,7 @@ class ResolvableCall extends RelevantInvoke { * A call site that could not be resolved. */ class UnresolvableCall extends RelevantInvoke { - UnresolvableCall() { - not this instanceof ResolvableCall - } + UnresolvableCall() { not this instanceof ResolvableCall } } /** diff --git a/javascript/ql/src/meta/analysis-quality/UnresolvableImports.ql b/javascript/ql/src/meta/analysis-quality/UnresolvableImports.ql index a2a35e8f11c..88f51d8c237 100644 --- a/javascript/ql/src/meta/analysis-quality/UnresolvableImports.ql +++ b/javascript/ql/src/meta/analysis-quality/UnresolvableImports.ql @@ -11,8 +11,6 @@ import javascript import CallGraphQuality -Import unresolvableImport() { - not exists(result.getImportedModule()) -} +Import unresolvableImport() { not exists(result.getImportedModule()) } select projectRoot(), count(unresolvableImport()) diff --git a/javascript/ql/src/meta/extraction-metrics/MissingMetrics.ql b/javascript/ql/src/meta/extraction-metrics/MissingMetrics.ql index 76216331f50..4f0b708b481 100644 --- a/javascript/ql/src/meta/extraction-metrics/MissingMetrics.ql +++ b/javascript/ql/src/meta/extraction-metrics/MissingMetrics.ql @@ -12,7 +12,8 @@ import javascript import semmle.javascript.meta.ExtractionMetrics::ExtractionMetrics from File f, string cause -where not extraction_data(f, _, _, _) and cause = "No extraction_data for this file" - or - not extraction_time(f, _,_, _) and cause = "No extraction_time for this file" -select f, cause \ No newline at end of file +where + not extraction_data(f, _, _, _) and cause = "No extraction_data for this file" + or + not extraction_time(f, _, _, _) and cause = "No extraction_time for this file" +select f, cause diff --git a/javascript/ql/src/meta/extraction-metrics/PhaseTimings.ql b/javascript/ql/src/meta/extraction-metrics/PhaseTimings.ql index a9fd872eea1..20e4bab4d35 100644 --- a/javascript/ql/src/meta/extraction-metrics/PhaseTimings.ql +++ b/javascript/ql/src/meta/extraction-metrics/PhaseTimings.ql @@ -9,4 +9,5 @@ import semmle.javascript.meta.ExtractionMetrics::ExtractionMetrics from PhaseName phaseName -select phaseName, Aggregated::getCpuTime(phaseName) as CPU_NANO, Aggregated::getWallclockTime(phaseName) as WALLCLOCK_NANO +select phaseName, Aggregated::getCpuTime(phaseName) as CPU_NANO, + Aggregated::getWallclockTime(phaseName) as WALLCLOCK_NANO diff --git a/javascript/ql/src/semmle/javascript/BasicBlocks.qll b/javascript/ql/src/semmle/javascript/BasicBlocks.qll index 0e0ebfabbc5..f6ee06df0f8 100644 --- a/javascript/ql/src/semmle/javascript/BasicBlocks.qll +++ b/javascript/ql/src/semmle/javascript/BasicBlocks.qll @@ -235,7 +235,8 @@ class BasicBlock extends @cfg_node, Locatable { */ private int nextDefOrUseAfter(PurelyLocalVariable v, int i, VarDef d) { defAt(i, v, d) and - result = min(int j | + result = + min(int j | (defAt(j, v, _) or useAt(j, v, _) or j = length()) and j > i ) diff --git a/javascript/ql/src/semmle/javascript/CharacterEscapes.qll b/javascript/ql/src/semmle/javascript/CharacterEscapes.qll index 33c2c01ec65..2d2a3c5f3a8 100644 --- a/javascript/ql/src/semmle/javascript/CharacterEscapes.qll +++ b/javascript/ql/src/semmle/javascript/CharacterEscapes.qll @@ -57,7 +57,8 @@ module CharacterEscapes { hasRawStringAndQuote(n, delim, rawStringNode, raw) and if rawStringNode instanceof RegExpLiteral then - additionalEscapeChars = Sets::regexpMetaChars() + Sets::regexpAssertionChars() + Sets::regexpCharClassChars() + + additionalEscapeChars = + Sets::regexpMetaChars() + Sets::regexpAssertionChars() + Sets::regexpCharClassChars() + Sets::regexpBackreferenceChars() else additionalEscapeChars = "b" | diff --git a/javascript/ql/src/semmle/javascript/Classes.qll b/javascript/ql/src/semmle/javascript/Classes.qll index 793ff5a92e4..e93e0565ce8 100644 --- a/javascript/ql/src/semmle/javascript/Classes.qll +++ b/javascript/ql/src/semmle/javascript/Classes.qll @@ -123,13 +123,14 @@ class ClassOrInterface extends @classorinterface, TypeParameterized { * Anonymous classes and interfaces do not have a canonical name. */ TypeName getTypeName() { result.getADefinition() = this } - + /** * Gets the ClassOrInterface corresponding to either a super type or an implemented interface. */ ClassOrInterface getASuperTypeDeclaration() { this.getSuperClass().(VarAccess).getVariable().getADeclaration() = result.getIdentifier() or - this.getASuperInterface().(LocalTypeAccess).getLocalTypeName().getADeclaration() = result.getIdentifier() + this.getASuperInterface().(LocalTypeAccess).getLocalTypeName().getADeclaration() = + result.getIdentifier() } } @@ -334,11 +335,8 @@ class ClassExpr extends @classexpr, ClassDefinition, Expr { else if exists(getClassInitializedMember()) then - result = min(ClassInitializedMember m | - m = getClassInitializedMember() - | - m order by m.getIndex() - ) + result = + min(ClassInitializedMember m | m = getClassInitializedMember() | m order by m.getIndex()) else result = this } @@ -688,7 +686,8 @@ class MethodDeclaration extends MemberDeclaration { */ int getOverloadIndex() { exists(ClassOrInterface type, string name | - this = rank[result + 1](MethodDeclaration method, int i | + this = + rank[result + 1](MethodDeclaration method, int i | methodDeclaredInType(type, name, i, method) | method order by i @@ -696,7 +695,8 @@ class MethodDeclaration extends MemberDeclaration { ) or exists(ClassDefinition type | - this = rank[result + 1](ConstructorDeclaration ctor, int i | + this = + rank[result + 1](ConstructorDeclaration ctor, int i | ctor = type.getMemberByIndex(i) | ctor order by i @@ -1156,7 +1156,8 @@ class FunctionCallSignature extends @function_call_signature, CallSignature { /** Gets the index of this function call signature among the function call signatures in the enclosing type. */ int getOverloadIndex() { exists(ClassOrInterface type | type = getDeclaringType() | - this = rank[result + 1](FunctionCallSignature sig, int i | + this = + rank[result + 1](FunctionCallSignature sig, int i | sig = type.getMemberByIndex(i) | sig order by i @@ -1186,7 +1187,8 @@ class ConstructorCallSignature extends @constructor_call_signature, CallSignatur /** Gets the index of this constructor call signature among the constructor call signatures in the enclosing type. */ int getOverloadIndex() { exists(ClassOrInterface type | type = getDeclaringType() | - this = rank[result + 1](ConstructorCallSignature sig, int i | + this = + rank[result + 1](ConstructorCallSignature sig, int i | sig = type.getMemberByIndex(i) | sig order by i diff --git a/javascript/ql/src/semmle/javascript/Concepts.qll b/javascript/ql/src/semmle/javascript/Concepts.qll index 23bbbce3b16..798747af8b8 100644 --- a/javascript/ql/src/semmle/javascript/Concepts.qll +++ b/javascript/ql/src/semmle/javascript/Concepts.qll @@ -22,6 +22,14 @@ abstract class SystemCommandExecution extends DataFlow::Node { * to the command. */ DataFlow::Node getArgumentList() { none() } + + /** Holds if the command execution happens synchronously. */ + abstract predicate isSync(); + + /** + * Gets the data-flow node (if it exists) for an options argument. + */ + abstract DataFlow::Node getOptionsArg(); } /** diff --git a/javascript/ql/src/semmle/javascript/DefUse.qll b/javascript/ql/src/semmle/javascript/DefUse.qll index 204545c05fb..8c0c008256e 100644 --- a/javascript/ql/src/semmle/javascript/DefUse.qll +++ b/javascript/ql/src/semmle/javascript/DefUse.qll @@ -284,7 +284,8 @@ private SsaDefinition getAPseudoDefinitionInput(SsaDefinition nd) { */ private int nextDefAfter(BasicBlock bb, Variable v, int i, VarDef d) { bb.defAt(i, v, d) and - result = min(int jj | + result = + min(int jj | (bb.defAt(jj, v, _) or jj = bb.length()) and jj > i ) diff --git a/javascript/ql/src/semmle/javascript/DynamicPropertyAccess.qll b/javascript/ql/src/semmle/javascript/DynamicPropertyAccess.qll index 807e4265b26..456fd773f64 100644 --- a/javascript/ql/src/semmle/javascript/DynamicPropertyAccess.qll +++ b/javascript/ql/src/semmle/javascript/DynamicPropertyAccess.qll @@ -11,7 +11,7 @@ private import semmle.javascript.dataflow.internal.FlowSteps * Gets a node that refers to an element of `array`, likely obtained * as a result of enumerating the elements of the array. */ -private SourceNode getAnEnumeratedArrayElement(SourceNode array) { +SourceNode getAnEnumeratedArrayElement(SourceNode array) { exists(MethodCallNode call, string name | call = array.getAMethodCall(name) and (name = "forEach" or name = "map") and diff --git a/javascript/ql/src/semmle/javascript/ES2015Modules.qll b/javascript/ql/src/semmle/javascript/ES2015Modules.qll index ca50b3cdcc6..df094df2952 100644 --- a/javascript/ql/src/semmle/javascript/ES2015Modules.qll +++ b/javascript/ql/src/semmle/javascript/ES2015Modules.qll @@ -78,9 +78,7 @@ class ImportDeclaration extends Stmt, Import, @importdeclaration { } /** Holds if this is declared with the `type` keyword, so it only imports types. */ - predicate isTypeOnly() { - hasTypeKeyword(this) - } + predicate isTypeOnly() { hasTypeKeyword(this) } override predicate isAmbient() { Stmt.super.isAmbient() or @@ -268,9 +266,7 @@ abstract class ExportDeclaration extends Stmt, @exportdeclaration { abstract DataFlow::Node getSourceNode(string name); /** Holds if is declared with the `type` keyword, so only types are exported. */ - predicate isTypeOnly() { - hasTypeKeyword(this) - } + predicate isTypeOnly() { hasTypeKeyword(this) } override predicate isAmbient() { Stmt.super.isAmbient() or @@ -614,15 +610,10 @@ abstract class ReExportDeclaration extends ExportDeclaration { * * Gets the module from which this declaration re-exports. */ - deprecated - ES2015Module getImportedModule() { - result = getReExportedModule() - } + deprecated ES2015Module getImportedModule() { result = getReExportedModule() } /** Gets the module from which this declaration re-exports, if it is an ES2015 module. */ - ES2015Module getReExportedES2015Module() { - result = getReExportedModule() - } + ES2015Module getReExportedES2015Module() { result = getReExportedModule() } /** Gets the module from which this declaration re-exports. */ Module getReExportedModule() { @@ -635,7 +626,8 @@ abstract class ReExportDeclaration extends ExportDeclaration { * Gets a module in a `node_modules/@types/` folder that matches the imported module name. */ private Module resolveFromTypeRoot() { - result.getFile() = min(TypeRootFolder typeRoot | + result.getFile() = + min(TypeRootFolder typeRoot | | typeRoot.getModuleFile(getImportedPath().getStringValue()) order by diff --git a/javascript/ql/src/semmle/javascript/EmailClients.qll b/javascript/ql/src/semmle/javascript/EmailClients.qll index e84148c8152..11d12fa0658 100644 --- a/javascript/ql/src/semmle/javascript/EmailClients.qll +++ b/javascript/ql/src/semmle/javascript/EmailClients.qll @@ -43,9 +43,8 @@ abstract class EmailSender extends DataFlow::SourceNode { */ private class NodemailerEmailSender extends EmailSender, DataFlow::MethodCallNode { NodemailerEmailSender() { - this = DataFlow::moduleMember("nodemailer", "createTransport") - .getACall() - .getAMethodCall("sendMail") + this = + DataFlow::moduleMember("nodemailer", "createTransport").getACall().getAMethodCall("sendMail") } override DataFlow::Node getPlainTextBody() { result = getOptionArgument(0, "text") } diff --git a/javascript/ql/src/semmle/javascript/Expr.qll b/javascript/ql/src/semmle/javascript/Expr.qll index b43a38306ce..e835176c7ad 100644 --- a/javascript/ql/src/semmle/javascript/Expr.qll +++ b/javascript/ql/src/semmle/javascript/Expr.qll @@ -253,12 +253,15 @@ class Expr extends @expr, ExprOrStmt, ExprOrType, AST::ValueNode { DataFlow::Node getExceptionTarget() { if exists(this.getEnclosingStmt().getEnclosingTryCatchStmt()) then - result = DataFlow::parameterNode(this + result = + DataFlow::parameterNode(this .getEnclosingStmt() .getEnclosingTryCatchStmt() .getACatchClause() .getAParameter()) - else result = any(DataFlow::FunctionNode f | f.getFunction() = this.getContainer()).getExceptionalReturn() + else + result = + any(DataFlow::FunctionNode f | f.getFunction() = this.getContainer()).getExceptionalReturn() } } diff --git a/javascript/ql/src/semmle/javascript/Files.qll b/javascript/ql/src/semmle/javascript/Files.qll index 09b31bd7915..0ac6df39c65 100644 --- a/javascript/ql/src/semmle/javascript/Files.qll +++ b/javascript/ql/src/semmle/javascript/Files.qll @@ -183,11 +183,8 @@ class Folder extends Container, @folder { * HTML files will not be found by this method. */ File getJavaScriptFile(string stem) { - result = min(int p, string ext | - p = getFileExtensionPriority(ext) - | - getFile(stem, ext) order by p - ) + result = + min(int p, string ext | p = getFileExtensionPriority(ext) | getFile(stem, ext) order by p) } /** Gets a subfolder contained in this folder. */ diff --git a/javascript/ql/src/semmle/javascript/Functions.qll b/javascript/ql/src/semmle/javascript/Functions.qll index c8f41e76570..d6f0f965df7 100644 --- a/javascript/ql/src/semmle/javascript/Functions.qll +++ b/javascript/ql/src/semmle/javascript/Functions.qll @@ -206,7 +206,8 @@ class Function extends @function, Parameterized, TypeParameterized, StmtContaine /** Gets the cyclomatic complexity of this function. */ int getCyclomaticComplexity() { - result = 2 + + result = + 2 + sum(Expr nd | nd.getContainer() = this and nd.isBranch() | @@ -420,9 +421,7 @@ class Function extends @function, Parameterized, TypeParameterized, StmtContaine /** * Gets the call signature of this function, as determined by the TypeScript compiler, if any. */ - CallSignatureType getCallSignature() { - declared_function_signature(this, result) - } + CallSignatureType getCallSignature() { declared_function_signature(this, result) } } /** diff --git a/javascript/ql/src/semmle/javascript/GeneratedCode.qll b/javascript/ql/src/semmle/javascript/GeneratedCode.qll index 0f8b161e7ae..e2f8e81d3c9 100644 --- a/javascript/ql/src/semmle/javascript/GeneratedCode.qll +++ b/javascript/ql/src/semmle/javascript/GeneratedCode.qll @@ -35,8 +35,10 @@ class CodeGeneratorMarkerComment extends GeneratedCodeMarkerComment { */ private predicate codeGeneratorMarkerComment(Comment c, string tool) { exists(string toolPattern | - toolPattern = "js_of_ocaml|CoffeeScript|LiveScript|dart2js|ANTLR|PEG\\.js|Opal|JSX|jison(?:-lex)?|(?:Microsoft \\(R\\) AutoRest Code Generator)|purs" and - tool = c + toolPattern = + "js_of_ocaml|CoffeeScript|LiveScript|dart2js|ANTLR|PEG\\.js|Opal|JSX|jison(?:-lex)?|(?:Microsoft \\(R\\) AutoRest Code Generator)|purs" and + tool = + c .getText() .regexpCapture("(?s)[\\s*]*(?:parser |Code )?[gG]eneratedy? (?:from .*)?by (" + toolPattern + ")\\b.*", 1) diff --git a/javascript/ql/src/semmle/javascript/GlobalAccessPaths.qll b/javascript/ql/src/semmle/javascript/GlobalAccessPaths.qll index 2a3d8f963a2..5d260b98ac7 100644 --- a/javascript/ql/src/semmle/javascript/GlobalAccessPaths.qll +++ b/javascript/ql/src/semmle/javascript/GlobalAccessPaths.qll @@ -5,23 +5,18 @@ import javascript private import semmle.javascript.dataflow.InferredTypes -deprecated -module GlobalAccessPath { +deprecated module GlobalAccessPath { /** * DEPRECATED. Instead use `AccessPath::getAReferenceTo` with the result and parameter reversed. */ pragma[inline] - string fromReference(DataFlow::Node node) { - node = AccessPath::getAReferenceTo(result) - } + string fromReference(DataFlow::Node node) { node = AccessPath::getAReferenceTo(result) } /** * DEPRECATED. Instead use `AccessPath::getAnAssignmentTo` with the result and parameter reversed. */ pragma[inline] - string fromRhs(DataFlow::Node node) { - node = AccessPath::getAnAssignmentTo(result) - } + string fromRhs(DataFlow::Node node) { node = AccessPath::getAnAssignmentTo(result) } /** * DEPRECATED. Use `AccessPath::getAReferenceOrAssignmentTo`. @@ -67,9 +62,7 @@ module AccessPath { } /** Holds if this represents the root of the global access path. */ - predicate isGlobal() { - this = DataFlow::globalAccessPathRootPseudoNode() - } + predicate isGlobal() { this = DataFlow::globalAccessPathRootPseudoNode() } } /** @@ -212,7 +205,8 @@ module AccessPath { * ``` */ private predicate isSelfAssignment(DataFlow::Node rhs) { - fromRhs(rhs, DataFlow::globalAccessPathRootPseudoNode()) = fromReference(rhs, DataFlow::globalAccessPathRootPseudoNode()) + fromRhs(rhs, DataFlow::globalAccessPathRootPseudoNode()) = + fromReference(rhs, DataFlow::globalAccessPathRootPseudoNode()) } /** @@ -418,8 +412,8 @@ module AccessPath { */ pragma[inline] DataFlow::SourceNode getAnAliasedSourceNode(DataFlow::Node node) { - exists(DataFlow::SourceNode root, string accessPath | - node = AccessPath::getAReferenceTo(root, accessPath) and + exists(DataFlow::SourceNode root, string accessPath | + node = AccessPath::getAReferenceTo(root, accessPath) and result = AccessPath::getAReferenceTo(root, accessPath) ) or diff --git a/javascript/ql/src/semmle/javascript/HtmlSanitizers.qll b/javascript/ql/src/semmle/javascript/HtmlSanitizers.qll index e624b1310bb..47f88f0390c 100644 --- a/javascript/ql/src/semmle/javascript/HtmlSanitizers.qll +++ b/javascript/ql/src/semmle/javascript/HtmlSanitizers.qll @@ -49,9 +49,8 @@ private class DefaultHtmlSanitizerCall extends HtmlSanitizerCall { callee = LodashUnderscore::member("escape") or exists(string name | name = "encode" or name = "encodeNonUTF" | - callee = DataFlow::moduleMember("html-entities", _) - .getAnInstantiation() - .getAPropertyRead(name) or + callee = + DataFlow::moduleMember("html-entities", _).getAnInstantiation().getAPropertyRead(name) or callee = DataFlow::moduleMember("html-entities", _).getAPropertyRead(name) ) or diff --git a/javascript/ql/src/semmle/javascript/InclusionTests.qll b/javascript/ql/src/semmle/javascript/InclusionTests.qll index 7a90dbf1583..e7152f96a94 100644 --- a/javascript/ql/src/semmle/javascript/InclusionTests.qll +++ b/javascript/ql/src/semmle/javascript/InclusionTests.qll @@ -1,6 +1,7 @@ /** * Contains classes for recognizing array and string inclusion tests. */ + private import javascript /** @@ -57,6 +58,40 @@ module InclusionTest { boolean getPolarity() { result = true } } + /** + * A call to a utility function (`callee`) that performs an InclusionTest (`inner`). + */ + private class IndirectInclusionTest extends Range, DataFlow::CallNode { + InclusionTest inner; + Function callee; + + IndirectInclusionTest() { + inner.getEnclosingExpr() = callee.getAReturnedExpr() and + this.getACallee() = callee and + count(this.getACallee()) = 1 and + count(callee.getAReturnedExpr()) = 1 and + not this.isImprecise() and + inner.getContainerNode().getALocalSource().getEnclosingExpr() = callee.getAParameter() and + inner.getContainedNode().getALocalSource().getEnclosingExpr() = callee.getAParameter() + } + + override DataFlow::Node getContainerNode() { + exists(int arg | + inner.getContainerNode().getALocalSource().getEnclosingExpr() = callee.getParameter(arg) and + result = this.getArgument(arg) + ) + } + + override DataFlow::Node getContainedNode() { + exists(int arg | + inner.getContainedNode().getALocalSource().getEnclosingExpr() = callee.getParameter(arg) and + result = this.getArgument(arg) + ) + } + + override boolean getPolarity() { result = inner.getPolarity() } + } + /** * A call to a method named `includes`, assumed to refer to `String.prototype.includes` * or `Array.prototype.includes`. diff --git a/javascript/ql/src/semmle/javascript/Locations.qll b/javascript/ql/src/semmle/javascript/Locations.qll index aa7dc6210b3..72939c8b419 100644 --- a/javascript/ql/src/semmle/javascript/Locations.qll +++ b/javascript/ql/src/semmle/javascript/Locations.qll @@ -142,5 +142,6 @@ class Locatable extends @locatable { */ private class FileLocatable extends File, Locatable { override Location getLocation() { result = File.super.getLocation() } + override string toString() { result = File.super.toString() } } diff --git a/javascript/ql/src/semmle/javascript/Modules.qll b/javascript/ql/src/semmle/javascript/Modules.qll index 26b2331afcf..2352e2ba496 100644 --- a/javascript/ql/src/semmle/javascript/Modules.qll +++ b/javascript/ql/src/semmle/javascript/Modules.qll @@ -140,7 +140,8 @@ abstract class Import extends ASTNode { * Gets a module in a `node_modules/@types/` folder that matches the imported module name. */ private Module resolveFromTypeRoot() { - result.getFile() = min(TypeRootFolder typeRoot | + result.getFile() = + min(TypeRootFolder typeRoot | | typeRoot.getModuleFile(getImportedPath().getValue()) order by diff --git a/javascript/ql/src/semmle/javascript/NodeJS.qll b/javascript/ql/src/semmle/javascript/NodeJS.qll index bf88039ff77..f184c0e1c38 100644 --- a/javascript/ql/src/semmle/javascript/NodeJS.qll +++ b/javascript/ql/src/semmle/javascript/NodeJS.qll @@ -243,7 +243,8 @@ class Require extends CallExpr, Import { private File load(int priority) { exists(int r | getEnclosingModule().searchRoot(getImportedPath(), _, r) | result = loadAsFile(this, r, priority - prioritiesPerCandidate() * r) or - result = loadAsDirectory(this, r, + result = + loadAsDirectory(this, r, priority - (prioritiesPerCandidate() * r + numberOfExtensions() + 1)) ) } diff --git a/javascript/ql/src/semmle/javascript/NodeModuleResolutionImpl.qll b/javascript/ql/src/semmle/javascript/NodeModuleResolutionImpl.qll index 83023fabb6f..20354a1f086 100644 --- a/javascript/ql/src/semmle/javascript/NodeModuleResolutionImpl.qll +++ b/javascript/ql/src/semmle/javascript/NodeModuleResolutionImpl.qll @@ -92,7 +92,7 @@ File resolveMainModule(PackageJSON pkg, int priority) { not exists(main.resolve()) and not exists(main.getExtension()) and exists(int n | n = main.getNumComponent() | - result = tryExtensions(main.resolveUpTo(n-1), main.getComponent(n-1), priority) + result = tryExtensions(main.resolveUpTo(n - 1), main.getComponent(n - 1), priority) ) ) else result = tryExtensions(pkg.getFile().getParentContainer(), "index", priority) diff --git a/javascript/ql/src/semmle/javascript/Promises.qll b/javascript/ql/src/semmle/javascript/Promises.qll index cac97e54e09..413938ec3f7 100644 --- a/javascript/ql/src/semmle/javascript/Promises.qll +++ b/javascript/ql/src/semmle/javascript/Promises.qll @@ -128,17 +128,13 @@ private module PromiseFlow { /** * Gets the pseudo-field used to describe resolved values in a promise. */ - string resolveField() { - result = "$PromiseResolveField$" - } + string resolveField() { result = "$PromiseResolveField$" } /** * Gets the pseudo-field used to describe rejected values in a promise. */ - string rejectField() { - result = "$PromiseRejectField$" - } - + string rejectField() { result = "$PromiseRejectField$" } + /** * A flow step describing a promise definition. * @@ -146,9 +142,8 @@ private module PromiseFlow { */ class PromiseDefitionStep extends DataFlow::AdditionalFlowStep { PromiseDefinition promise; - PromiseDefitionStep() { - this = promise - } + + PromiseDefitionStep() { this = promise } override predicate storeStep(DataFlow::Node pred, DataFlow::Node succ, string prop) { prop = resolveField() and @@ -170,15 +165,14 @@ private module PromiseFlow { succ = this } } - + /** * A flow step describing the a Promise.resolve (and similar) call. */ class CreationStep extends DataFlow::AdditionalFlowStep { PromiseCreationCall promise; - CreationStep() { - this = promise - } + + CreationStep() { this = promise } override predicate storeStep(DataFlow::Node pred, DataFlow::Node succ, string prop) { prop = resolveField() and @@ -194,7 +188,6 @@ private module PromiseFlow { } } - /** * A load step loading the pseudo-field describing that the promise is rejected. * The rejected value is thrown as a exception. @@ -202,6 +195,7 @@ private module PromiseFlow { class AwaitStep extends DataFlow::AdditionalFlowStep { DataFlow::Node operand; AwaitExpr await; + AwaitStep() { this.getEnclosingExpr() = await and operand.getEnclosingExpr() = await.getOperand() @@ -222,9 +216,7 @@ private module PromiseFlow { * A flow step describing the data-flow related to the `.then` method of a promise. */ class ThenStep extends DataFlow::AdditionalFlowStep, DataFlow::MethodCallNode { - ThenStep() { - this.getMethodName() = "then" - } + ThenStep() { this.getMethodName() = "then" } override predicate loadStep(DataFlow::Node pred, DataFlow::Node succ, string prop) { prop = resolveField() and @@ -235,7 +227,7 @@ private module PromiseFlow { pred = getReceiver() and succ = getCallback(1).getParameter(0) } - + override predicate loadStoreStep(DataFlow::Node pred, DataFlow::Node succ, string prop) { not exists(this.getArgument(1)) and prop = rejectField() and @@ -244,17 +236,17 @@ private module PromiseFlow { or // read the value of a resolved/rejected promise that is returned (prop = rejectField() or prop = resolveField()) and - pred = getCallback([0..1]).getAReturn() and + pred = getCallback([0 .. 1]).getAReturn() and succ = this } - + override predicate storeStep(DataFlow::Node pred, DataFlow::Node succ, string prop) { prop = resolveField() and - pred = getCallback([0..1]).getAReturn() and + pred = getCallback([0 .. 1]).getAReturn() and succ = this or prop = rejectField() and - pred = getCallback([0..1]).getExceptionalReturn() and + pred = getCallback([0 .. 1]).getExceptionalReturn() and succ = this } } @@ -263,9 +255,7 @@ private module PromiseFlow { * A flow step describing the data-flow related to the `.catch` method of a promise. */ class CatchStep extends DataFlow::AdditionalFlowStep, DataFlow::MethodCallNode { - CatchStep() { - this.getMethodName() = "catch" - } + CatchStep() { this.getMethodName() = "catch" } override predicate loadStep(DataFlow::Node pred, DataFlow::Node succ, string prop) { prop = rejectField() and @@ -299,9 +289,7 @@ private module PromiseFlow { * A flow step describing the data-flow related to the `.finally` method of a promise. */ class FinallyStep extends DataFlow::AdditionalFlowStep, DataFlow::MethodCallNode { - FinallyStep() { - this.getMethodName() = "finally" - } + FinallyStep() { this.getMethodName() = "finally" } override predicate loadStoreStep(DataFlow::Node pred, DataFlow::Node succ, string prop) { (prop = resolveField() or prop = rejectField()) and @@ -332,15 +320,13 @@ predicate promiseTaintStep(DataFlow::Node pred, DataFlow::Node succ) { // from `x` to `Promise.resolve(x)` pred = succ.(PromiseCreationCall).getValue() or - exists(DataFlow::MethodCallNode thn | - thn.getMethodName() = "then" - | + exists(DataFlow::MethodCallNode thn | thn.getMethodName() = "then" | // from `p` to `x` in `p.then(x => ...)` pred = thn.getReceiver() and succ = thn.getCallback(0).getParameter(0) or // from `v` to `p.then(x => return v)` - pred = thn.getCallback([0..1]).getAReturn() and + pred = thn.getCallback([0 .. 1]).getAReturn() and succ = thn ) or @@ -406,22 +392,19 @@ module Bluebird { override DataFlow::Node getValue() { result = getArgument(0) } } - + /** - * An aggregated promise produced either by `Promise.all`, `Promise.race` or `Promise.map`. + * An aggregated promise produced either by `Promise.all`, `Promise.race` or `Promise.map`. */ class AggregateBluebirdPromiseDefinition extends PromiseCreationCall { AggregateBluebirdPromiseDefinition() { - exists(string m | m = "all" or m = "race" or m = "map" | - this = bluebird().getAMemberCall(m) - ) + exists(string m | m = "all" or m = "race" or m = "map" | this = bluebird().getAMemberCall(m)) } override DataFlow::Node getValue() { result = getArgument(0).getALocalSource().(DataFlow::ArrayCreationNode).getAnElement() } } - } /** diff --git a/javascript/ql/src/semmle/javascript/Regexp.qll b/javascript/ql/src/semmle/javascript/Regexp.qll index e3c45261537..e4fdef07783 100644 --- a/javascript/ql/src/semmle/javascript/Regexp.qll +++ b/javascript/ql/src/semmle/javascript/Regexp.qll @@ -296,9 +296,7 @@ class RegExpSequence extends RegExpTerm, @regexp_seq { forall(RegExpTerm child | child = getAChild() | child.isNullable()) } - override string getConstantValue() { - result = getConstantValue(0) - } + override string getConstantValue() { result = getConstantValue(0) } /** * Gets the single string matched by the `i`th child and all following children of @@ -308,13 +306,11 @@ class RegExpSequence extends RegExpTerm, @regexp_seq { i = getNumChild() and result = "" or - result = getChild(i).getConstantValue() + getConstantValue(i+1) + 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 preceding `element` in this sequence. */ + RegExpTerm previousElement(RegExpTerm element) { element = nextElement(result) } /** Gets the element following `element` in this sequence. */ RegExpTerm nextElement(RegExpTerm element) { @@ -834,7 +830,7 @@ class RegExpParseError extends Error, @regexp_parse_error { } /** - * Holds if `func` is a method defined on `String.prototype` with name `name`. + * Holds if `func` is a method defined on `String.prototype` with name `name`. */ private predicate isNativeStringMethod(Function func, string name) { exists(ExternalInstanceMemberDecl decl | @@ -856,9 +852,7 @@ predicate isInterpretedAsRegExp(DataFlow::Node source) { exists(DataFlow::MethodCallNode mce, string methodName | mce.getReceiver().analyze().getAType() = TTString() and mce.getMethodName() = methodName and - not exists(Function func | - func = mce.getACallee() - | + not exists(Function func | func = mce.getACallee() | not isNativeStringMethod(func, methodName) ) | diff --git a/javascript/ql/src/semmle/javascript/RestrictedLocations.qll b/javascript/ql/src/semmle/javascript/RestrictedLocations.qll index 4146f177009..8c45ad533ba 100644 --- a/javascript/ql/src/semmle/javascript/RestrictedLocations.qll +++ b/javascript/ql/src/semmle/javascript/RestrictedLocations.qll @@ -25,9 +25,8 @@ class FirstLineOf extends Locatable { if xl = startline then endcolumn = xc else - endcolumn = max(int c | - any(Location l).hasLocationInfo(filepath, startline, _, startline, c) - ) + endcolumn = + max(int c | any(Location l).hasLocationInfo(filepath, startline, _, startline, c)) ) } } diff --git a/javascript/ql/src/semmle/javascript/SSA.qll b/javascript/ql/src/semmle/javascript/SSA.qll index b1b3a904955..a3d74e4f724 100644 --- a/javascript/ql/src/semmle/javascript/SSA.qll +++ b/javascript/ql/src/semmle/javascript/SSA.qll @@ -647,9 +647,7 @@ class SsaPhiNode extends SsaPseudoDefinition, TPhi { result = getDefReachingEndOf(bb, getSourceVariable()) } - override SsaVariable getAnInput() { - result = getInputFromBlock(_) - } + override SsaVariable getAnInput() { result = getInputFromBlock(_) } override predicate definesAt(ReachableBasicBlock bb, int i, SsaSourceVariable v) { bb = getBasicBlock() and v = getSourceVariable() and i = -1 @@ -676,9 +674,7 @@ class SsaPhiNode extends SsaPseudoDefinition, TPhi { * gets that variable. */ SsaVariable getRephinedVariable() { - forex(SsaVariable input | input = getAnInput() | - result = getRefinedVariable(input) - ) + forex(SsaVariable input | input = getAnInput() | result = getRefinedVariable(input)) } } diff --git a/javascript/ql/src/semmle/javascript/StandardLibrary.qll b/javascript/ql/src/semmle/javascript/StandardLibrary.qll index aae98553faf..b5ae842789e 100644 --- a/javascript/ql/src/semmle/javascript/StandardLibrary.qll +++ b/javascript/ql/src/semmle/javascript/StandardLibrary.qll @@ -49,7 +49,8 @@ class DirectEval extends CallExpr { * Models `Array.prototype.map` and friends as partial invocations that pass their second * argument as the receiver to the callback. */ -private class ArrayIterationCallbackAsPartialInvoke extends DataFlow::PartialInvokeNode::Range, DataFlow::MethodCallNode { +private class ArrayIterationCallbackAsPartialInvoke extends DataFlow::PartialInvokeNode::Range, + DataFlow::MethodCallNode { ArrayIterationCallbackAsPartialInvoke() { getNumArgument() = 2 and // Filter out library methods named 'forEach' etc diff --git a/javascript/ql/src/semmle/javascript/Stmt.qll b/javascript/ql/src/semmle/javascript/Stmt.qll index c96d6509e8d..7ec69344aa7 100644 --- a/javascript/ql/src/semmle/javascript/Stmt.qll +++ b/javascript/ql/src/semmle/javascript/Stmt.qll @@ -55,15 +55,15 @@ class Stmt extends @stmt, ExprOrStmt, Documentable { } override predicate isAmbient() { hasDeclareKeyword(this) or getParent().isAmbient() } - + /** - * Gets the `try` statement with a catch block containing this statement without - * crossing function boundaries or other `try ` statements with catch blocks. - */ + * Gets the `try` statement with a catch block containing this statement without + * crossing function boundaries or other `try ` statements with catch blocks. + */ TryStmt getEnclosingTryCatchStmt() { getParentStmt+() = result.getBody() and exists(result.getACatchClause()) and - not exists(TryStmt mid | exists(mid.getACatchClause()) | + not exists(TryStmt mid | exists(mid.getACatchClause()) | getParentStmt+() = mid.getBody() and mid.getParentStmt+() = result.getBody() ) } diff --git a/javascript/ql/src/semmle/javascript/StringOps.qll b/javascript/ql/src/semmle/javascript/StringOps.qll index fac784cb49f..6a4a2760524 100644 --- a/javascript/ql/src/semmle/javascript/StringOps.qll +++ b/javascript/ql/src/semmle/javascript/StringOps.qll @@ -56,6 +56,40 @@ module StringOps { boolean getPolarity() { result = true } } + /** + * A call to a utility function (`callee`) that performs a StartsWith check (`inner`). + */ + private class IndirectStartsWith extends Range, DataFlow::CallNode { + StartsWith inner; + Function callee; + + IndirectStartsWith() { + inner.getEnclosingExpr() = callee.getAReturnedExpr() and + this.getACallee() = callee and + count(this.getACallee()) = 1 and + count(callee.getAReturnedExpr()) = 1 and + not this.isImprecise() and + inner.getBaseString().getALocalSource().getEnclosingExpr() = callee.getAParameter() and + inner.getSubstring().getALocalSource().getEnclosingExpr() = callee.getAParameter() + } + + override DataFlow::Node getBaseString() { + exists(int arg | + inner.getBaseString().getALocalSource().getEnclosingExpr() = callee.getParameter(arg) and + result = this.getArgument(arg) + ) + } + + override DataFlow::Node getSubstring() { + exists(int arg | + inner.getSubstring().getALocalSource().getEnclosingExpr() = callee.getParameter(arg) and + result = this.getArgument(arg) + ) + } + + override boolean getPolarity() { result = inner.getPolarity() } + } + /** * An expression of form `A.startsWith(B)`. */ @@ -165,10 +199,16 @@ module StringOps { StartsWith_Substring() { astNode.hasOperands(call.asExpr(), substring.asExpr()) and - (call.getMethodName() = "substring" or call.getMethodName() = "substr" or call.getMethodName() = "slice") and + ( + call.getMethodName() = "substring" or + call.getMethodName() = "substr" or + call.getMethodName() = "slice" + ) and call.getNumArgument() = 2 and ( - AccessPath::getAnAliasedSourceNode(substring).getAPropertyRead("length").flowsTo(call.getArgument(1)) + AccessPath::getAnAliasedSourceNode(substring) + .getAPropertyRead("length") + .flowsTo(call.getArgument(1)) or substring.getStringValue().length() = call.getArgument(1).asExpr().getIntValue() ) @@ -247,6 +287,40 @@ module StringOps { boolean getPolarity() { result = true } } + /** + * A call to a utility function (`callee`) that performs an EndsWith check (`inner`). + */ + private class IndirectEndsWith extends Range, DataFlow::CallNode { + EndsWith inner; + Function callee; + + IndirectEndsWith() { + inner.getEnclosingExpr() = callee.getAReturnedExpr() and + this.getACallee() = callee and + count(this.getACallee()) = 1 and + count(callee.getAReturnedExpr()) = 1 and + not this.isImprecise() and + inner.getBaseString().getALocalSource().getEnclosingExpr() = callee.getAParameter() and + inner.getSubstring().getALocalSource().getEnclosingExpr() = callee.getAParameter() + } + + override DataFlow::Node getBaseString() { + exists(int arg | + inner.getBaseString().getALocalSource().getEnclosingExpr() = callee.getParameter(arg) and + result = this.getArgument(arg) + ) + } + + override DataFlow::Node getSubstring() { + exists(int arg | + inner.getSubstring().getALocalSource().getEnclosingExpr() = callee.getParameter(arg) and + result = this.getArgument(arg) + ) + } + + override boolean getPolarity() { result = inner.getPolarity() } + } + /** * A call of form `A.endsWith(B)`. */ @@ -502,7 +576,8 @@ module StringOps { result = getStringValue() or not exists(getStringValue()) and - result = strictconcat(StringLiteralLike leaf | + result = + strictconcat(StringLiteralLike leaf | leaf = getALeaf().asExpr() | leaf.getStringValue() order by leaf.getFirstToken().getIndex() diff --git a/javascript/ql/src/semmle/javascript/TypeScript.qll b/javascript/ql/src/semmle/javascript/TypeScript.qll index 7821c463d8e..c6497355763 100644 --- a/javascript/ql/src/semmle/javascript/TypeScript.qll +++ b/javascript/ql/src/semmle/javascript/TypeScript.qll @@ -939,9 +939,7 @@ class PredicateTypeExpr extends @predicatetypeexpr, TypeExpr { /** * Holds if this is a type of form `asserts E is T` or `asserts E`. */ - predicate hasAssertsKeyword() { - hasAssertsKeyword(this) - } + predicate hasAssertsKeyword() { hasAssertsKeyword(this) } } /** @@ -954,9 +952,7 @@ class PredicateTypeExpr extends @predicatetypeexpr, TypeExpr { * ``` */ class IsTypeExpr extends PredicateTypeExpr { - IsTypeExpr() { - exists(getPredicateType()) - } + IsTypeExpr() { exists(getPredicateType()) } } /** @@ -2312,18 +2308,14 @@ class EnumLiteralType extends TypeReference { * A type that refers to a type alias. */ class TypeAliasReference extends TypeReference { - TypeAliasReference() { - type_alias(this, _) - } + TypeAliasReference() { type_alias(this, _) } /** * Gets the type behind the type alias. * * For example, for `type B = T[][]`, this maps the type `B` to `number[][]`. */ - Type getAliasedType() { - type_alias(this, result) - } + Type getAliasedType() { type_alias(this, result) } } /** @@ -2635,9 +2627,7 @@ class CallSignatureType extends @signature_type { * * For example, for the signature `(...y: string[])`, this gets the type `string[]`. */ - PlainArrayType getRestParameterArrayType() { - signature_rest_parameter(this, result) - } + PlainArrayType getRestParameterArrayType() { signature_rest_parameter(this, result) } } /** diff --git a/javascript/ql/src/semmle/javascript/Variables.qll b/javascript/ql/src/semmle/javascript/Variables.qll index df4dccaf987..5c5c3e3f115 100644 --- a/javascript/ql/src/semmle/javascript/Variables.qll +++ b/javascript/ql/src/semmle/javascript/Variables.qll @@ -189,10 +189,8 @@ class Variable extends @variable, LexicalName { */ predicate isCaptured() { this instanceof GlobalVariable or - getAnAccess().getContainer().getFunctionBoundary() != this - .(LocalVariable) - .getDeclaringContainer() - .getFunctionBoundary() + getAnAccess().getContainer().getFunctionBoundary() != + this.(LocalVariable).getDeclaringContainer().getFunctionBoundary() } /** Holds if there is a declaration of this variable in `tl`. */ @@ -764,9 +762,7 @@ class Parameter extends BindingPattern { * function f(x?: number) {} * ``` */ - predicate isDeclaredOptional() { - isOptionalParameterDeclaration(this) - } + predicate isDeclaredOptional() { isOptionalParameterDeclaration(this) } } /** diff --git a/javascript/ql/src/semmle/javascript/dataflow/BackwardExploration.qll b/javascript/ql/src/semmle/javascript/dataflow/BackwardExploration.qll index caf67e6db7f..29ed748dc68 100644 --- a/javascript/ql/src/semmle/javascript/dataflow/BackwardExploration.qll +++ b/javascript/ql/src/semmle/javascript/dataflow/BackwardExploration.qll @@ -18,9 +18,7 @@ import javascript private class BackwardExploringConfiguration extends DataFlow::Configuration { DataFlow::Configuration cfg; - BackwardExploringConfiguration() { - this = cfg - } + BackwardExploringConfiguration() { this = cfg } override predicate isSource(DataFlow::Node node) { any() } diff --git a/javascript/ql/src/semmle/javascript/dataflow/Configuration.qll b/javascript/ql/src/semmle/javascript/dataflow/Configuration.qll index f71b6982f1e..807733f309a 100644 --- a/javascript/ql/src/semmle/javascript/dataflow/Configuration.qll +++ b/javascript/ql/src/semmle/javascript/dataflow/Configuration.qll @@ -149,7 +149,7 @@ abstract class Configuration extends string { predicate isBarrier(DataFlow::Node node) { exists(BarrierGuardNode guard | isBarrierGuardInternal(guard) and - guard.internalBlocks(node, "") + barrierGuardBlocksNode(guard, node, "") ) } @@ -183,7 +183,7 @@ abstract class Configuration extends string { predicate isLabeledBarrier(DataFlow::Node node, FlowLabel lbl) { exists(BarrierGuardNode guard | isBarrierGuardInternal(guard) and - guard.internalBlocks(node, lbl) + barrierGuardBlocksNode(guard, node, lbl) ) or none() // relax type inference to account for overriding @@ -199,6 +199,10 @@ abstract class Configuration extends string { */ predicate isBarrierGuard(BarrierGuardNode guard) { none() } + /** + * Holds if `guard` is a barrier guard for this configuration, added through + * `isBarrierGuard` or `AdditionalBarrierGuardNode`. + */ private predicate isBarrierGuardInternal(BarrierGuardNode guard) { isBarrierGuard(guard) or @@ -319,44 +323,6 @@ module FlowLabel { * implementations of `blocks` will _both_ apply to any configuration that includes either of them. */ abstract class BarrierGuardNode extends DataFlow::Node { - /** - * Holds if data flow node `nd` acts as a barrier for data flow, possibly due to aliasing - * through an access path. - * - * `label` is bound to the blocked label, or the empty string if all labels should be blocked. - * - * INTERNAL: this predicate should only be used from within `blocks(boolean, Expr)`. - */ - predicate internalBlocks(DataFlow::Node nd, string label) { - // 1) `nd` is a use of a refinement node that blocks its input variable - exists(SsaRefinementNode ref, boolean outcome | - nd = DataFlow::ssaDefinitionNode(ref) and - outcome = ref.getGuard().(ConditionGuardNode).getOutcome() and - ssaRefinementBlocks(outcome, ref, label) - ) - or - // 2) `nd` is an instance of an access path `p`, and dominated by a barrier for `p` - exists(AccessPath p, BasicBlock bb, ConditionGuardNode cond, boolean outcome | - nd = DataFlow::valueNode(p.getAnInstanceIn(bb)) and - getEnclosingExpr() = cond.getTest() and - outcome = cond.getOutcome() and - barrierGuardBlocksAccessPath(this, outcome, p, label) and - cond.dominates(bb) - ) - } - - /** - * Holds if there exists an input variable of `ref` that blocks the label `label`. - * - * This predicate is outlined to give the optimizer a hint about the join ordering. - */ - private predicate ssaRefinementBlocks(boolean outcome, SsaRefinementNode ref, string label) { - getEnclosingExpr() = ref.getGuard().getTest() and - forex(SsaVariable input | input = ref.getAnInput() | - barrierGuardBlocksExpr(this, outcome, input.getAUse(), label) - ) - } - /** * Holds if this node blocks expression `e` provided it evaluates to `outcome`. * @@ -387,6 +353,17 @@ private predicate barrierGuardBlocksExpr( guard.(AdditionalBarrierGuardCall).internalBlocksLabel(outcome, test, label) } +/** + * Holds if `guard` blocks the flow of a value reachable through exploratory flow. + */ +pragma[noinline] +private predicate barrierGuardIsRelevant(BarrierGuardNode guard) { + exists(Expr e | + barrierGuardBlocksExpr(guard, _, e, _) and + isRelevantForward(e.flow(), _) + ) +} + /** * Holds if data flow node `nd` acts as a barrier for data flow due to aliasing through * an access path. @@ -397,9 +374,50 @@ pragma[noinline] private predicate barrierGuardBlocksAccessPath( BarrierGuardNode guard, boolean outcome, AccessPath ap, string label ) { + barrierGuardIsRelevant(guard) and barrierGuardBlocksExpr(guard, outcome, ap.getAnInstance(), label) } +/** + * Holds if there exists an input variable of `ref` that blocks the label `label`. + * + * This predicate is outlined to give the optimizer a hint about the join ordering. + */ +private predicate barrierGuardBlocksSsaRefinement( + BarrierGuardNode guard, boolean outcome, SsaRefinementNode ref, string label +) { + barrierGuardIsRelevant(guard) and + guard.getEnclosingExpr() = ref.getGuard().getTest() and + forex(SsaVariable input | input = ref.getAnInput() | + barrierGuardBlocksExpr(guard, outcome, input.getAUse(), label) + ) +} + +/** + * Holds if data flow node `nd` acts as a barrier for data flow, possibly due to aliasing + * through an access path. + * + * `label` is bound to the blocked label, or the empty string if all labels should be blocked. + */ +private predicate barrierGuardBlocksNode(BarrierGuardNode guard, DataFlow::Node nd, string label) { + // 1) `nd` is a use of a refinement node that blocks its input variable + exists(SsaRefinementNode ref, boolean outcome | + nd = DataFlow::ssaDefinitionNode(ref) and + outcome = ref.getGuard().(ConditionGuardNode).getOutcome() and + barrierGuardBlocksSsaRefinement(guard, outcome, ref, label) + ) + or + // 2) `nd` is an instance of an access path `p`, and dominated by a barrier for `p` + barrierGuardIsRelevant(guard) and + exists(AccessPath p, BasicBlock bb, ConditionGuardNode cond, boolean outcome | + nd = DataFlow::valueNode(p.getAnInstanceIn(bb)) and + guard.getEnclosingExpr() = cond.getTest() and + outcome = cond.getOutcome() and + barrierGuardBlocksAccessPath(guard, outcome, p, label) and + cond.dominates(bb) + ) +} + /** * Holds if `guard` should block flow along the edge `pred -> succ`. * @@ -408,6 +426,7 @@ private predicate barrierGuardBlocksAccessPath( private predicate barrierGuardBlocksEdge( BarrierGuardNode guard, DataFlow::Node pred, DataFlow::Node succ, string label ) { + barrierGuardIsRelevant(guard) and exists( SsaVariable input, SsaPhiNode phi, BasicBlock bb, ConditionGuardNode cond, boolean outcome | @@ -569,11 +588,12 @@ private class FlowStepThroughImport extends AdditionalFlowStep, DataFlow::ValueN /** * Holds if there is a flow step from `pred` to `succ` described by `summary` - * under configuration `cfg`. + * under configuration `cfg`, disregarding barriers. * * Summary steps through function calls are not taken into account. */ -private predicate basicFlowStep( +pragma[inline] +private predicate basicFlowStepNoBarrier( DataFlow::Node pred, DataFlow::Node succ, PathSummary summary, DataFlow::Configuration cfg ) { isLive() and @@ -582,8 +602,7 @@ private predicate basicFlowStep( // Local flow exists(FlowLabel predlbl, FlowLabel succlbl | localFlowStep(pred, succ, cfg, predlbl, succlbl) and - not isLabeledBarrierEdge(cfg, pred, succ, predlbl) and - not isBarrierEdge(cfg, pred, succ) and + not cfg.isBarrierEdge(pred, succ) and summary = MkPathSummary(false, false, predlbl, succlbl) ) or @@ -605,6 +624,20 @@ private predicate basicFlowStep( ) } +/** + * Holds if there is a flow step from `pred` to `succ` described by `summary` + * under configuration `cfg`. + * + * Summary steps through function calls are not taken into account. + */ +private predicate basicFlowStep( + DataFlow::Node pred, DataFlow::Node succ, PathSummary summary, DataFlow::Configuration cfg +) { + basicFlowStepNoBarrier(pred, succ, summary, cfg) and + not isLabeledBarrierEdge(cfg, pred, succ, summary.getStartLabel()) and + not isBarrierEdge(cfg, pred, succ) +} + /** * Holds if there is a flow step from `pred` to `succ` under configuration `cfg`, * including both basic flow steps and steps into/out of properties. @@ -615,7 +648,7 @@ private predicate basicFlowStep( private predicate exploratoryFlowStep( DataFlow::Node pred, DataFlow::Node succ, DataFlow::Configuration cfg ) { - basicFlowStep(pred, succ, _, cfg) or + basicFlowStepNoBarrier(pred, succ, _, cfg) or basicStoreStep(pred, succ, _) or basicLoadStep(pred, succ, _) or isAdditionalStoreStep(pred, succ, _, cfg) or @@ -901,8 +934,16 @@ private predicate reachableFromStoreBase( string prop, DataFlow::Node rhs, DataFlow::Node nd, DataFlow::Configuration cfg, PathSummary summary ) { - isRelevant(rhs, cfg) and - storeStep(rhs, nd, prop, cfg, summary) + exists(PathSummary s1, PathSummary s2 | + reachableFromSource(rhs, cfg, s1) + or + reachableFromStoreBase(_, _, rhs, cfg, s1) + | + storeStep(rhs, nd, prop, cfg, s2) and + summary = + MkPathSummary(false, s1.hasCall().booleanOr(s2.hasCall()), s2.getStartLabel(), + s2.getEndLabel()) + ) or exists(DataFlow::Node mid, PathSummary oldSummary, PathSummary newSummary | reachableFromStoreBase(prop, rhs, mid, cfg, oldSummary) and @@ -1035,7 +1076,9 @@ private predicate flowIntoHigherOrderCall( summary = oldSummary.append(PathSummary::call()) ) or - exists(DataFlow::SourceNode cb, DataFlow::FunctionNode f, int i, int boundArgs, PathSummary oldSummary | + 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 @@ -1432,6 +1475,7 @@ private class BarrierGuardFunction extends Function { string label; BarrierGuardFunction() { + barrierGuardIsRelevant(guard) and exists(Expr e | exists(Expr returnExpr | returnExpr = guard.asExpr() @@ -1494,9 +1538,9 @@ private class AdditionalBarrierGuardCall extends AdditionalBarrierGuardNode, Dat } /** - * 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. - */ + * 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 | diff --git a/javascript/ql/src/semmle/javascript/dataflow/DataFlow.qll b/javascript/ql/src/semmle/javascript/dataflow/DataFlow.qll index e4c1097b877..5d7ce03e467 100644 --- a/javascript/ql/src/semmle/javascript/dataflow/DataFlow.qll +++ b/javascript/ql/src/semmle/javascript/dataflow/DataFlow.qll @@ -87,10 +87,10 @@ module DataFlow { Expr asExpr() { this = TValueNode(result) } /** - * Gets the expression enclosing this data flow node. - * In most cases the result is the same as `asExpr()`, however this method - * additionally the `InvokeExpr` corresponding to reflective calls, and the `Parameter` - * for a `DataFlow::ParameterNode`. + * Gets the expression enclosing this data flow node. + * In most cases the result is the same as `asExpr()`, however this method + * additionally the `InvokeExpr` corresponding to reflective calls, and the `Parameter` + * for a `DataFlow::ParameterNode`. */ Expr getEnclosingExpr() { result = asExpr() or @@ -536,10 +536,10 @@ module DataFlow { */ pragma[noinline] predicate accesses(Node base, string p) { getBase() = base and getPropertyName() = p } - + /** * Holds if this data flow node reads or writes a private field in a class. - */ + */ predicate isPrivateField() { getPropertyName().charAt(0) = "#" and getPropertyNameExpr() instanceof Label } @@ -860,7 +860,7 @@ module DataFlow { override Expr getPropertyNameExpr() { none() } override string getPropertyName() { - exists (int i | + exists(int i | elt = pattern.getElement(i) and result = i.toString() ) @@ -872,7 +872,6 @@ module DataFlow { */ private class ImportSpecifierAsPropRead extends PropRead, ValueNode { override ImportSpecifier astNode; - ImportDeclaration imprt; ImportSpecifierAsPropRead() { @@ -894,9 +893,7 @@ module DataFlow { private class ForOfLvalueAsPropRead extends PropRead { ForOfStmt stmt; - ForOfLvalueAsPropRead() { - this = lvalueNode(stmt.getLValue()) - } + ForOfLvalueAsPropRead() { this = lvalueNode(stmt.getLValue()) } override Node getBase() { result = stmt.getIterationDomain().flow() } @@ -1010,7 +1007,7 @@ module DataFlow { * Gets a pseudo-node representing the root of a global access path. */ DataFlow::Node globalAccessPathRootPseudoNode() { result instanceof TGlobalAccessPathRoot } - + /** * Gets a data flow node representing the underlying call performed by the given * call to `Function.prototype.call` or `Function.prototype.apply`. diff --git a/javascript/ql/src/semmle/javascript/dataflow/ForwardExploration.qll b/javascript/ql/src/semmle/javascript/dataflow/ForwardExploration.qll index 0e3a7ae91c2..080152d3094 100644 --- a/javascript/ql/src/semmle/javascript/dataflow/ForwardExploration.qll +++ b/javascript/ql/src/semmle/javascript/dataflow/ForwardExploration.qll @@ -16,9 +16,7 @@ import javascript private class ForwardExploringConfiguration extends DataFlow::Configuration { DataFlow::Configuration cfg; - ForwardExploringConfiguration() { - this = cfg - } + ForwardExploringConfiguration() { this = cfg } override predicate isSink(DataFlow::Node node) { any() } diff --git a/javascript/ql/src/semmle/javascript/dataflow/InferredTypes.qll b/javascript/ql/src/semmle/javascript/dataflow/InferredTypes.qll index 84d3c7dd5a5..90fed456d76 100644 --- a/javascript/ql/src/semmle/javascript/dataflow/InferredTypes.qll +++ b/javascript/ql/src/semmle/javascript/dataflow/InferredTypes.qll @@ -134,6 +134,7 @@ class NonPrimitiveType extends InferredType { * Gets a pretty-printed list of all type tags in alphabetical order. */ string ppAllTypeTags() { - result = "boolean, class, date, function, null, number, object, regular expression," + + result = + "boolean, class, date, function, null, number, object, regular expression," + "string or undefined" } diff --git a/javascript/ql/src/semmle/javascript/dataflow/Nodes.qll b/javascript/ql/src/semmle/javascript/dataflow/Nodes.qll index 937255e60a9..c988bc9362e 100644 --- a/javascript/ql/src/semmle/javascript/dataflow/Nodes.qll +++ b/javascript/ql/src/semmle/javascript/dataflow/Nodes.qll @@ -9,7 +9,7 @@ private import semmle.javascript.dependencies.Dependencies private import internal.CallGraphs /** - * A data flow node corresponding to an expression. + * A data flow node corresponding to an expression. * * Examples: * ```js @@ -147,7 +147,8 @@ class InvokeNode extends DataFlow::SourceNode { */ ParameterNode getABoundCallbackParameter(int callback, int param) { exists(int boundArgs | - result = getArgument(callback).getABoundFunctionValue(boundArgs).getParameter(param + boundArgs) + result = + getArgument(callback).getABoundFunctionValue(boundArgs).getParameter(param + boundArgs) ) } @@ -548,9 +549,7 @@ class RegExpLiteralNode extends DataFlow::ValueNode, DataFlow::SourceNode { RegExpTerm getRoot() { result = astNode.getRoot() } /** Gets the flags of this regular expression literal. */ - string getFlags() { - result = astNode.getFlags() - } + string getFlags() { result = astNode.getFlags() } } /** @@ -581,10 +580,9 @@ class ArrayConstructorInvokeNode extends DataFlow::InvokeNode { /** Gets the initial size of the created array, if it can be determined. */ int getSize() { - if getNumArgument() = 1 then - result = getArgument(0).getIntValue() - else - result = count(getAnElement()) + if getNumArgument() = 1 + then result = getArgument(0).getIntValue() + else result = count(getAnElement()) } } @@ -596,7 +594,7 @@ class ArrayConstructorInvokeNode extends DataFlow::InvokeNode { * Examples: * ```js * ['apple', 'orange']; - * Array('apple', 'orange') + * Array('apple', 'orange') * new Array('apple', 'orange') * Array(16) * new Array(16) @@ -1236,7 +1234,9 @@ class PartialInvokeNode extends DataFlow::Node { /** * 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) } + DataFlow::Node getBoundReceiver(DataFlow::Node callback) { + result = range.getBoundReceiver(callback) + } } module PartialInvokeNode { @@ -1247,7 +1247,9 @@ module PartialInvokeNode { /** * Holds if `argument` is passed as argument `index` to the function in `callback`. */ - predicate isPartialArgument(DataFlow::Node callback, DataFlow::Node argument, int index) { none() } + predicate isPartialArgument(DataFlow::Node callback, DataFlow::Node argument, int index) { + none() + } /** * Gets a node referring to a bound version of `callback` with `boundArgs` arguments bound. @@ -1259,8 +1261,7 @@ module PartialInvokeNode { * * Gets the node holding the receiver to be passed to the bound function, if specified. */ - deprecated - DataFlow::Node getBoundReceiver() { none() } + deprecated DataFlow::Node getBoundReceiver() { none() } /** * Gets the node holding the receiver to be passed to `callback`. @@ -1269,12 +1270,11 @@ module PartialInvokeNode { } /** - * A partial call through the built-in `Function.prototype.bind`. - */ + * A partial call through the built-in `Function.prototype.bind`. + */ private class BindPartialCall extends PartialInvokeNode::Range, DataFlow::MethodCallNode { BindPartialCall() { getMethodName() = "bind" and - // Avoid overlap with angular.bind and goog.bind not this = AngularJS::angular().getAMethodCall() and not getReceiver().accessesGlobal("goog") @@ -1299,8 +1299,8 @@ module PartialInvokeNode { } /** - * A partial call through `_.partial`. - */ + * A partial call through `_.partial`. + */ private class LodashPartialCall extends PartialInvokeNode::Range, DataFlow::CallNode { LodashPartialCall() { this = LodashUnderscore::member("partial").getACall() } @@ -1323,9 +1323,7 @@ module PartialInvokeNode { private class RamdaPartialCall extends PartialInvokeNode::Range, DataFlow::CallNode { RamdaPartialCall() { this = DataFlow::moduleMember("ramda", "partial").getACall() } - private DataFlow::ArrayCreationNode getArgumentsArray() { - result.flowsTo(getArgument(1)) - } + private DataFlow::ArrayCreationNode getArgumentsArray() { result.flowsTo(getArgument(1)) } override predicate isPartialArgument(DataFlow::Node callback, DataFlow::Node argument, int index) { callback = getArgument(0) and @@ -1376,17 +1374,13 @@ deprecated class AdditionalPartialInvokeNode = PartialInvokeNode::Range; * ``` */ class RegExpConstructorInvokeNode extends DataFlow::InvokeNode { - RegExpConstructorInvokeNode() { - this = DataFlow::globalVarRef("RegExp").getAnInvocation() - } + RegExpConstructorInvokeNode() { this = DataFlow::globalVarRef("RegExp").getAnInvocation() } /** * Gets the AST of the regular expression created here, provided that the * first argument is a string literal. */ - RegExpTerm getRoot() { - result = getArgument(0).asExpr().(StringLiteral).asRegExp() - } + RegExpTerm getRoot() { result = getArgument(0).asExpr().(StringLiteral).asRegExp() } /** * Gets the flags provided in the second argument, or an empty string if no @@ -1462,13 +1456,9 @@ class RegExpCreationNode extends DataFlow::SourceNode { t.start() and result = this or - exists(DataFlow::TypeTracker t2 | - result = getAReference(t2).track(t2, t) - ) + exists(DataFlow::TypeTracker t2 | result = getAReference(t2).track(t2, t)) } /** Gets a data flow node referring to this regular expression. */ - DataFlow::SourceNode getAReference() { - result = getAReference(DataFlow::TypeTracker::end()) - } + DataFlow::SourceNode getAReference() { result = getAReference(DataFlow::TypeTracker::end()) } } diff --git a/javascript/ql/src/semmle/javascript/dataflow/TaintTracking.qll b/javascript/ql/src/semmle/javascript/dataflow/TaintTracking.qll index e5acc6ee5b5..ea187f93f07 100644 --- a/javascript/ql/src/semmle/javascript/dataflow/TaintTracking.qll +++ b/javascript/ql/src/semmle/javascript/dataflow/TaintTracking.qll @@ -341,7 +341,7 @@ module TaintTracking { pred = call.getAnArgument() and succ = call or - // `e = arr1.concat(arr2, arr3)`: if any of the `arr` is tainted, then so is `e`. + // `e = arr1.concat(arr2, arr3)`: if any of the `arr` is tainted, then so is `e`. call.(DataFlow::MethodCallNode).calls(pred, "concat") and succ = call or @@ -574,7 +574,6 @@ module TaintTracking { succ = this } } - /** * A taint propagating data flow edge arising from calling `String.prototype.match()`. @@ -583,7 +582,7 @@ module TaintTracking { StringMatchTaintStep() { this.getMethodName() = "match" and this.getNumArgument() = 1 and - this.getArgument(0) .analyze().getAType() = TTRegExp() + this.getArgument(0).analyze().getAType() = TTRegExp() } override predicate step(DataFlow::Node pred, DataFlow::Node succ) { @@ -696,9 +695,7 @@ module TaintTracking { * A taint step through the Node.JS function `util.inspect(..)`. */ class UtilInspectTaintStep extends AdditionalTaintStep, DataFlow::InvokeNode { - UtilInspectTaintStep() { - this = DataFlow::moduleImport("util").getAMemberCall("inspect") - } + UtilInspectTaintStep() { this = DataFlow::moduleImport("util").getAMemberCall("inspect") } override predicate step(DataFlow::Node pred, DataFlow::Node succ) { succ = this and @@ -719,14 +716,16 @@ module TaintTracking { mce = astNode and mce.calls(base, m) and firstArg = mce.getArgument(0) | // /re/.test(u) or /re/.exec(u) - RegExp::isGenericRegExpSanitizer(RegExp::getRegExpObjectFromNode(base.flow()), sanitizedOutcome) and + RegExp::isGenericRegExpSanitizer(RegExp::getRegExpObjectFromNode(base.flow()), + sanitizedOutcome) and (m = "test" or m = "exec") and firstArg = expr or // u.match(/re/) or u.match("re") base = expr and m = "match" and - RegExp::isGenericRegExpSanitizer(RegExp::getRegExpFromNode(firstArg.flow()), sanitizedOutcome) + RegExp::isGenericRegExpSanitizer(RegExp::getRegExpFromNode(firstArg.flow()), + sanitizedOutcome) ) or // m = /re/.exec(u) and similar diff --git a/javascript/ql/src/semmle/javascript/dataflow/TypeTracking.qll b/javascript/ql/src/semmle/javascript/dataflow/TypeTracking.qll index ecbb3187728..e7db4e2d660 100644 --- a/javascript/ql/src/semmle/javascript/dataflow/TypeTracking.qll +++ b/javascript/ql/src/semmle/javascript/dataflow/TypeTracking.qll @@ -137,10 +137,11 @@ module StepSummary { summary = LoadStep(prop) ) ) and - if param = fun.getAParameter() then ( + if param = fun.getAParameter() + then // Step from argument to call site. argumentPassing(succ, pred, fun.getFunction(), param) - ) else ( + else ( // Step from captured parameter to local call sites pred = param and succ = fun.getAnInvocation() diff --git a/javascript/ql/src/semmle/javascript/dataflow/internal/AccessPaths.qll b/javascript/ql/src/semmle/javascript/dataflow/internal/AccessPaths.qll index bea4ddc8511..6aa26fe9cdb 100644 --- a/javascript/ql/src/semmle/javascript/dataflow/internal/AccessPaths.qll +++ b/javascript/ql/src/semmle/javascript/dataflow/internal/AccessPaths.qll @@ -128,8 +128,9 @@ class AccessPath extends TAccessPath { exists(AccessPath base, PropertyName name, string rest | rest = "." + any(string s | name = StaticPropertyName(s)) or - rest = "[" + - any(SsaVariable var | name = DynamicPropertyName(var)).getSourceVariable().getName() + "]" + rest = + "[" + any(SsaVariable var | name = DynamicPropertyName(var)).getSourceVariable().getName() + + "]" | result = base.toString() + rest and this = MkAccessStep(base, name) diff --git a/javascript/ql/src/semmle/javascript/dataflow/internal/CallGraphs.qll b/javascript/ql/src/semmle/javascript/dataflow/internal/CallGraphs.qll index 51bd466215e..944214a94af 100644 --- a/javascript/ql/src/semmle/javascript/dataflow/internal/CallGraphs.qll +++ b/javascript/ql/src/semmle/javascript/dataflow/internal/CallGraphs.qll @@ -1,6 +1,7 @@ /** * Internal predicates for computing the call graph. */ + private import javascript cached @@ -24,21 +25,22 @@ module CallGraph { * from underlying class tracking if the function came from a class or instance. */ pragma[nomagic] - private - DataFlow::SourceNode getAFunctionReference(DataFlow::FunctionNode function, int imprecision, DataFlow::TypeTracker t) { + private DataFlow::SourceNode getAFunctionReference( + DataFlow::FunctionNode function, int imprecision, DataFlow::TypeTracker t + ) { t.start() and exists(Function fun | fun = function.getFunction() and fun = getAFunctionValue(result) | - if isIndefiniteGlobal(result) then + if isIndefiniteGlobal(result) + then fun.getFile() = result.getFile() and imprecision = 0 or fun.inExternsFile() and imprecision = 1 or imprecision = 2 - else - imprecision = 0 + else imprecision = 0 ) or imprecision = 0 and @@ -73,8 +75,9 @@ module CallGraph { * with `function` as the underlying function. */ pragma[nomagic] - private - DataFlow::SourceNode getABoundFunctionReferenceAux(DataFlow::FunctionNode function, int boundArgs, DataFlow::TypeTracker t) { + private 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) @@ -87,8 +90,10 @@ module CallGraph { } pragma[noinline] - private - DataFlow::SourceNode getABoundFunctionReferenceAux(DataFlow::FunctionNode function, int boundArgs, DataFlow::TypeTracker t, DataFlow::StepSummary summary) { + private DataFlow::SourceNode getABoundFunctionReferenceAux( + DataFlow::FunctionNode function, int boundArgs, DataFlow::TypeTracker t, + DataFlow::StepSummary summary + ) { exists(DataFlow::SourceNode prev | prev = getABoundFunctionReferenceAux(function, boundArgs, t) and DataFlow::StepSummary::step(prev, result, summary) @@ -100,7 +105,9 @@ module CallGraph { * with `function` as the underlying function. */ cached - DataFlow::SourceNode getABoundFunctionReference(DataFlow::FunctionNode function, int boundArgs, boolean contextDependent) { + DataFlow::SourceNode getABoundFunctionReference( + DataFlow::FunctionNode function, int boundArgs, boolean contextDependent + ) { exists(DataFlow::TypeTracker t | result = getABoundFunctionReferenceAux(function, boundArgs, t) and t.end() and @@ -116,8 +123,9 @@ module CallGraph { * This predicate may be overridden to customize the class hierarchy analysis. */ pragma[nomagic] - private - DataFlow::PropRead getAnInstanceMemberAccess(DataFlow::ClassNode cls, string name, DataFlow::TypeTracker t) { + private DataFlow::PropRead getAnInstanceMemberAccess( + DataFlow::ClassNode cls, string name, DataFlow::TypeTracker t + ) { result = cls.getAnInstanceReference(t.continue()).getAPropertyRead(name) or exists(DataFlow::ClassNode subclass | diff --git a/javascript/ql/src/semmle/javascript/dataflow/internal/FlowSteps.qll b/javascript/ql/src/semmle/javascript/dataflow/internal/FlowSteps.qll index d01c8ca9e8a..ad1897b1da8 100644 --- a/javascript/ql/src/semmle/javascript/dataflow/internal/FlowSteps.qll +++ b/javascript/ql/src/semmle/javascript/dataflow/internal/FlowSteps.qll @@ -440,8 +440,8 @@ class PathSummary extends TPathSummary { exists(Boolean hasReturn2, Boolean hasCall2, FlowLabel end2 | that = MkPathSummary(hasReturn2, hasCall2, end, end2) | - result = MkPathSummary(hasReturn.booleanOr(hasReturn2), hasCall.booleanOr(hasCall2), start, - end2) and + result = + MkPathSummary(hasReturn.booleanOr(hasReturn2), hasCall.booleanOr(hasCall2), start, end2) and // avoid constructing invalid paths not (hasCall = true and hasReturn2 = true) ) @@ -456,8 +456,8 @@ class PathSummary extends TPathSummary { exists(Boolean hasReturn2, Boolean hasCall2 | that = MkPathSummary(hasReturn2, hasCall2, FlowLabel::data(), FlowLabel::data()) | - result = MkPathSummary(hasReturn.booleanOr(hasReturn2), hasCall.booleanOr(hasCall2), start, - end) and + result = + MkPathSummary(hasReturn.booleanOr(hasReturn2), hasCall.booleanOr(hasCall2), start, end) and // avoid constructing invalid paths not (hasCall = true and hasReturn2 = true) ) @@ -474,8 +474,9 @@ class PathSummary extends TPathSummary { (if hasReturn = true then withReturn = "with" else withReturn = "without") and (if hasCall = true then withCall = "with" else withCall = "without") | - result = "path " + withReturn + " return steps and " + withCall + " call steps " + - "transforming " + start + " into " + end + result = + "path " + withReturn + " return steps and " + withCall + " call steps " + "transforming " + + start + " into " + end ) } } diff --git a/javascript/ql/src/semmle/javascript/dependencies/FrameworkLibraries.qll b/javascript/ql/src/semmle/javascript/dependencies/FrameworkLibraries.qll index f7076aa710c..45eb4c8be32 100644 --- a/javascript/ql/src/semmle/javascript/dependencies/FrameworkLibraries.qll +++ b/javascript/ql/src/semmle/javascript/dependencies/FrameworkLibraries.qll @@ -145,8 +145,9 @@ abstract class FrameworkLibraryWithGenericURL extends FrameworkLibraryWithURLReg override string getAURLRegex() { exists(string id | id = getId() or id = getAnAlias() | result = ".*(?:^|/)" + id + "-(" + semverRegex() + ")" + variantRegex() + "\\.js" or - result = ".*/(?:\\w+@)?(" + semverRegex() + ")/(?:(?:dist|js|" + id + ")/)?" + id + - variantRegex() + "\\.js" + result = + ".*/(?:\\w+@)?(" + semverRegex() + ")/(?:(?:dist|js|" + id + ")/)?" + id + variantRegex() + + "\\.js" ) } } @@ -158,7 +159,8 @@ abstract class FrameworkLibraryWithGenericURL extends FrameworkLibraryWithURLReg * We ignore these when identifying frameworks. */ private string variantRegex() { - result = "([.-](slim|min|debug|dbg|umd|dev|all|testing|polyfills|" + + result = + "([.-](slim|min|debug|dbg|umd|dev|all|testing|polyfills|" + "core|compat|more|modern|sandbox|rtl|with-addons|legacy))*" } @@ -235,8 +237,8 @@ private predicate jQueryMarkerComment(Comment c, TopLevel tl, string version) { tl = c.getTopLevel() and exists(string txt | txt = c.getText() | // more recent versions use this format - version = txt - .regexpCapture("(?s).*jQuery (?:JavaScript Library )?v(" + versionRegex() + ").*", 1) + version = + txt.regexpCapture("(?s).*jQuery (?:JavaScript Library )?v(" + versionRegex() + ").*", 1) or // earlier versions used this format version = txt.regexpCapture("(?s).*jQuery (" + versionRegex() + ") - New Wave Javascript.*", 1) @@ -502,7 +504,8 @@ private class Lodash extends FrameworkLibraryWithGenericURL, FrameworkLibraryWit Lodash() { this = "lodash" } override string getAMarkerCommentRegex() { - result = "(?s).* (?:lod|Lo-D)ash ()" + "(?: \\(Custom Build\\))? " + + result = + "(?s).* (?:lod|Lo-D)ash ()" + "(?: \\(Custom Build\\))? " + ".*" } @@ -842,7 +845,8 @@ private class ApplicationInsightsInstance extends FrameworkLibraryInstance { string version; ApplicationInsightsInstance() { - version = this + version = + this .(TopLevel) .getFile() .getAbsolutePath() diff --git a/javascript/ql/src/semmle/javascript/frameworks/AngularJS/AngularJSCore.qll b/javascript/ql/src/semmle/javascript/frameworks/AngularJS/AngularJSCore.qll index 6d1aba6bf8e..6eede4a1aa8 100644 --- a/javascript/ql/src/semmle/javascript/frameworks/AngularJS/AngularJSCore.qll +++ b/javascript/ql/src/semmle/javascript/frameworks/AngularJS/AngularJSCore.qll @@ -832,7 +832,8 @@ private newtype TAngularScope = } or MkIsolateScope(CustomDirective dir) { dir.hasIsolateScope() } or MkElementScope(DOM::ElementDefinition elem) { - any(DirectiveInstance d | not d.(CustomDirective).hasIsolateScope()).getAMatchingElement() = elem + any(DirectiveInstance d | not d.(CustomDirective).hasIsolateScope()).getAMatchingElement() = + elem } /** diff --git a/javascript/ql/src/semmle/javascript/frameworks/AngularJS/AngularJSExpressions.qll b/javascript/ql/src/semmle/javascript/frameworks/AngularJS/AngularJSExpressions.qll index 191c8bf3268..bd0029abd0f 100644 --- a/javascript/ql/src/semmle/javascript/frameworks/AngularJS/AngularJSExpressions.qll +++ b/javascript/ql/src/semmle/javascript/frameworks/AngularJS/AngularJSExpressions.qll @@ -152,7 +152,8 @@ private class TemplateFieldNgSourceProvider extends NgSourceProvider { TemplateFieldNgSourceProvider() { this = directive.getMember("template").asExpr() and - source = this + source = + this .(ConstantString) .getStringValue() .regexpFind(getInterpolatedExpressionPattern(), _, offset) @@ -211,11 +212,8 @@ abstract class NgToken extends TNgToken { */ private int getIndex() { exists(NgSource src, int start | this.at(src, start) | - start = rank[result + 1](NgToken someToken, int someStart | - someToken.at(src, someStart) - | - someStart - ) + start = + rank[result + 1](NgToken someToken, int someStart | someToken.at(src, someStart) | someStart) ) } @@ -278,7 +276,8 @@ private module Lexer { NgOpTokenType() { this = "NgOpTokenType" } override string getPattern() { - result = concat(string op | + result = + concat(string op | op = "===" or op = "!==" or op = "==" or @@ -383,7 +382,8 @@ abstract class NgAstNode extends TNode { */ language[monotonicAggregates] string ppChildren() { - result = concat(NgAstNode child, int idx | + result = + concat(NgAstNode child, int idx | child = getChild(idx) and not child instanceof Empty | diff --git a/javascript/ql/src/semmle/javascript/frameworks/ClientRequests.qll b/javascript/ql/src/semmle/javascript/frameworks/ClientRequests.qll index d39b26f2563..e97169264e0 100644 --- a/javascript/ql/src/semmle/javascript/frameworks/ClientRequests.qll +++ b/javascript/ql/src/semmle/javascript/frameworks/ClientRequests.qll @@ -110,6 +110,27 @@ module ClientRequest { */ private string httpMethodName() { result = any(HTTP::RequestMethodName m).toLowerCase() } + /** + * Gets a model of an instance of the `request` library, or one of + * its wrappers, `promise` is true if the instance uses promises + * rather than callbacks. + */ + private DataFlow::SourceNode getRequestLibrary(boolean promise) { + exists(string moduleName | result = DataFlow::moduleImport(moduleName) | + promise = false and + moduleName = "request" + or + promise = true and + ( + moduleName = "request-promise" or + moduleName = "request-promise-any" or + moduleName = "request-promise-native" + ) + ) + or + result = getRequestLibrary(promise).getAMethodCall("defaults") + } + /** * A model of a URL request made using the `request` library. */ @@ -117,22 +138,9 @@ module ClientRequest { boolean promise; RequestUrlRequest() { - exists(string moduleName, DataFlow::SourceNode callee | this = callee.getACall() | - ( - promise = false and - moduleName = "request" - or - promise = true and - ( - moduleName = "request-promise" or - moduleName = "request-promise-any" or - moduleName = "request-promise-native" - ) - ) and - ( - callee = DataFlow::moduleImport(moduleName) or - callee = DataFlow::moduleMember(moduleName, httpMethodName()) - ) + exists(DataFlow::SourceNode callee | this = callee.getACall() | + callee = getRequestLibrary(promise) or + callee = getRequestLibrary(promise).getAPropertyRead(httpMethodName()) ) } @@ -553,10 +561,10 @@ module ClientRequest { /** * Gets a reference to an instance of `chrome-remote-interface`. * - * An instantiation of `chrome-remote-interface` either accepts a callback or returns a promise. - * - * The `isPromise` parameter reflects whether the reference is a promise containing - * an instance of `chrome-remote-interface`, or an instance of `chrome-remote-interface`. + * An instantiation of `chrome-remote-interface` either accepts a callback or returns a promise. + * + * The `isPromise` parameter reflects whether the reference is a promise containing + * an instance of `chrome-remote-interface`, or an instance of `chrome-remote-interface`. */ private DataFlow::SourceNode chromeRemoteInterface(DataFlow::TypeTracker t, boolean isPromise) { t.start() and diff --git a/javascript/ql/src/semmle/javascript/frameworks/CryptoLibraries.qll b/javascript/ql/src/semmle/javascript/frameworks/CryptoLibraries.qll index 624040db386..71aa4b4a3fb 100644 --- a/javascript/ql/src/semmle/javascript/frameworks/CryptoLibraries.qll +++ b/javascript/ql/src/semmle/javascript/frameworks/CryptoLibraries.qll @@ -609,7 +609,8 @@ private module Forge { NonKeyCipher() { exists(string algorithmName | algorithm.matchesName(algorithmName) | // require("forge").md.md5.create().update('The quick brown fox jumps over the lazy dog'); - this = getAnImportNode() + this = + getAnImportNode() .getAPropertyRead("md") .getAPropertyRead(algorithmName) .getAMemberCall("create") diff --git a/javascript/ql/src/semmle/javascript/frameworks/Electron.qll b/javascript/ql/src/semmle/javascript/frameworks/Electron.qll index 5d146f7e036..7d099932738 100644 --- a/javascript/ql/src/semmle/javascript/frameworks/Electron.qll +++ b/javascript/ql/src/semmle/javascript/frameworks/Electron.qll @@ -138,9 +138,7 @@ module Electron { result = this.getABoundCallbackParameter(1, 0).getAPropertyWrite("returnValue").getRhs() } - override IPCDispatch getAReturnDispatch() { - result.getCalleeName() = "sendSync" - } + override IPCDispatch getAReturnDispatch() { result.getCalleeName() = "sendSync" } } /** @@ -168,7 +166,7 @@ module Electron { } /** - * Gets a registration that this dispatch can send an event to. + * Gets a registration that this dispatch can send an event to. */ override IPCSendRegistration getAReceiver() { this.getEmitter() instanceof RendererProcess and diff --git a/javascript/ql/src/semmle/javascript/frameworks/EventEmitter.qll b/javascript/ql/src/semmle/javascript/frameworks/EventEmitter.qll index ed3b04c8a7e..4db64ad0ab7 100644 --- a/javascript/ql/src/semmle/javascript/frameworks/EventEmitter.qll +++ b/javascript/ql/src/semmle/javascript/frameworks/EventEmitter.qll @@ -129,9 +129,7 @@ module EventRegistration { * argument is the event handler callback. */ abstract class DefaultEventRegistration extends Range, DataFlow::InvokeNode { - override string getChannel() { - this.getArgument(0).mayHaveStringValue(result) - } + override string getChannel() { this.getArgument(0).mayHaveStringValue(result) } override DataFlow::Node getReceivedItem(int i) { result = this.getABoundCallbackParameter(1, i) @@ -187,13 +185,9 @@ module EventDispatch { * is the `i`th item sent to the event handler. */ abstract class DefaultEventDispatch extends Range, DataFlow::InvokeNode { - override string getChannel() { - this.getArgument(0).mayHaveStringValue(result) - } + override string getChannel() { this.getArgument(0).mayHaveStringValue(result) } - override DataFlow::Node getSentItem(int i) { - result = this.getArgument(i + 1) - } + override DataFlow::Node getSentItem(int i) { result = this.getArgument(i + 1) } override EventRegistration::Range getAReceiver() { this.getEmitter() = result.getEmitter() } } @@ -223,4 +217,3 @@ private class EventEmitterTaintStep extends DataFlow::AdditionalFlowStep { succ = dispatch } } - diff --git a/javascript/ql/src/semmle/javascript/frameworks/Express.qll b/javascript/ql/src/semmle/javascript/frameworks/Express.qll index b6a972c47d7..702e1efde43 100644 --- a/javascript/ql/src/semmle/javascript/frameworks/Express.qll +++ b/javascript/ql/src/semmle/javascript/frameworks/Express.qll @@ -147,36 +147,34 @@ module Express { this.getRequestMethod() = that.getRequestMethod() } } - + /** * A call that sets up a Passport router that includes the request object. */ private class PassportRouteSetup extends HTTP::Servers::StandardRouteSetup, CallExpr { DataFlow::ModuleImportNode importNode; DataFlow::FunctionNode callback; - + // looks for this pattern: passport.use(new Strategy({passReqToCallback: true}, callback)) PassportRouteSetup() { importNode = DataFlow::moduleImport("passport") and this = importNode.getAMemberCall("use").asExpr() and - exists(DataFlow::NewNode strategy | - strategy.flowsToExpr(this.getArgument(0)) and - strategy.getNumArgument() = 2 and - // new Strategy({passReqToCallback: true}, ...) - strategy.getOptionArgument(0, "passReqToCallback").mayHaveBooleanValue(true) and - callback.flowsTo(strategy.getArgument(1)) + exists(DataFlow::NewNode strategy | + strategy.flowsToExpr(this.getArgument(0)) and + strategy.getNumArgument() = 2 and + // new Strategy({passReqToCallback: true}, ...) + strategy.getOptionArgument(0, "passReqToCallback").mayHaveBooleanValue(true) and + callback.flowsTo(strategy.getArgument(1)) ) } - + override Expr getServer() { result = importNode.asExpr() } - - override DataFlow::SourceNode getARouteHandler() { - result = callback - } + + override DataFlow::SourceNode getARouteHandler() { result = callback } } - + /** - * The callback given to passport in PassportRouteSetup. + * The callback given to passport in PassportRouteSetup. */ private class PassportRouteHandler extends RouteHandler, HTTP::Servers::StandardRouteHandler, DataFlow::ValueNode { @@ -185,7 +183,7 @@ module Express { PassportRouteHandler() { this = any(PassportRouteSetup setup).getARouteHandler() } override SimpleParameter getRouteHandlerParameter(string kind) { - kind = "request" and + kind = "request" and result = astNode.getParameter(0) } } diff --git a/javascript/ql/src/semmle/javascript/frameworks/Files.qll b/javascript/ql/src/semmle/javascript/frameworks/Files.qll index 6c8f6351f6c..161a2c13b37 100644 --- a/javascript/ql/src/semmle/javascript/frameworks/Files.qll +++ b/javascript/ql/src/semmle/javascript/frameworks/Files.qll @@ -21,7 +21,8 @@ private class WalkFileNameSource extends FileNameSource { WalkFileNameSource() { // `stats.name` in `require('walk').walk(_).on(_, (_, stats) => stats.name)` exists(DataFlow::FunctionNode callback | - callback = DataFlow::moduleMember("walk", "walk") + callback = + DataFlow::moduleMember("walk", "walk") .getACall() .getAMethodCall(EventEmitter::on()) .getCallback(1) @@ -66,7 +67,8 @@ private class GlobbyFileNameSource extends FileNameSource { this = DataFlow::moduleMember(moduleName, "sync").getACall() or // `files` in `require('globby')(_).then(files => ...)` - this = DataFlow::moduleImport(moduleName) + this = + DataFlow::moduleImport(moduleName) .getACall() .getAMethodCall("then") .getCallback(0) @@ -95,7 +97,8 @@ private class FastGlobFileNameSource extends FileNameSource { ) or // `file` in `require('fast-glob').stream(_).on(_, file => ...)` - this = DataFlow::moduleMember(moduleName, "stream") + this = + DataFlow::moduleMember(moduleName, "stream") .getACall() .getAMethodCall(EventEmitter::on()) .getCallback(1) diff --git a/javascript/ql/src/semmle/javascript/frameworks/HTTP.qll b/javascript/ql/src/semmle/javascript/frameworks/HTTP.qll index 45fd12054b6..fd28cd43666 100644 --- a/javascript/ql/src/semmle/javascript/frameworks/HTTP.qll +++ b/javascript/ql/src/semmle/javascript/frameworks/HTTP.qll @@ -123,8 +123,7 @@ module HTTP { * * Gets the string `http` or `https`. */ - deprecated - string httpOrHttps() { result = "http" or result = "https" } + deprecated string httpOrHttps() { result = "http" or result = "https" } /** * An expression whose value is sent as (part of) the body of an HTTP response. @@ -272,7 +271,7 @@ module HTTP { */ abstract class StandardRouteHandler extends RouteHandler { override HeaderDefinition getAResponseHeader(string name) { - result.(StandardHeaderDefinition).getRouteHandler() = this and + result.getRouteHandler() = this and result.getAHeaderName() = name } diff --git a/javascript/ql/src/semmle/javascript/frameworks/Handlebars.qll b/javascript/ql/src/semmle/javascript/frameworks/Handlebars.qll index 6879d4b7699..40c52849c25 100644 --- a/javascript/ql/src/semmle/javascript/frameworks/Handlebars.qll +++ b/javascript/ql/src/semmle/javascript/frameworks/Handlebars.qll @@ -6,7 +6,7 @@ import javascript module Handlebars { /** - * A reference to the Handlebars library. + * A reference to the Handlebars library. */ class Handlebars extends DataFlow::SourceNode { Handlebars() { diff --git a/javascript/ql/src/semmle/javascript/frameworks/Koa.qll b/javascript/ql/src/semmle/javascript/frameworks/Koa.qll index ab306964ebe..15378dc507d 100644 --- a/javascript/ql/src/semmle/javascript/frameworks/Koa.qll +++ b/javascript/ql/src/semmle/javascript/frameworks/Koa.qll @@ -11,7 +11,7 @@ module Koa { */ class AppDefinition extends HTTP::Servers::StandardServerDefinition, InvokeExpr { AppDefinition() { - // `app = new Koa()` / `app = Koa()` + // `app = new Koa()` / `app = Koa()` this = DataFlow::moduleImport("koa").getAnInvocation().asExpr() } } diff --git a/javascript/ql/src/semmle/javascript/frameworks/Logging.qll b/javascript/ql/src/semmle/javascript/frameworks/Logging.qll index 8fda6f8e1c1..c40e579c850 100644 --- a/javascript/ql/src/semmle/javascript/frameworks/Logging.qll +++ b/javascript/ql/src/semmle/javascript/frameworks/Logging.qll @@ -58,9 +58,11 @@ private module Console { } override DataFlow::Node getAMessageComponent() { - if name = "assert" - then result = getArgument([1 .. getNumArgument()]) - else result = getAnArgument() + ( + if name = "assert" + then result = getArgument([1 .. getNumArgument()]) + else result = getAnArgument() + ) or result = getASpreadArgument() } @@ -68,9 +70,7 @@ private module Console { /** * Gets the name of the console logging method, e.g. "log", "error", "assert", etc. */ - string getName() { - result = name - } + string getName() { result = name } } } @@ -99,7 +99,8 @@ private module Winston { */ class WinstonLoggerCall extends LoggerCall, DataFlow::MethodCallNode { WinstonLoggerCall() { - this = DataFlow::moduleMember("winston", "createLogger") + this = + DataFlow::moduleMember("winston", "createLogger") .getACall() .getAMethodCall(getAStandardLoggerMethodName()) } @@ -121,7 +122,8 @@ private module log4js { */ class Log4jsLoggerCall extends LoggerCall { Log4jsLoggerCall() { - this = DataFlow::moduleMember("log4js", "getLogger") + this = + DataFlow::moduleMember("log4js", "getLogger") .getACall() .getAMethodCall(getAStandardLoggerMethodName()) } diff --git a/javascript/ql/src/semmle/javascript/frameworks/NoSQL.qll b/javascript/ql/src/semmle/javascript/frameworks/NoSQL.qll index c7af3e40b01..563ba6b5db6 100644 --- a/javascript/ql/src/semmle/javascript/frameworks/NoSQL.qll +++ b/javascript/ql/src/semmle/javascript/frameworks/NoSQL.qll @@ -84,9 +84,7 @@ private module MongoDB { * of `mongodb.Collection`. */ private class CollectionFromType extends Collection { - CollectionFromType() { - hasUnderlyingType("mongodb", "Collection") - } + CollectionFromType() { hasUnderlyingType("mongodb", "Collection") } } /** Gets a data flow node referring to a MongoDB collection. */ diff --git a/javascript/ql/src/semmle/javascript/frameworks/NodeJSLib.qll b/javascript/ql/src/semmle/javascript/frameworks/NodeJSLib.qll index 0ee7c10fa7f..78393a1aabf 100644 --- a/javascript/ql/src/semmle/javascript/frameworks/NodeJSLib.qll +++ b/javascript/ql/src/semmle/javascript/frameworks/NodeJSLib.qll @@ -252,10 +252,11 @@ module NodeJSLib { private class WriteHead extends HeaderDefinition { WriteHead() { astNode.getMethodName() = "writeHead" and - astNode.getNumArgument() > 1 + astNode.getNumArgument() >= 1 } override predicate definesExplicitly(string headerName, Expr headerValue) { + astNode.getNumArgument() > 1 and exists(DataFlow::SourceNode headers, string header | headers.flowsToExpr(astNode.getLastArgument()) and headers.hasPropertyWrite(header, DataFlow::valueNode(headerValue)) and @@ -368,10 +369,8 @@ module NodeJSLib { class Credentials extends CredentialsExpr { Credentials() { exists(string http | http = "http" or http = "https" | - this = DataFlow::moduleMember(http, "request") - .getACall() - .getOptionArgument(0, "auth") - .asExpr() + this = + DataFlow::moduleMember(http, "request").getACall().getOptionArgument(0, "auth").asExpr() ) } @@ -431,16 +430,27 @@ module NodeJSLib { } /** - * A member `member` from module `fs` or its drop-in replacements `graceful-fs` or `fs-extra`. + * A member `member` from module `fs` or its drop-in replacements `graceful-fs`, `fs-extra`, `original-fs`. */ private DataFlow::SourceNode fsModuleMember(string member) { + result = fsModule(DataFlow::TypeTracker::end()).getAPropertyRead(member) + } + + private DataFlow::SourceNode fsModule(DataFlow::TypeTracker t) { exists(string moduleName | moduleName = "fs" or moduleName = "graceful-fs" or - moduleName = "fs-extra" + moduleName = "fs-extra" or + moduleName = "original-fs" | - result = DataFlow::moduleMember(moduleName, member) - ) + result = DataFlow::moduleImport(moduleName) + or + // extra support for flexible names + result.asExpr().(Require).getArgument(0).mayHaveStringValue(moduleName) + ) and + t.start() + or + exists(DataFlow::TypeTracker t2 | result = fsModule(t2).track(t2, t)) } /** @@ -623,6 +633,23 @@ module NodeJSLib { // all of the above methods take the argument list as their second argument result = getArgument(1) } + + override predicate isSync() { "Sync" = methodName.suffix(methodName.length() - 4) } + + override DataFlow::Node getOptionsArg() { + not result.getALocalSource() instanceof DataFlow::FunctionNode and // looks like callback + not result.getALocalSource() instanceof DataFlow::ArrayCreationNode and // looks like argumentlist + not result = getArgument(0) and + // fork/spawn and all sync methos always has options as the last argument + if + methodName.regexpMatch("fork.*") or + methodName.regexpMatch("spawn.*") or + methodName.regexpMatch(".*Sync") + then result = getLastArgument() + else + // the rest (exec/execFile) has the options argument as their second last. + result = getArgument(this.getNumArgument() - 2) + } } /** @@ -921,6 +948,13 @@ module NodeJSLib { ImportedNodeJSEventEmitter() { this = getAnEventEmitterImport().getAnInstantiation() } } + /** + * The NodeJS `process` object as an EventEmitter subclass. + */ + private class ProcessAsNodeJSEventEmitter extends NodeJSEventEmitter { + ProcessAsNodeJSEventEmitter() { this = process() } + } + /** * A class that extends EventEmitter. */ @@ -937,16 +971,17 @@ module NodeJSLib { * By extending `NodeJSEventEmitter' we get data-flow on the events passing through this EventEmitter. */ class CustomEventEmitter extends NodeJSEventEmitter { - EventEmitterSubClass clazz; + EventEmitterSubClass clazz; + CustomEventEmitter() { - if exists(clazz.getAClassReference().getAnInstantiation()) then - this = clazz.getAClassReference().getAnInstantiation() + if exists(clazz.getAClassReference().getAnInstantiation()) + then this = clazz.getAClassReference().getAnInstantiation() else - // In case there are no explicit instantiations of the clazz, then we still want to track data flow between `this` nodes. - // This cannot produce false flow as the `.ref()` method below is always used when creating event-registrations/event-dispatches. + // In case there are no explicit instantiations of the clazz, then we still want to track data flow between `this` nodes. + // This cannot produce false flow as the `.ref()` method below is always used when creating event-registrations/event-dispatches. this = clazz } - + override DataFlow::SourceNode ref() { result = NodeJSEventEmitter.super.ref() and not this = clazz or @@ -988,25 +1023,25 @@ module NodeJSLib { /** * Gets a reference to this server. - */ + */ DataFlow::SourceNode ref() { result = ref(DataFlow::TypeTracker::end()) } } - + /** * A connection opened on a NodeJS net server. - */ + */ private class NodeJSNetServerConnection extends EventEmitter::Range { - NodeJSNetServer server; - - NodeJSNetServerConnection() { - exists(DataFlow::MethodCallNode call | - call = server.ref().getAMethodCall("on") and - call.getArgument(0).mayHaveStringValue("connection") - | - this = call.getCallback(1).getParameter(0) - ) - } - + NodeJSNetServer server; + + NodeJSNetServerConnection() { + exists(DataFlow::MethodCallNode call | + call = server.ref().getAMethodCall("on") and + call.getArgument(0).mayHaveStringValue("connection") + | + this = call.getCallback(1).getParameter(0) + ) + } + DataFlow::SourceNode ref() { result = EventEmitter::trackEventEmitter(this) } } @@ -1030,32 +1065,25 @@ module NodeJSLib { override string getSourceType() { result = "NodeJS server" } } - + /** * An instantiation of the `respjs` library, which is an EventEmitter. */ private class RespJS extends NodeJSEventEmitter { - RespJS() { - this = DataFlow::moduleImport("respjs").getAnInstantiation() - } + RespJS() { this = DataFlow::moduleImport("respjs").getAnInstantiation() } } - + /** - * A event dispatch that serializes the input data and emits the result on the "data" channel. + * A event dispatch that serializes the input data and emits the result on the "data" channel. */ - private class RespWrite extends EventDispatch::DefaultEventDispatch, - DataFlow::MethodCallNode { + private class RespWrite extends EventDispatch::DefaultEventDispatch, DataFlow::MethodCallNode { override RespJS emitter; RespWrite() { this = emitter.ref().getAMethodCall("write") } - - override string getChannel() { - result = "data" - } - override DataFlow::Node getSentItem(int i) { - i = 0 and result = this.getArgument(i) - } + override string getChannel() { result = "data" } + + override DataFlow::Node getSentItem(int i) { i = 0 and result = this.getArgument(i) } } /** diff --git a/javascript/ql/src/semmle/javascript/frameworks/React.qll b/javascript/ql/src/semmle/javascript/frameworks/React.qll index 3b593e66478..3cf93056a8c 100644 --- a/javascript/ql/src/semmle/javascript/frameworks/React.qll +++ b/javascript/ql/src/semmle/javascript/frameworks/React.qll @@ -278,9 +278,7 @@ class FunctionalComponent extends ReactComponent, Function { t.start() and result = DataFlow::valueNode(this) or - exists(DataFlow::TypeTracker t2 | - result = getAComponentCreatorReference(t2).track(t2, t) - ) + exists(DataFlow::TypeTracker t2 | result = getAComponentCreatorReference(t2).track(t2, t)) } override DataFlow::SourceNode getAComponentCreatorReference() { @@ -446,9 +444,7 @@ class ES5Component extends ReactComponent, ObjectExpr { t.start() and result = create or - exists(DataFlow::TypeTracker t2 | - result = getAComponentCreatorReference(t2).track(t2, t) - ) + exists(DataFlow::TypeTracker t2 | result = getAComponentCreatorReference(t2).track(t2, t)) } override DataFlow::SourceNode getAComponentCreatorReference() { @@ -520,7 +516,8 @@ private class FactoryDefinition extends ReactElementDefinition { * Partial invocation for calls to `React.Children.map` or a similar library function * that binds `this` of a callback. */ -private class ReactCallbackPartialInvoke extends DataFlow::PartialInvokeNode::Range, DataFlow::CallNode { +private class ReactCallbackPartialInvoke extends DataFlow::PartialInvokeNode::Range, + DataFlow::CallNode { ReactCallbackPartialInvoke() { exists(string name | // React.Children.map or React.Children.forEach diff --git a/javascript/ql/src/semmle/javascript/frameworks/ShellJS.qll b/javascript/ql/src/semmle/javascript/frameworks/ShellJS.qll index 22605479244..3fc1644fdfd 100644 --- a/javascript/ql/src/semmle/javascript/frameworks/ShellJS.qll +++ b/javascript/ql/src/semmle/javascript/frameworks/ShellJS.qll @@ -160,6 +160,15 @@ module ShellJS { override DataFlow::Node getACommandArgument() { result = getArgument(0) } override predicate isShellInterpreted(DataFlow::Node arg) { arg = getACommandArgument() } + + override predicate isSync() { none() } + + override DataFlow::Node getOptionsArg() { + result = getLastArgument() and + not result = getArgument(0) and + not result.getALocalSource() instanceof DataFlow::FunctionNode and // looks like callback + not result.getALocalSource() instanceof DataFlow::ArrayCreationNode // looks like argumentlist + } } /** diff --git a/javascript/ql/src/semmle/javascript/frameworks/SocketIO.qll b/javascript/ql/src/semmle/javascript/frameworks/SocketIO.qll index 82b716f46dd..45a58f9ec26 100644 --- a/javascript/ql/src/semmle/javascript/frameworks/SocketIO.qll +++ b/javascript/ql/src/semmle/javascript/frameworks/SocketIO.qll @@ -24,28 +24,26 @@ module SocketIO { } /** - * A common superclass for all socket-like objects on the serverside of SocketIO. - * All of the subclasses can be used to send data to SocketIO clients (see the `SendNode` class). + * A common superclass for all socket-like objects on the serverside of SocketIO. + * All of the subclasses can be used to send data to SocketIO clients (see the `SendNode` class). */ abstract private class SocketIOObject extends DataFlow::SourceNode, EventEmitter::Range { /** * Gets a node that refers to this SocketIOObject object. */ abstract DataFlow::SourceNode ref(); - + /** Gets the namespace belonging to this object. */ abstract NamespaceObject getNamespace(); } /** A socket.io server. */ class ServerObject extends SocketIOObject { - ServerObject() { - this = newServer() - } + ServerObject() { this = newServer() } /** Gets the default namespace of this server. */ NamespaceObject getDefaultNamespace() { result = MkNamespace(this, "/") } - + /** Gets the default namespace of this server. */ override NamespaceObject getNamespace() { result = getDefaultNamespace() } @@ -331,9 +329,7 @@ module SocketIO { /** * Gets the namespace to which data is sent. */ - NamespaceObject getNamespace() { - result = emitter.getNamespace() - } + NamespaceObject getNamespace() { result = emitter.getNamespace() } /** Gets the event name associated with the data, if it can be determined. */ override string getChannel() { @@ -366,7 +362,7 @@ module SocketIO { /** * An acknowledgment callback registered when sending a message to a client. - * Responses from clients are received using this callback. + * Responses from clients are received using this callback. */ class SendCallback extends EventRegistration::Range, DataFlow::FunctionNode { SendNode send; @@ -617,7 +613,7 @@ module SocketIOClient { } } - /** + /** * An acknowledgment callback registered when sending a message to a server. * Responses from servers are received using this callback. */ diff --git a/javascript/ql/src/semmle/javascript/frameworks/SystemCommandExecutors.qll b/javascript/ql/src/semmle/javascript/frameworks/SystemCommandExecutors.qll index 7cea87bb3f3..8c9535d5f7e 100644 --- a/javascript/ql/src/semmle/javascript/frameworks/SystemCommandExecutors.qll +++ b/javascript/ql/src/semmle/javascript/frameworks/SystemCommandExecutors.qll @@ -7,15 +7,17 @@ import javascript private class SystemCommandExecutors extends SystemCommandExecution, DataFlow::InvokeNode { int cmdArg; - + int optionsArg; // either a positive number representing the n'th argument, or a negative number representing the n'th last argument (e.g. -2 is the second last argument). boolean shell; + boolean sync; SystemCommandExecutors() { exists(string mod, DataFlow::SourceNode callee | exists(string method | - mod = "cross-spawn" and method = "sync" and cmdArg = 0 and shell = false + mod = "cross-spawn" and method = "sync" and cmdArg = 0 and shell = false and optionsArg = -1 or mod = "execa" and + optionsArg = -1 and ( shell = false and ( @@ -31,27 +33,30 @@ private class SystemCommandExecutors extends SystemCommandExecution, DataFlow::I ) and cmdArg = 0 | - callee = DataFlow::moduleMember(mod, method) + callee = DataFlow::moduleMember(mod, method) and + sync = getSync(method) ) or + sync = false and ( shell = false and ( - mod = "cross-spawn" and cmdArg = 0 + mod = "cross-spawn" and cmdArg = 0 and optionsArg = -1 or - mod = "cross-spawn-async" and cmdArg = 0 + mod = "cross-spawn-async" and cmdArg = 0 and optionsArg = -1 or - mod = "exec-async" and cmdArg = 0 + mod = "exec-async" and cmdArg = 0 and optionsArg = -1 or - mod = "execa" and cmdArg = 0 + mod = "execa" and cmdArg = 0 and optionsArg = -1 ) or shell = true and ( mod = "exec" and + optionsArg = -2 and cmdArg = 0 or - mod = "remote-exec" and cmdArg = 1 + mod = "remote-exec" and cmdArg = 1 and optionsArg = -1 ) ) and callee = DataFlow::moduleImport(mod) @@ -65,4 +70,30 @@ private class SystemCommandExecutors extends SystemCommandExecution, DataFlow::I override predicate isShellInterpreted(DataFlow::Node arg) { arg = getACommandArgument() and shell = true } + + override DataFlow::Node getArgumentList() { shell = false and result = getArgument(1) } + + override predicate isSync() { sync = true } + + override DataFlow::Node getOptionsArg() { + ( + if optionsArg < 0 + then + result = getArgument(getNumArgument() + optionsArg) and + getNumArgument() + optionsArg > cmdArg + else result = getArgument(optionsArg) + ) and + not result.getALocalSource() instanceof DataFlow::FunctionNode and // looks like callback + not result.getALocalSource() instanceof DataFlow::ArrayCreationNode // looks like argumentlist + } +} + +/** + * Gets a boolean reflecting if the name ends with "sync" or "Sync". + */ +bindingset[name] +private boolean getSync(string name) { + if name.suffix(name.length() - 4) = "Sync" or name.suffix(name.length() - 4) = "sync" + then result = true + else result = false } diff --git a/javascript/ql/src/semmle/javascript/frameworks/Typeahead.qll b/javascript/ql/src/semmle/javascript/frameworks/Typeahead.qll index 161c738b311..83001dd2e09 100644 --- a/javascript/ql/src/semmle/javascript/frameworks/Typeahead.qll +++ b/javascript/ql/src/semmle/javascript/frameworks/Typeahead.qll @@ -91,7 +91,8 @@ module Typeahead { TypeaheadSuggestionFunction() { // Matches `$(...).typeahead({..}, { templates: { suggestion: } })`. - this = typeaheadCall + this = + typeaheadCall .getOptionArgument(1, "templates") .getALocalSource() .getAPropertyWrite("suggestion") diff --git a/javascript/ql/src/semmle/javascript/frameworks/WebSocket.qll b/javascript/ql/src/semmle/javascript/frameworks/WebSocket.qll index ffe05017057..65f458153f4 100644 --- a/javascript/ql/src/semmle/javascript/frameworks/WebSocket.qll +++ b/javascript/ql/src/semmle/javascript/frameworks/WebSocket.qll @@ -83,7 +83,9 @@ module ClientWebSocket { /** * Gets a handler, that is registered using method `methodName` and receives messages sent to `emitter`. */ - private DataFlow::FunctionNode getAMessageHandler(ClientWebSocket::ClientSocket emitter, string methodName) { + private DataFlow::FunctionNode getAMessageHandler( + ClientWebSocket::ClientSocket emitter, string methodName + ) { exists(DataFlow::CallNode call | call = emitter.getAMemberCall(methodName) and call.getArgument(0).mayHaveStringValue("message") and @@ -93,8 +95,8 @@ module ClientWebSocket { /** * A handler that receives a message using the WebSocket API. - * The WebSocket API is used both by the WebSocket library in browsers, and the same API is also implemented as part of the "ws" library. - * This class therefore models both the WebSocket library, and a subset of the "ws" library. + * The WebSocket API is used both by the WebSocket library in browsers, and the same API is also implemented as part of the "ws" library. + * This class therefore models both the WebSocket library, and a subset of the "ws" library. */ private class WebSocketReceiveNode extends ClientWebSocket::ReceiveNode { WebSocketReceiveNode() { @@ -109,18 +111,16 @@ module ClientWebSocket { } /** - * A handler that receives a message using the API from the "ws" library. - * The "ws" library additionally implements the WebSocket API, which is modeled in the `WebSocketReceiveNode` class. + * A handler that receives a message using the API from the "ws" library. + * The "ws" library additionally implements the WebSocket API, which is modeled in the `WebSocketReceiveNode` class. */ private class WSReceiveNode extends ClientWebSocket::ReceiveNode { - WSReceiveNode () { + WSReceiveNode() { emitter.isNode() and this = getAMessageHandler(emitter, EventEmitter::on()) } - override DataFlow::Node getReceivedItem(int i) { - i = 0 and result = this.getParameter(0) - } + override DataFlow::Node getReceivedItem(int i) { i = 0 and result = this.getParameter(0) } } } @@ -134,7 +134,8 @@ module ServerWebSocket { class ServerSocket extends EventEmitter::Range, DataFlow::SourceNode { ServerSocket() { exists(DataFlow::CallNode onCall | - onCall = DataFlow::moduleImport("ws") + onCall = + DataFlow::moduleImport("ws") .getAConstructorInvocation("Server") .getAMemberCall(EventEmitter::on()) and onCall.getArgument(0).mayHaveStringValue("connection") diff --git a/javascript/ql/src/semmle/javascript/frameworks/jQuery.qll b/javascript/ql/src/semmle/javascript/frameworks/jQuery.qll index 458f0710931..61ad6d52878 100644 --- a/javascript/ql/src/semmle/javascript/frameworks/jQuery.qll +++ b/javascript/ql/src/semmle/javascript/frameworks/jQuery.qll @@ -125,7 +125,7 @@ private class JQueryDomElementDefinition extends DOM::ElementDefinition, @callex /** * An attribute defined using jQuery APIs. */ -abstract private class JQueryAttributeDefinition extends DOM::AttributeDefinition {} +abstract private class JQueryAttributeDefinition extends DOM::AttributeDefinition { } /** * An attribute definition supplied when constructing a DOM element using `$(...)`. @@ -150,9 +150,7 @@ private class JQueryAttributeDefinitionInElement extends JQueryAttributeDefiniti } /** Gets the `attr` or `prop` string. */ -private string attrOrProp() { - result = "attr" or result = "prop" -} +private string attrOrProp() { result = "attr" or result = "prop" } /** * An attribute definition using `elt.attr(name, value)` or `elt.prop(name, value)` @@ -382,9 +380,7 @@ module JQuery { } /** A source of jQuery objects from the AST-based `JQueryObject` class. */ - private DataFlow::Node legacyObjectSource() { - result = any(JQueryObjectInternal e).flow() - } + private DataFlow::Node legacyObjectSource() { result = any(JQueryObjectInternal e).flow() } /** Gets a source of jQuery objects. */ private DataFlow::SourceNode objectSource(DataFlow::TypeTracker t) { diff --git a/javascript/ql/src/semmle/javascript/linters/JSLint.qll b/javascript/ql/src/semmle/javascript/linters/JSLint.qll index 4c94db457fb..bfe8de1768d 100644 --- a/javascript/ql/src/semmle/javascript/linters/JSLint.qll +++ b/javascript/ql/src/semmle/javascript/linters/JSLint.qll @@ -23,9 +23,8 @@ abstract class JSLintDirective extends SlashStarComment { * and with end-of-line characters replaced by spaces. */ string getContent() { - result = getText() - .regexpReplaceAll("[\\n\\r\\u2028\\u2029]", " ") - .regexpCapture("\\s*\\w+ (.*)", 1) + result = + getText().regexpReplaceAll("[\\n\\r\\u2028\\u2029]", " ").regexpCapture("\\s*\\w+ (.*)", 1) } /** diff --git a/javascript/ql/src/semmle/javascript/security/SensitiveActions.qll b/javascript/ql/src/semmle/javascript/security/SensitiveActions.qll index d3bcc289a50..0a5a701ab82 100644 --- a/javascript/ql/src/semmle/javascript/security/SensitiveActions.qll +++ b/javascript/ql/src/semmle/javascript/security/SensitiveActions.qll @@ -63,10 +63,11 @@ module HeuristicNames { /** * Gets a regular expression that identifies strings that may indicate the presence of data - * that is hashed or encrypted, and hence rendered non-sensitive. + * that is hashed or encrypted, and hence rendered non-sensitive, or contains special characters + * suggesting nouns within the string do not represent the meaning of the whole string (e.g. a URL or a SQL query). */ string notSensitive() { - result = "(?is).*(redact|censor|obfuscate|hash|md5|sha|((?|<| |\\$|&|,|\\`| |;", _, _)) +} + +/** + * Gets the constant string parts from a data-flow node. + * Either the result is a constant string value that the node can hold, or the node is a string-concatenation and the result is the string parts from the concatenation. + */ +private string getConstantStringParts(DataFlow::Node node) { + node.mayHaveStringValue(result) + or + result = node.(StringOps::ConcatenationRoot).getConstantStringParts() +} + +/** + * A call to a useless use of `cat`. + */ +class UselessCat extends CommandCall { + UselessCat() { + this = command and + isACallTo(getACatExecuteable()) and + // There is a file to read, it's not just spawning `cat`. + not ( + not exists(getArgumentList()) and + getArgument(0).mayHaveStringValue(getACatExecuteable()) + ) and + // wildcards, pipes, redirections, other bash features, and multiple files (spaces) are OK. + not containsNonTrivialShellChar(getNonCommandConstantString()) and + // Only acceptable option is "encoding", everything else is non-trivial to emulate with fs.readFile. + ( + not exists(getOptionsArg()) + or + forex(string prop | exists(getOptionsArg().getALocalSource().getAPropertyWrite(prop)) | + prop = "encoding" + ) + ) and + // If there is a callback, then it must either have one or two parameters, or if there is a third parameter it must be unused. + ( + not exists(getCallback()) + or + exists(DataFlow::FunctionNode func | func = getCallback() | + func.getNumParameter() = 1 + or + func.getNumParameter() = 2 + or + // `exec` can use 3 parameters, `readFile` can only use two, so it is OK to have a third parameter if it is unused, + func.getNumParameter() = 3 and + not exists(SSA::definition(func.getParameter(2).getParameter())) + ) + ) and + // The process returned by an async call is unused. + ( + isSync() + or + inVoidContext(this.getEnclosingExpr()) + or + this.getEnclosingExpr() = any(UnusedLocal v).getAnAssignedExpr() + ) + } +} + +/** + * Gets a string used to call `cat`. + */ +private string getACatExecuteable() { result = "cat" or result = "/bin/cat" } + +/** + * Predicates for creating an equivalent call to `fs.readFile` from a command execution of `cat`. + */ +module PrettyPrintCatCall { + /** + * Create a string representation of an equivalent call to `fs.readFile` for a given command execution `cat`. + */ + string createReadFileCall(UselessCat cat) { + exists(string sync, string extraArg, string callback, string fileArg | + (if cat.isSync() then sync = "Sync" else sync = "") and + ( + exists(cat.getOptionsArg()) and + ( + extraArg = ", " + createOptionsArg(cat.getOptionsArg()) + or + not exists(createOptionsArg(cat.getOptionsArg())) and + extraArg = ", ..." + ) + or + extraArg = "" and not exists(cat.getOptionsArg()) + ) and + ( + callback = createCallbackString(cat.getCallback()) + or + callback = "" and not exists(cat.getCallback()) + ) and + fileArg = createFileArgument(cat).trim() and + // sanity check in case of surprising `toString` results, other uses of `containsNonTrivialBashChar` should ensure that this conjunct will hold most of the time + not containsNonTrivialShellChar(fileArg.regexpReplaceAll("\\$|\\`| ", "")) // string concat might contain " ", template strings might contain "$" or `, and that is OK. + | + result = "fs.readFile" + sync + "(" + fileArg + extraArg + callback + ")" + ) + } + + /** + * Create a string representation of the expression that determines what file is read by `cat`. + */ + string createFileArgument(CommandCall cat) { + if cat.hasArgumentList() + then + cat.getArgument(0).mayHaveStringValue(getACatExecuteable()) and + result = createFileThatIsReadFromCommandList(cat) + else result = createFileArgumentWithoutCat(cat.getArgument(0)) + } + + /** + * Create a string representing the callback `func`. + */ + string createCallbackString(DataFlow::FunctionNode func) { + exists(string params | params = createCallbackParams(func) | + if func.getFunction() instanceof ArrowFunctionExpr + then + if func.getFunction().getBody() instanceof Expr + then result = ", (" + params + ") => ..." + else result = ", (" + params + ") => {...}" + else result = ", function(" + params + ") {...}" + ) + } + + /** + * Create a string concatenation of the parameter names in a function `func`. + */ + private string createCallbackParams(DataFlow::FunctionNode func) { + result = + concat(int i | + i = [0 .. func.getNumParameter()] + | + func.getParameter(i).getName(), ", " order by i + ) + } + + /** + * Create a string representation of the options argument `arg` from an exec-like call. + */ + private string createOptionsArg(DataFlow::Node arg) { + result = arg.asExpr().(VarAccess).getVariable().getName() + or + // fall back to toString(), but ensure that we don't have dots in the middle. + result = arg.(DataFlow::ObjectLiteralNode).toString() and not result.regexpMatch(".*\\.\\..*") + } + + /** + * Create a string representation of a string concatenation. + */ + private string createConcatRepresentation(StringOps::ConcatenationRoot root) { + // String concat + not exists(root.getStringValue()) and + not root.asExpr() instanceof TemplateLiteral and + forall(Expr e | e = root.getALeaf().asExpr() | exists(createLeafRepresentation(e))) and + result = + concat(Expr leaf | + leaf = root.getALeaf().asExpr() + | + createLeafRepresentation(leaf), " + " order by leaf.getFirstToken().getIndex() + ) + or + // Template string + exists(TemplateLiteral template | template = root.asExpr() | + forall(Expr e | e = template.getAChild() | exists(createTemplateElementRepresentation(e))) and + result = + "`" + + concat(int i | + i = [0 .. template.getNumChild() - 1] + | + createTemplateElementRepresentation(template.getChild(i)) order by i + ) + "`" + ) + } + + /** + * Create a string representing the expression needed to re-create the value for a leaf in a string-concatenation. + */ + private string createLeafRepresentation(Expr e) { + result = "\"" + e.getStringValue() + "\"" or + result = e.(VarAccess).getVariable().getName() + } + + /** + * Create a string representing the expression needed to re-create the value for an element of a template string. + */ + private string createTemplateElementRepresentation(Expr e) { + result = "${" + e.(VarAccess).getVariable().getName() + "}" + or + result = e.(TemplateElement).getValue() + } + + /** + * Create a string representing an expression that gets the file read by a call to `cat`. + * The input `arg` is the node that determines the commandline where `cat` is invoked. + */ + private string createFileArgumentWithoutCat(DataFlow::Node arg) { + exists(string cat | cat = getACatExecuteable() | + exists(string command | arg.mayHaveStringValue(command) | + command.prefix(cat.length()) = cat and + result = "\"" + command.suffix(cat.length()).trim() + "\"" + ) + or + exists(StringOps::ConcatenationRoot root, string printed, string quote | + root = arg and printed = createConcatRepresentation(root).suffix(1) // remove initial quote + | + (if root.asExpr() instanceof TemplateLiteral then quote = "`" else quote = "\"") and + root.getFirstLeaf().getStringValue().prefix(cat.length()) = cat and + exists(string rawConcat | rawConcat = quote + printed.suffix(cat.length()).trim() | + result = createSimplifiedStringConcat(rawConcat) + ) + ) + ) + } + + /** + * Create a simplified and equivalent string concatenation for a given string concatenation `str` + */ + bindingset[str] + private string createSimplifiedStringConcat(string str) { + // Remove an initial ""+ (e.g. in `""+file`) + if str.prefix(5) = "\"\" + " + then result = str.suffix(5) + else + // prettify `${newpath}` to just newpath + if + str.prefix(3) = "`${" and + str.suffix(str.length() - 2) = "}`" and + not str.suffix(3).matches("%{%") + then result = str.prefix(str.length() - 2).suffix(3) + else result = str + } + + /** + * Create the file that is read for a call with an explicit command list (e.g. `child_process.execFile/execFileSync`). + */ + string createFileThatIsReadFromCommandList(CommandCall call) { + exists(DataFlow::ArrayCreationNode array, DataFlow::Node element | + array = call.getArgumentList().(DataFlow::ArrayCreationNode) and + array.getSize() = 1 and + element = array.getElement(0) + | + result = element.asExpr().(VarAccess).getVariable().getName() or + result = "\"" + element.getStringValue() + "\"" or + result = createConcatRepresentation(element) + ) + } +} diff --git a/javascript/ql/src/semmle/javascript/security/dataflow/CleartextLogging.qll b/javascript/ql/src/semmle/javascript/security/dataflow/CleartextLogging.qll index 240e8e43309..615f8ea2ead 100644 --- a/javascript/ql/src/semmle/javascript/security/dataflow/CleartextLogging.qll +++ b/javascript/ql/src/semmle/javascript/security/dataflow/CleartextLogging.qll @@ -37,7 +37,7 @@ module CleartextLogging { override predicate isSanitizerEdge(DataFlow::Node pred, DataFlow::Node succ) { succ.(DataFlow::PropRead).getBase() = pred } - + override predicate isAdditionalTaintStep(DataFlow::Node src, DataFlow::Node trg) { // A taint propagating data flow edge through objects: a tainted write taints the entire object. exists(DataFlow::PropWrite write | diff --git a/javascript/ql/src/semmle/javascript/security/dataflow/CleartextLoggingCustomizations.qll b/javascript/ql/src/semmle/javascript/security/dataflow/CleartextLoggingCustomizations.qll index 3c7a8d818a7..3ceadf90e5f 100644 --- a/javascript/ql/src/semmle/javascript/security/dataflow/CleartextLoggingCustomizations.qll +++ b/javascript/ql/src/semmle/javascript/security/dataflow/CleartextLoggingCustomizations.qll @@ -15,7 +15,7 @@ module CleartextLogging { abstract class Source extends DataFlow::Node { /** Gets a string that describes the type of this data flow source. */ abstract string describe(); - + abstract DataFlow::FlowLabel getLabel(); } @@ -23,16 +23,14 @@ module CleartextLogging { * A data flow sink for clear-text logging of sensitive information. */ abstract class Sink extends DataFlow::Node { - DataFlow::FlowLabel getLabel() { - result.isDataOrTaint() - } + DataFlow::FlowLabel getLabel() { result.isDataOrTaint() } } /** * A barrier for clear-text logging of sensitive information. */ abstract class Barrier extends DataFlow::Node { } - + /** * A call to `.replace()` that seems to mask sensitive information. */ @@ -43,8 +41,7 @@ module CleartextLogging { reg = this.getArgument(0).getALocalSource().asExpr() and reg.isGlobal() and any(RegExpDot term).getLiteral() = reg - ) - and + ) and exists(this.getArgument(1).getStringValue()) } } @@ -129,10 +126,8 @@ module CleartextLogging { } override string describe() { result = "an access to " + name } - - override DataFlow::FlowLabel getLabel() { - result.isData() - } + + override DataFlow::FlowLabel getLabel() { result.isData() } } /** An access to a variable or property that might contain a password. */ @@ -157,10 +152,8 @@ module CleartextLogging { } override string describe() { result = "an access to " + name } - - override DataFlow::FlowLabel getLabel() { - result.isData() - } + + override DataFlow::FlowLabel getLabel() { result.isData() } } /** A call that might return a password. */ @@ -173,33 +166,27 @@ module CleartextLogging { } override string describe() { result = "a call to " + name } - - override DataFlow::FlowLabel getLabel() { - result.isData() - } + + override DataFlow::FlowLabel getLabel() { result.isData() } } /** An access to the sensitive object `process.env`. */ class ProcessEnvSource extends Source { - ProcessEnvSource() { - this = NodeJSLib::process().getAPropertyRead("env") - } + ProcessEnvSource() { this = NodeJSLib::process().getAPropertyRead("env") } override string describe() { result = "process environment" } - + override DataFlow::FlowLabel getLabel() { - result.isData() or + result.isData() or result instanceof PartiallySensitiveMap } } - + /** * A flow label describing a map that might contain sensitive information in some properties. - * Property reads on such maps where the property name is fixed is unlikely to leak sensitive information. + * Property reads on such maps where the property name is fixed is unlikely to leak sensitive information. */ class PartiallySensitiveMap extends DataFlow::FlowLabel { - PartiallySensitiveMap() { - this = "PartiallySensitiveMap" - } + PartiallySensitiveMap() { this = "PartiallySensitiveMap" } } } diff --git a/javascript/ql/src/semmle/javascript/security/dataflow/CommandInjection.qll b/javascript/ql/src/semmle/javascript/security/dataflow/CommandInjection.qll index c2c1425ab82..ff40919e25e 100644 --- a/javascript/ql/src/semmle/javascript/security/dataflow/CommandInjection.qll +++ b/javascript/ql/src/semmle/javascript/security/dataflow/CommandInjection.qll @@ -35,5 +35,4 @@ module CommandInjection { override predicate isSanitizer(DataFlow::Node node) { node instanceof Sanitizer } } - } diff --git a/javascript/ql/src/semmle/javascript/security/dataflow/DOM.qll b/javascript/ql/src/semmle/javascript/security/dataflow/DOM.qll index 5b639abfa11..d02e85609aa 100644 --- a/javascript/ql/src/semmle/javascript/security/dataflow/DOM.qll +++ b/javascript/ql/src/semmle/javascript/security/dataflow/DOM.qll @@ -85,8 +85,13 @@ class DomMethodCallExpr extends MethodCallExpr { name = "setAttributeNS" and argPos = 2 ) and // restrict to potentially dangerous attributes - exists(string attr | - attr = "action" or attr = "formaction" or attr = "href" or attr = "src" or attr = "xlink:href" | + exists(string attr | + attr = "action" or + attr = "formaction" or + attr = "href" or + attr = "src" or + attr = "xlink:href" + | getArgument(argPos - 1).getStringValue().toLowerCase() = attr ) ) diff --git a/javascript/ql/src/semmle/javascript/security/dataflow/DomBasedXssCustomizations.qll b/javascript/ql/src/semmle/javascript/security/dataflow/DomBasedXssCustomizations.qll index b74977c8cb5..34c8eeeac50 100644 --- a/javascript/ql/src/semmle/javascript/security/dataflow/DomBasedXssCustomizations.qll +++ b/javascript/ql/src/semmle/javascript/security/dataflow/DomBasedXssCustomizations.qll @@ -1,9 +1,8 @@ /** * Provides default sources for reasoning about DOM-based - * cross-site scripting vulnerabilities. + * cross-site scripting vulnerabilities. */ - import javascript module DomBasedXss { diff --git a/javascript/ql/src/semmle/javascript/security/dataflow/ExceptionXss.qll b/javascript/ql/src/semmle/javascript/security/dataflow/ExceptionXss.qll index c64ecfd361d..a23e9fecf4c 100644 --- a/javascript/ql/src/semmle/javascript/security/dataflow/ExceptionXss.qll +++ b/javascript/ql/src/semmle/javascript/security/dataflow/ExceptionXss.qll @@ -37,7 +37,8 @@ module ExceptionXss { * Holds if `node` is unlikely to cause an exception containing sensitive information to be thrown. */ private predicate isUnlikelyToThrowSensitiveInformation(DataFlow::Node node) { - node = any(DataFlow::CallNode call | call.getCalleeName() = getAnUnlikelyToThrowMethodName()) + node = + any(DataFlow::CallNode call | call.getCalleeName() = getAnUnlikelyToThrowMethodName()) .getAnArgument() or node = DataFlow::globalVarRef("console").getAMemberCall(_).getAnArgument() diff --git a/javascript/ql/src/semmle/javascript/security/dataflow/IndirectCommandArgument.qll b/javascript/ql/src/semmle/javascript/security/dataflow/IndirectCommandArgument.qll index e0c8e0a05d9..3873f90c810 100644 --- a/javascript/ql/src/semmle/javascript/security/dataflow/IndirectCommandArgument.qll +++ b/javascript/ql/src/semmle/javascript/security/dataflow/IndirectCommandArgument.qll @@ -34,7 +34,7 @@ private DataFlow::Node commandArgument(SystemCommandExecution sys, DataFlow::Typ } /** - * Gets a data-flow node whose value ends up being interpreted as the command argument in `sys`. + * Gets a data-flow node whose value ends up being interpreted as the command argument in `sys`. */ private DataFlow::Node commandArgument(SystemCommandExecution sys) { result = commandArgument(sys, DataFlow::TypeBackTracker::end()) @@ -48,9 +48,7 @@ private DataFlow::SourceNode argumentList(SystemCommandExecution sys, DataFlow:: t.start() and result = sys.getArgumentList().getALocalSource() or - exists(DataFlow::TypeBackTracker t2, DataFlow::SourceNode pred | - pred = argumentList(sys, t2) - | + exists(DataFlow::TypeBackTracker t2, DataFlow::SourceNode pred | pred = argumentList(sys, t2) | result = pred.backtrack(t2, t) or t = t2.continue() and @@ -59,7 +57,7 @@ private DataFlow::SourceNode argumentList(SystemCommandExecution sys, DataFlow:: } /** - * Gets a data-flow node whose value ends up being interpreted as the argument list in `sys`. + * Gets a data-flow node whose value ends up being interpreted as the argument list in `sys`. */ private DataFlow::SourceNode argumentList(SystemCommandExecution sys) { result = argumentList(sys, DataFlow::TypeBackTracker::end()) diff --git a/javascript/ql/src/semmle/javascript/security/dataflow/IndirectCommandInjectionCustomizations.qll b/javascript/ql/src/semmle/javascript/security/dataflow/IndirectCommandInjectionCustomizations.qll index df7376188a5..dba4fb62763 100644 --- a/javascript/ql/src/semmle/javascript/security/dataflow/IndirectCommandInjectionCustomizations.qll +++ b/javascript/ql/src/semmle/javascript/security/dataflow/IndirectCommandInjectionCustomizations.qll @@ -45,11 +45,14 @@ module IndirectCommandInjection { class ParsedCommandLineArgumentsAsSource extends Source { ParsedCommandLineArgumentsAsSource() { // `require('get-them-args')(...)` => `{ unknown: [], a: ... b: ... }` - this = DataFlow::moduleImport("get-them-args").getACall() or + this = DataFlow::moduleImport("get-them-args").getACall() + or // `require('minimist')(...)` => `{ _: [], a: ... b: ... }` - this = DataFlow::moduleImport("minimist").getACall() or + this = DataFlow::moduleImport("minimist").getACall() + or // `require('yargs').argv` => `{ _: [], a: ... b: ... }` - this = DataFlow::moduleMember("yargs", "argv") or + this = DataFlow::moduleMember("yargs", "argv") + or // `require('optimist').argv` => `{ _: [], a: ... b: ... }` this = DataFlow::moduleMember("optimist", "argv") } diff --git a/javascript/ql/src/semmle/javascript/security/dataflow/LoopBoundInjectionCustomizations.qll b/javascript/ql/src/semmle/javascript/security/dataflow/LoopBoundInjectionCustomizations.qll index 6bf1ef5634a..8e3857d049b 100644 --- a/javascript/ql/src/semmle/javascript/security/dataflow/LoopBoundInjectionCustomizations.qll +++ b/javascript/ql/src/semmle/javascript/security/dataflow/LoopBoundInjectionCustomizations.qll @@ -33,9 +33,7 @@ module LoopBoundInjection { */ class ArrayIterationLoop extends Stmt { LocalVariable indexVariable; - LoopStmt loop; - DataFlow::PropRead lengthRead; ArrayIterationLoop() { @@ -203,9 +201,7 @@ module LoopBoundInjection { ) or // Similar to the loop sink - the existence of an early-exit usually means that no DoS can happen. - exists(ThrowStmt throw | - throw.getTarget() = func.asExpr() - ) + exists(ThrowStmt throw | throw.getTarget() = func.asExpr()) or // A crash happens looking up the index variable. hasCrashingArrayAccess(func.getParameter(1).getParameter().getVariable()) @@ -269,7 +265,6 @@ module LoopBoundInjection { class LengthCheckSanitizerGuard extends TaintTracking::LabeledSanitizerGuardNode, DataFlow::ValueNode { override RelationalComparison astNode; - DataFlow::PropRead propRead; LengthCheckSanitizerGuard() { diff --git a/javascript/ql/src/semmle/javascript/security/dataflow/MissingRateLimiting.qll b/javascript/ql/src/semmle/javascript/security/dataflow/MissingRateLimiting.qll index 0a62b8cda3f..82db9e0fad3 100644 --- a/javascript/ql/src/semmle/javascript/security/dataflow/MissingRateLimiting.qll +++ b/javascript/ql/src/semmle/javascript/security/dataflow/MissingRateLimiting.qll @@ -150,9 +150,8 @@ class RouteHandlerLimitedByExpressLimiter extends RateLimitedRouteHandlerExpr { RouteHandlerLimitedByExpressLimiter() { exists(DataFlow::ModuleImportNode expressLimiter | expressLimiter.getPath() = "express-limiter" and - expressLimiter.getACall().getArgument(0).getALocalSource().asExpr() = this - .getSetup() - .getRouter() + expressLimiter.getACall().getArgument(0).getALocalSource().asExpr() = + this.getSetup().getRouter() ) } } diff --git a/javascript/ql/src/semmle/javascript/security/dataflow/TaintedPath.qll b/javascript/ql/src/semmle/javascript/security/dataflow/TaintedPath.qll index 8dca676af46..06b0a6eb9e6 100644 --- a/javascript/ql/src/semmle/javascript/security/dataflow/TaintedPath.qll +++ b/javascript/ql/src/semmle/javascript/security/dataflow/TaintedPath.qll @@ -36,7 +36,7 @@ module TaintedPath { guard instanceof StartsWithDirSanitizer or guard instanceof IsAbsoluteSanitizer or guard instanceof ContainsDotDotSanitizer or - guard instanceof RelativePathStartsWithDotDotSanitizer or + guard instanceof RelativePathStartsWithSanitizer or guard instanceof IsInsideCheckSanitizer } @@ -197,6 +197,15 @@ module TaintedPath { srclabel = dstlabel ) or + // foo.replace(/\./, "") and similar + exists(DotRemovingReplaceCall call | + src = call.getInput() and + dst = call.getOutput() and + srclabel.isAbsolute() and + dstlabel.isAbsolute() and + dstlabel.isNormalized() + ) + or // path.join() exists(DataFlow::CallNode join, int n | join = NodeJSLib::Path::moduleMember("join").getACall() diff --git a/javascript/ql/src/semmle/javascript/security/dataflow/TaintedPathCustomizations.qll b/javascript/ql/src/semmle/javascript/security/dataflow/TaintedPathCustomizations.qll index a3060a74eb8..c55fa8bad33 100644 --- a/javascript/ql/src/semmle/javascript/security/dataflow/TaintedPathCustomizations.qll +++ b/javascript/ql/src/semmle/javascript/security/dataflow/TaintedPathCustomizations.qll @@ -106,12 +106,10 @@ module TaintedPath { } /** - * A flow label representing an array of path elements that may include "..". - */ + * A flow label representing an array of path elements that may include "..". + */ class SplitPath extends DataFlow::FlowLabel { - SplitPath() { - this = "splitPath" - } + SplitPath() { this = "splitPath" } } } @@ -215,17 +213,51 @@ module TaintedPath { output = this ) or - // non-global replace or replace of something other than /\.\./g + // non-global replace or replace of something other than /\.\./g, /[/]/g, or /[\.]/g. this.getCalleeName() = "replace" and input = getReceiver() and output = this and - not exists(RegExpLiteral literal, RegExpSequence seq | + not exists(RegExpLiteral literal, RegExpTerm term | getArgument(0).getALocalSource().asExpr() = literal and literal.isGlobal() and - literal.getRoot() = seq and - seq.getChild(0).(RegExpConstant).getValue() = "." and - seq.getChild(1).(RegExpConstant).getValue() = "." and - seq.getNumChild() = 2 + literal.getRoot() = term + | + term.getAMatchedString() = "/" or + term.getAMatchedString() = "." or + term.getAMatchedString() = ".." + ) + } + + /** + * Gets the input path to be normalized. + */ + DataFlow::Node getInput() { result = input } + + /** + * Gets the normalized path. + */ + DataFlow::Node getOutput() { result = output } + } + + /** + * A call that removes all "." or ".." from a path, without also removing all forward slashes. + */ + class DotRemovingReplaceCall extends DataFlow::CallNode { + DataFlow::Node input; + DataFlow::Node output; + + DotRemovingReplaceCall() { + this.getCalleeName() = "replace" and + input = getReceiver() and + output = this and + exists(RegExpLiteral literal, RegExpTerm term | + getArgument(0).getALocalSource().asExpr() = literal and + literal.isGlobal() and + literal.getRoot() = term and + not term.getAMatchedString() = "/" + | + term.getAMatchedString() = "." or + term.getAMatchedString() = ".." ) } @@ -370,32 +402,50 @@ module TaintedPath { * // pathname is safe * } * ``` + * + * or + * ``` + * var relative = path.resolve(pathname); // or path.normalize + * if(relative.startsWith(webroot) { + * // pathname is safe + * } else { + * // pathname is unsafe + * } + * ``` */ - class RelativePathStartsWithDotDotSanitizer extends DataFlow::BarrierGuardNode { + class RelativePathStartsWithSanitizer extends DataFlow::BarrierGuardNode { StringOps::StartsWith startsWith; - DataFlow::CallNode relativeCall; + DataFlow::CallNode pathCall; + string member; - RelativePathStartsWithDotDotSanitizer() { + RelativePathStartsWithSanitizer() { + (member = "relative" or member = "resolve" or member = "normalize") and this = startsWith and - relativeCall = NodeJSLib::Path::moduleMember("relative").getACall() and + pathCall = NodeJSLib::Path::moduleMember(member).getACall() and ( - startsWith.getBaseString().getALocalSource() = relativeCall + startsWith.getBaseString().getALocalSource() = pathCall or startsWith .getBaseString() .getALocalSource() .(NormalizingPathCall) .getInput() - .getALocalSource() = relativeCall + .getALocalSource() = pathCall ) and - isDotDotSlashPrefix(startsWith.getSubstring()) + (not member = "relative" or isDotDotSlashPrefix(startsWith.getSubstring())) } override predicate blocks(boolean outcome, Expr e) { - e = relativeCall.getArgument(1).asExpr() and outcome = startsWith.getPolarity().booleanNot() + member = "relative" and + e = pathCall.getArgument(1).asExpr() and + outcome = startsWith.getPolarity().booleanNot() + or + not member = "relative" and + e = pathCall.getArgument(0).asExpr() and + outcome = startsWith.getPolarity() } } - + /** * A guard node for a variable in a negative condition, such as `x` in `if(!x)`. */ diff --git a/javascript/ql/src/semmle/javascript/security/dataflow/UnsafeJQueryPlugin.qll b/javascript/ql/src/semmle/javascript/security/dataflow/UnsafeJQueryPlugin.qll index b08cbb2f5f8..e9981d97db0 100644 --- a/javascript/ql/src/semmle/javascript/security/dataflow/UnsafeJQueryPlugin.qll +++ b/javascript/ql/src/semmle/javascript/security/dataflow/UnsafeJQueryPlugin.qll @@ -34,7 +34,6 @@ module UnsafeJQueryPlugin { override predicate isSanitizerEdge(DataFlow::Node pred, DataFlow::Node succ) { // prefixing prevents forced html/css confusion: - // prefixing through concatenation: StringConcatenation::taintStep(pred, succ, _, any(int i | i >= 1)) or diff --git a/javascript/ql/src/semmle/javascript/security/dataflow/Xss.qll b/javascript/ql/src/semmle/javascript/security/dataflow/Xss.qll index d9567862fc6..47aff96c9c3 100644 --- a/javascript/ql/src/semmle/javascript/security/dataflow/Xss.qll +++ b/javascript/ql/src/semmle/javascript/security/dataflow/Xss.qll @@ -243,10 +243,8 @@ module DomBasedXss { VHtmlSourceWrite() { exists(Vue::Instance instance, string expr | - attr.getAttr().getRoot() = instance - .getTemplateElement() - .(Vue::Template::HtmlElement) - .getElement() and + attr.getAttr().getRoot() = + instance.getTemplateElement().(Vue::Template::HtmlElement).getElement() and expr = attr.getAttr().getValue() and // only support for simple identifier expressions expr.regexpMatch("(?i)[a-z0-9_]+") and @@ -307,18 +305,72 @@ module ReflectedXss { * a content type that does not (case-insensitively) contain the string "html". This * is to prevent us from flagging plain-text or JSON responses as vulnerable. */ - private class HttpResponseSink extends Sink, DataFlow::ValueNode { + class HttpResponseSink extends Sink, DataFlow::ValueNode { override HTTP::ResponseSendArgument astNode; - HttpResponseSink() { not nonHtmlContentType(astNode.getRouteHandler()) } + HttpResponseSink() { not exists(getANonHtmlHeaderDefinition(astNode)) } + } + + /** + * Gets a HeaderDefinition that defines a non-html content-type for `send`. + */ + HTTP::HeaderDefinition getANonHtmlHeaderDefinition(HTTP::ResponseSendArgument send) { + exists(HTTP::RouteHandler h | + send.getRouteHandler() = h and + result = nonHtmlContentTypeHeader(h) + | + // The HeaderDefinition affects a response sent at `send`. + headerAffects(result, send) + ) } /** * Holds if `h` may send a response with a content type other than HTML. */ - private predicate nonHtmlContentType(HTTP::RouteHandler h) { - exists(HTTP::HeaderDefinition hd | hd = h.getAResponseHeader("content-type") | - not exists(string tp | hd.defines("content-type", tp) | tp.regexpMatch("(?i).*html.*")) + HTTP::HeaderDefinition nonHtmlContentTypeHeader(HTTP::RouteHandler h) { + result = h.getAResponseHeader("content-type") and + not exists(string tp | result.defines("content-type", tp) | tp.regexpMatch("(?i).*html.*")) + } + + /** + * Holds if a header set in `header` is likely to affect a response sent at `sender`. + */ + predicate headerAffects(HTTP::HeaderDefinition header, HTTP::ResponseSendArgument sender) { + sender.getRouteHandler() = header.getRouteHandler() and + ( + // `sender` is affected by a dominating `header`. + header.getBasicBlock().(ReachableBasicBlock).dominates(sender.getBasicBlock()) + or + // There is no dominating header, and `header` is non-local. + not isLocalHeaderDefinition(header) and + not exists(HTTP::HeaderDefinition dominatingHeader | + dominatingHeader.getBasicBlock().(ReachableBasicBlock).dominates(sender.getBasicBlock()) + ) + ) + } + + /** + * Holds if the HeaderDefinition `header` seems to be local. + * A HeaderDefinition is local if it dominates exactly one `ResponseSendArgument`. + * + * Recognizes variants of: + * ``` + * response.writeHead(500, ...); + * response.end('Some error'); + * return; + * ``` + */ + predicate isLocalHeaderDefinition(HTTP::HeaderDefinition header) { + exists(ReachableBasicBlock headerBlock | + headerBlock = header.getBasicBlock().(ReachableBasicBlock) + | + 1 = + strictcount(HTTP::ResponseSendArgument sender | + sender.getRouteHandler() = header.getRouteHandler() and + header.getBasicBlock().(ReachableBasicBlock).dominates(sender.getBasicBlock()) + ) and + // doesn't dominate something that looks like a callback. + not exists(Expr e | e instanceof Function | headerBlock.dominates(e.getBasicBlock())) ) } diff --git a/javascript/ql/src/semmle/javascript/security/performance/PolynomialReDoS.qll b/javascript/ql/src/semmle/javascript/security/performance/PolynomialReDoS.qll new file mode 100644 index 00000000000..68c85931aa3 --- /dev/null +++ b/javascript/ql/src/semmle/javascript/security/performance/PolynomialReDoS.qll @@ -0,0 +1,32 @@ +/** + * Provides a taint tracking configuration for reasoning about + * polynomial regular expression denial-of-service attacks. + * + * Note, for performance reasons: only import this file if + * `PolynomialReDoS::Configuration` is needed, otherwise + * `PolynomialReDoSCustomizations` should be imported instead. + */ + +import javascript + +module PolynomialReDoS { + import PolynomialReDoSCustomizations::PolynomialReDoS + + class Configuration extends TaintTracking::Configuration { + Configuration() { this = "PolynomialReDoS" } + + override predicate isSource(DataFlow::Node source) { source instanceof Source } + + override predicate isSink(DataFlow::Node sink) { sink instanceof Sink } + + override predicate isSanitizerGuard(TaintTracking::SanitizerGuardNode node) { + super.isSanitizerGuard(node) or + node instanceof LengthGuard + } + + override predicate isSanitizer(DataFlow::Node node) { + super.isSanitizer(node) or + node instanceof Sanitizer + } + } +} diff --git a/javascript/ql/src/semmle/javascript/security/performance/PolynomialReDoSCustomizations.qll b/javascript/ql/src/semmle/javascript/security/performance/PolynomialReDoSCustomizations.qll new file mode 100644 index 00000000000..5507f9e6981 --- /dev/null +++ b/javascript/ql/src/semmle/javascript/security/performance/PolynomialReDoSCustomizations.qll @@ -0,0 +1,102 @@ +/** + * Provides default sources, sinks and sanitizers for reasoning about + * polynomial regular expression denial-of-service attacks, as well + * as extension points for adding your own. + */ + +import javascript +import SuperlinearBackTracking + +module PolynomialReDoS { + /** + * A data flow source node for polynomial regular expression denial-of-service vulnerabilities. + */ + abstract class Source extends DataFlow::Node { } + + /** + * A data flow sink node for polynomial regular expression denial-of-service vulnerabilities. + */ + abstract class Sink extends DataFlow::Node { + abstract RegExpTerm getRegExp(); + } + + /** + * A sanitizer for polynomial regular expression denial-of-service vulnerabilities. + */ + abstract class Sanitizer extends DataFlow::Node { } + + /** + * A remote input to a server, seen as a source for polynomial + * regular expression denial-of-service vulnerabilities. + */ + class RequestInputAccessAsSource extends Source { + RequestInputAccessAsSource() { this instanceof HTTP::RequestInputAccess } + } + + /** + * A use of a superlinear backtracking term, seen as a sink for polynomial + * regular expression denial-of-service vulnerabilities. + */ + class PolynomialBackTrackingTermUse extends Sink { + PolynomialBackTrackingTerm term; + + PolynomialBackTrackingTermUse() { + exists(DataFlow::MethodCallNode mcn, DataFlow::Node regexp, string name | + term.getRootTerm() = RegExp::getRegExpFromNode(regexp) + | + this = mcn.getArgument(0) and + regexp = mcn.getReceiver() and + ( + name = "match" or + name = "split" or + name = "matchAll" or + name = "replace" or + name = "search" + ) + or + this = mcn.getReceiver() and + regexp = mcn.getArgument(0) and + (name = "test" or name = "exec") + ) + } + + override RegExpTerm getRegExp() { result = term } + } + + /** + * An operation that limits the length of a string, seen as a sanitizer. + */ + class StringLengthLimiter extends Sanitizer { + StringLengthLimiter() { + this.(StringReplaceCall).isGlobal() + or + exists(string name | name = "slice" or name = "substring" or name = "substr" | + this.(DataFlow::MethodCallNode).getMethodName() = name + ) + } + } + + /** + * An check on the length of a string, seen as a sanitizer guard. + */ + class LengthGuard extends TaintTracking::SanitizerGuardNode, DataFlow::ValueNode { + DataFlow::Node input; + boolean polarity; + + LengthGuard() { + exists(RelationalComparison cmp, DataFlow::PropRead length | + this.asExpr() = cmp and + length.accesses(input, "length") + | + length.flowsTo(cmp.getLesserOperand().flow()) and polarity = true + or + length.flowsTo(cmp.getGreaterOperand().flow()) and polarity = false + ) + } + + override predicate sanitizes(boolean outcome, Expr e) { + outcome = polarity and + e = input.asExpr() + } + } +} diff --git a/javascript/ql/src/semmle/javascript/security/performance/SuperlinearBackTracking.qll b/javascript/ql/src/semmle/javascript/security/performance/SuperlinearBackTracking.qll new file mode 100644 index 00000000000..55015ff17ef --- /dev/null +++ b/javascript/ql/src/semmle/javascript/security/performance/SuperlinearBackTracking.qll @@ -0,0 +1,143 @@ +/** + * Provides classes for working with regular expressions that can + * perform backtracking in superlinear time. + */ + +import javascript + +/** + * A regular expression term that permits unlimited repetitions. + */ +private class InfiniteRepetitionQuantifier extends RegExpQuantifier { + InfiniteRepetitionQuantifier() { + this instanceof RegExpPlus + or + this instanceof RegExpStar + or + this instanceof RegExpRange and not exists(this.(RegExpRange).getUpperBound()) + } +} + +/** + * Holds if `t` matches at least an epsilon symbol. + * + * That is, this term does not restrict the language of the enclosing regular expression. + * + * This is implemented as an under-approximation, and this predicate does not hold for sub-patterns in particular. + */ +private predicate matchesEpsilon(RegExpTerm t) { + t instanceof RegExpStar + or + t instanceof RegExpOpt + or + t.(RegExpRange).getLowerBound() = 0 + or + exists(RegExpTerm child | + child = t.getAChild() and + matchesEpsilon(child) + | + t instanceof RegExpAlt or + t instanceof RegExpGroup or + t instanceof RegExpPlus or + t instanceof RegExpRange + ) + or + matchesEpsilon(t.(RegExpBackRef).getGroup()) + or + forex(RegExpTerm child | child = t.(RegExpSequence).getAChild() | matchesEpsilon(child)) +} + +/** + * Gets a term that matches the symbol immediately before `t` is done matching. + * + * Examples: + * + * - For `d` in `abc?de` this gets `b`, `c`, `c?` (in addition to `d`). + * - For `(bc|de)` in `a(bc|de)f` this gets `c` and `e` (in addition to `bc|de` and `(bc|de)`). + */ +private RegExpTerm getAMatchPredecessor(RegExpTerm t) { + result = t + or + exists(RegExpTerm recurse | result = getAMatchPredecessor(recurse) | + // wrappers depend on their children + recurse = t.getAChild() and + ( + t instanceof RegExpAlt + or + t instanceof RegExpGroup + or + t instanceof RegExpQuantifier + ) + or + recurse = t.(RegExpSequence).getLastChild() + or + recurse = t.(RegExpBackRef).getGroup() + or + // recurse past epsilon terms + matchesEpsilon(t) and recurse = t.getPredecessor() + ) +} + +private RegExpCharacterClassEscape unwrapCharacterClassEscape(RegExpTerm t) { + t = result or + t.(RegExpCharacterClass).getAChild() = result +} + +pragma[inline] +private predicate compatibleConstants(RegExpTerm t1, RegExpTerm t2) { + exists(string s1, string s2 | + s1 = t1.getAMatchedString() and s2 = t2.getAMatchedString() + or + unwrapCharacterClassEscape(t1).getValue() = s1 and + unwrapCharacterClassEscape(t2).getValue() = s2 + | + s1 = s2 + ) +} + +/** + * Holds if `s1` and `s2` possibly have a non-empty intersection. + * + * This is a simple, and under-approximate, version of + * ReDoS::compatible/2, as this predicate only handles some character + * classes and constant values. + */ +pragma[inline] +private predicate compatible(RegExpTerm s1, RegExpTerm s2) { + not s1.(RegExpCharacterClass).isInverted() and + not s2.(RegExpCharacterClass).isInverted() and + compatibleConstants(s1, s2) +} + +/** + * A term that may cause a regular expression engine to perform a + * polynomial number of match attempts, relative to the input length. + */ +class PolynomialBackTrackingTerm extends InfiniteRepetitionQuantifier { + string reason; + + PolynomialBackTrackingTerm() { + // the regexp may fail to match ... + exists(RegExpTerm succ | this.getSuccessor+() = succ | not matchesEpsilon(succ)) and + ( + // ... and while failing, it will try to start matching at all positions of a long string + forall(RegExpTerm pred | pred = this.getPredecessor+() | matchesEpsilon(pred)) and + reason = "it can start matching anywhere" + or + exists(InfiniteRepetitionQuantifier pred | + pred = getAMatchPredecessor(this.getPredecessor()) and + compatible(pred.getAChild(), this.getAChild()) + | + reason = + "it can start matching anywhere after the start of the preceeding '" + pred.toString() + + "'" + ) + ) and + not this.getParent*() instanceof RegExpSubPattern // too many corner cases + } + + /** + * Gets the reason for the number of match attempts. + */ + string getReason() { result = reason } +} diff --git a/javascript/ql/test/experimental/README.md b/javascript/ql/test/experimental/README.md new file mode 100644 index 00000000000..2a9db304b9f --- /dev/null +++ b/javascript/ql/test/experimental/README.md @@ -0,0 +1 @@ +This directory contains tests for [experimental](../../../../docs/experimental.md) CodeQL queries and libraries. diff --git a/javascript/ql/test/library-tests/CallGraphs/AnnotatedTest/Test.ql b/javascript/ql/test/library-tests/CallGraphs/AnnotatedTest/Test.ql index dd41c794213..06b9671c2f5 100644 --- a/javascript/ql/test/library-tests/CallGraphs/AnnotatedTest/Test.ql +++ b/javascript/ql/test/library-tests/CallGraphs/AnnotatedTest/Test.ql @@ -34,9 +34,7 @@ class AnnotatedCall extends InvokeExpr { AnnotatedFunction getAnExpectedCallee() { result.getCalleeName() = getCallTargetName() } - int getBoundArgs() { - result = getAnnotation(this, "boundArgs").toInt() - } + int getBoundArgs() { result = getAnnotation(this, "boundArgs").toInt() } int getBoundArgsOrMinusOne() { result = getBoundArgs() diff --git a/javascript/ql/test/library-tests/Classes/ClassFlow.qll b/javascript/ql/test/library-tests/Classes/ClassFlow.qll index 6a6d44b667f..dec2475702c 100644 --- a/javascript/ql/test/library-tests/Classes/ClassFlow.qll +++ b/javascript/ql/test/library-tests/Classes/ClassFlow.qll @@ -14,4 +14,4 @@ class Configuration extends DataFlow::Configuration { query predicate dataflow(DataFlow::Node pred, DataFlow::Node succ) { any(Configuration c).hasFlow(pred, succ) -} \ No newline at end of file +} diff --git a/javascript/ql/test/library-tests/Classes/PrivateField.qll b/javascript/ql/test/library-tests/Classes/PrivateField.qll index a5b4a5ef04a..d4f5d3d4678 100644 --- a/javascript/ql/test/library-tests/Classes/PrivateField.qll +++ b/javascript/ql/test/library-tests/Classes/PrivateField.qll @@ -2,8 +2,5 @@ import javascript query string getAccessModifier(DataFlow::PropRef ref, Expr prop) { prop = ref.getPropertyNameExpr() and - if ref.isPrivateField() then - result = "Private" - else - result = "Public" -} \ No newline at end of file + if ref.isPrivateField() then result = "Private" else result = "Public" +} diff --git a/javascript/ql/test/library-tests/Classes/tests.ql b/javascript/ql/test/library-tests/Classes/tests.ql index 41fb13f3af0..5cb3c90db85 100644 --- a/javascript/ql/test/library-tests/Classes/tests.ql +++ b/javascript/ql/test/library-tests/Classes/tests.ql @@ -17,4 +17,4 @@ import ConstructorDefinitions import ClassNodeConstructor import ClassNodeInstanceMethod import PrivateField -import ClassFlow \ No newline at end of file +import ClassFlow diff --git a/javascript/ql/test/library-tests/CustomLoadStoreSteps/test.ql b/javascript/ql/test/library-tests/CustomLoadStoreSteps/test.ql index e4ffc89294d..0bde5a737a6 100644 --- a/javascript/ql/test/library-tests/CustomLoadStoreSteps/test.ql +++ b/javascript/ql/test/library-tests/CustomLoadStoreSteps/test.ql @@ -13,7 +13,7 @@ class Configuration extends TaintTracking::Configuration { // When the source code states that "foo" is being read, "bar" is additionally being read. override predicate isAdditionalLoadStep(DataFlow::Node pred, DataFlow::Node succ, string prop) { - exists(DataFlow::PropRead read | read = succ | + exists(DataFlow::PropRead read | read = succ | read.getBase() = pred and read.getPropertyName() = "foo" ) and diff --git a/javascript/ql/test/library-tests/DependencyModuleImports/Test.ql b/javascript/ql/test/library-tests/DependencyModuleImports/Test.ql index 9b8e63bf6d2..0552ff7b48a 100644 --- a/javascript/ql/test/library-tests/DependencyModuleImports/Test.ql +++ b/javascript/ql/test/library-tests/DependencyModuleImports/Test.ql @@ -32,9 +32,8 @@ class SampleVersionSink extends DataFlow::Node { functionName = "m1" and argNumber = 0 | - this = DataFlow::dependencyModuleImport(dep) - .getAMemberCall(functionName) - .getArgument(argNumber) and + this = + DataFlow::dependencyModuleImport(dep).getAMemberCall(functionName).getArgument(argNumber) and dep.info(dependencyName, vDep) and vDep.maybeBetween(vFrom, vTo) ) diff --git a/javascript/ql/test/library-tests/PartialInvokeNode/test.ql b/javascript/ql/test/library-tests/PartialInvokeNode/test.ql index a7e5a010a05..1c2093d0dad 100644 --- a/javascript/ql/test/library-tests/PartialInvokeNode/test.ql +++ b/javascript/ql/test/library-tests/PartialInvokeNode/test.ql @@ -1,21 +1,21 @@ import javascript -query -DataFlow::Node getBoundFunction(DataFlow::PartialInvokeNode invoke, DataFlow::Node callback, int boundArgs) { +query DataFlow::Node getBoundFunction( + DataFlow::PartialInvokeNode invoke, DataFlow::Node callback, int boundArgs +) { result = invoke.getBoundFunction(callback, boundArgs) } -query -predicate isPartialArgument(DataFlow::PartialInvokeNode invoke, DataFlow::Node callback, DataFlow::Node argument, int index) { +query predicate isPartialArgument( + DataFlow::PartialInvokeNode invoke, DataFlow::Node callback, DataFlow::Node argument, int index +) { invoke.isPartialArgument(callback, argument, index) } -query -DataFlow::Node getBoundReceiver(DataFlow::PartialInvokeNode invoke) { +query DataFlow::Node getBoundReceiver(DataFlow::PartialInvokeNode invoke) { result = invoke.getBoundReceiver() } -query -DataFlow::Node clickEvent() { +query DataFlow::Node clickEvent() { result = DataFlow::globalVarRef("addEventListener").getACall().getABoundCallbackParameter(1, 0) } diff --git a/javascript/ql/test/library-tests/Promises/ResolvedPromiseDefinition.qll b/javascript/ql/test/library-tests/Promises/ResolvedPromiseDefinition.qll index 5efd8896141..9a1debdc536 100644 --- a/javascript/ql/test/library-tests/Promises/ResolvedPromiseDefinition.qll +++ b/javascript/ql/test/library-tests/Promises/ResolvedPromiseDefinition.qll @@ -1,7 +1,5 @@ import javascript -query predicate test_ResolvedPromiseDefinition( - PromiseCreationCall resolved, DataFlow::Node res -) { +query predicate test_ResolvedPromiseDefinition(PromiseCreationCall resolved, DataFlow::Node res) { res = resolved.getValue() } diff --git a/javascript/ql/test/library-tests/Promises/flow.qll b/javascript/ql/test/library-tests/Promises/flow.qll index 395d5cb88a8..0b99ee9bcbd 100644 --- a/javascript/ql/test/library-tests/Promises/flow.qll +++ b/javascript/ql/test/library-tests/Promises/flow.qll @@ -31,4 +31,4 @@ query predicate flow(DataFlow::Node source, DataFlow::Node sink) { query predicate exclusiveTaintFlow(DataFlow::Node source, DataFlow::Node sink) { not any(Configuration c).hasFlow(source, sink) and any(TaintConfig c).hasFlow(source, sink) -} \ No newline at end of file +} diff --git a/javascript/ql/test/library-tests/Promises/tests.ql b/javascript/ql/test/library-tests/Promises/tests.ql index 2490f378970..bcb41981fe0 100644 --- a/javascript/ql/test/library-tests/Promises/tests.ql +++ b/javascript/ql/test/library-tests/Promises/tests.ql @@ -7,4 +7,4 @@ import PromiseDefinition_getAResolveHandler import PromiseDefinition_getRejectParameter import PromiseDefinition_getResolveParameter import PromiseDefinition_getACatchHandler -import flow \ No newline at end of file +import flow diff --git a/javascript/ql/test/library-tests/RangeAnalysis/DeadBranch.ql b/javascript/ql/test/library-tests/RangeAnalysis/DeadBranch.ql index 8e6a798d737..d1554c34933 100644 --- a/javascript/ql/test/library-tests/RangeAnalysis/DeadBranch.ql +++ b/javascript/ql/test/library-tests/RangeAnalysis/DeadBranch.ql @@ -22,8 +22,8 @@ class AssertionComment extends LineComment { isOK = true and exists(ConditionGuardNode guard | guard = getAGuardNode() | RangeAnalysis::isContradictoryGuardNode(guard) and - result = "Error: analysis claims " + getTestExpr() + " is always " + - guard.getOutcome().booleanNot() + result = + "Error: analysis claims " + getTestExpr() + " is always " + guard.getOutcome().booleanNot() ) or isOK = false and diff --git a/javascript/ql/test/library-tests/RegExp/EscapeInString/EscapeInString.ql b/javascript/ql/test/library-tests/RegExp/EscapeInString/EscapeInString.ql index f824fe01920..22fcecfe3c6 100644 --- a/javascript/ql/test/library-tests/RegExp/EscapeInString/EscapeInString.ql +++ b/javascript/ql/test/library-tests/RegExp/EscapeInString/EscapeInString.ql @@ -1,6 +1,7 @@ import javascript from StringLiteral literal, RegExpDot dot, int pos -where dot.getParent*() = literal - and pos = dot.getLocation().getStartColumn() - literal.getLocation().getStartColumn() +where + dot.getParent*() = literal and + pos = dot.getLocation().getStartColumn() - literal.getLocation().getStartColumn() select dot, literal.getRawValue().charAt(pos) diff --git a/javascript/ql/test/library-tests/RegExp/MissingUnicodeFlag/MissingUnicodeFlag.ql b/javascript/ql/test/library-tests/RegExp/MissingUnicodeFlag/MissingUnicodeFlag.ql index 80c214b41d6..e8d47d789f3 100644 --- a/javascript/ql/test/library-tests/RegExp/MissingUnicodeFlag/MissingUnicodeFlag.ql +++ b/javascript/ql/test/library-tests/RegExp/MissingUnicodeFlag/MissingUnicodeFlag.ql @@ -1,7 +1,8 @@ import javascript from RegExpLiteral literal, RegExpConstant wideConstant -where wideConstant.getLiteral() = literal and +where + wideConstant.getLiteral() = literal and not literal.getFlags().matches("%u%") and wideConstant.getValue().length() > 1 and ( 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 index c356564dc7f..c38742da802 100644 --- a/javascript/ql/test/library-tests/RegExp/predecessors_and_successors/getPredecessor.ql +++ b/javascript/ql/test/library-tests/RegExp/predecessors_and_successors/getPredecessor.ql @@ -2,4 +2,4 @@ import javascript from RegExpTerm pred, RegExpTerm succ where pred = succ.getPredecessor() -select pred, succ \ No newline at end of file +select pred, succ 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 index 49916f8c426..486a874c975 100644 --- a/javascript/ql/test/library-tests/RegExp/predecessors_and_successors/getSuccessor.ql +++ b/javascript/ql/test/library-tests/RegExp/predecessors_and_successors/getSuccessor.ql @@ -2,4 +2,4 @@ import javascript from RegExpTerm pred, RegExpTerm succ where succ = pred.getSuccessor() -select pred, succ \ No newline at end of file +select pred, succ diff --git a/javascript/ql/test/library-tests/SensitiveActions/tst.js b/javascript/ql/test/library-tests/SensitiveActions/tst.js index b867627e8ee..c4a627b742c 100644 --- a/javascript/ql/test/library-tests/SensitiveActions/tst.js +++ b/javascript/ql/test/library-tests/SensitiveActions/tst.js @@ -22,3 +22,7 @@ secret; require("process").exit(); global.process.exit(); + +get("https://example.com/news?password=true") +get("https://username:password@example.com") +execute("SELECT * FROM users WHERE password=?") diff --git a/javascript/ql/test/library-tests/TaintTracking/BasicTaintTracking.expected b/javascript/ql/test/library-tests/TaintTracking/BasicTaintTracking.expected index 53dbe8d1b53..5616da680b9 100644 --- a/javascript/ql/test/library-tests/TaintTracking/BasicTaintTracking.expected +++ b/javascript/ql/test/library-tests/TaintTracking/BasicTaintTracking.expected @@ -67,6 +67,12 @@ typeInferenceMismatch | exceptions.js:158:13:158:20 | source() | exceptions.js:161:10:161:10 | e | | importedReactComponent.jsx:4:40:4:47 | source() | exportedReactComponent.jsx:2:10:2:19 | props.text | | indexOf.js:4:11:4:18 | source() | indexOf.js:9:10:9:10 | x | +| nested-props.js:4:13:4:20 | source() | nested-props.js:5:10:5:14 | obj.x | +| nested-props.js:9:18:9:25 | source() | nested-props.js:10:10:10:16 | obj.x.y | +| nested-props.js:35:13:35:20 | source() | nested-props.js:36:10:36:20 | doLoad(obj) | +| nested-props.js:43:13:43:20 | source() | nested-props.js:44:10:44:18 | id(obj).x | +| nested-props.js:67:31:67:38 | source() | nested-props.js:68:10:68:10 | x | +| nested-props.js:77:36:77:43 | source() | nested-props.js:78:10:78:10 | x | | partialCalls.js:4:17:4:24 | source() | partialCalls.js:17:14:17:14 | x | | partialCalls.js:4:17:4:24 | source() | partialCalls.js:20:14:20:14 | y | | partialCalls.js:4:17:4:24 | source() | partialCalls.js:30:14:30:20 | x.value | diff --git a/javascript/ql/test/library-tests/TaintTracking/DataFlowTracking.expected b/javascript/ql/test/library-tests/TaintTracking/DataFlowTracking.expected index 127d58056ae..bcd301b5d53 100644 --- a/javascript/ql/test/library-tests/TaintTracking/DataFlowTracking.expected +++ b/javascript/ql/test/library-tests/TaintTracking/DataFlowTracking.expected @@ -42,6 +42,11 @@ | exceptions.js:158:13:158:20 | source() | exceptions.js:161:10:161:10 | e | | indexOf.js:4:11:4:18 | source() | indexOf.js:9:10:9:10 | x | | indexOf.js:4:11:4:18 | source() | indexOf.js:13:10:13:10 | x | +| nested-props.js:4:13:4:20 | source() | nested-props.js:5:10:5:14 | obj.x | +| nested-props.js:9:18:9:25 | source() | nested-props.js:10:10:10:16 | obj.x.y | +| nested-props.js:35:13:35:20 | source() | nested-props.js:36:10:36:20 | doLoad(obj) | +| nested-props.js:43:13:43:20 | source() | nested-props.js:44:10:44:18 | id(obj).x | +| nested-props.js:67:31:67:38 | source() | nested-props.js:68:10:68:10 | x | | partialCalls.js:4:17:4:24 | source() | partialCalls.js:17:14:17:14 | x | | partialCalls.js:4:17:4:24 | source() | partialCalls.js:20:14:20:14 | y | | partialCalls.js:4:17:4:24 | source() | partialCalls.js:30:14:30:20 | x.value | diff --git a/javascript/ql/test/library-tests/TaintTracking/nested-props.js b/javascript/ql/test/library-tests/TaintTracking/nested-props.js new file mode 100644 index 00000000000..a5ea3cc248b --- /dev/null +++ b/javascript/ql/test/library-tests/TaintTracking/nested-props.js @@ -0,0 +1,81 @@ +import * as dummy from 'dummy'; + +function storeLoadFlow(obj) { + obj.x = source(); + sink(obj.x); // NOT OK +} + +function storeStoreLoadLoadFlow(obj) { + obj.x = { y: source() }; + sink(obj.x.y); // NOT OK +} + +function loadStoreFlow(obj) { + obj.x.y = source(); + sink(obj.x.y); // NOT OK - but not found +} + +function loadLoadStoreFlow(obj) { + obj.x.y.z = source(); + sink(obj.x.y.z); // NOT OK - but not found +} + +function doStore(obj, x) { + obj.x = x; +} +function callStoreBackcallFlow(obj) { + doStore(obj, source()); + sink(obj.x); // NOT OK - but not found +} + +function doLoad(obj) { + return obj.x +} +function storeCallLoadReturnFlow(obj) { + obj.x = source(); + sink(doLoad(obj)); // NOT OK +} + +function id(obj) { + return obj +} +function storeCallReturnLoadFlow(obj) { + obj.x = source(); + sink(id(obj).x); // NOT OK +} + +function doLoadStore(obj, val) { + obj.x.y = val; +} +function callStoreBackloadBackcallLoadLoad(obj) { + doLoadStore(obj, source()); + sink(obj.x.y); // NOT OK - but not found +} + +function doLoadLoad(obj) { + return obj.x.y +} +function storeBackloadCallLoadLoadReturn(obj) { + obj.x.y = source(); + sink(doLoadStore(obj)); // NOT OK - but not found +} + +function doStoreReturn(val) { + return { x: val } +} +function callStoreReturnLoad() { + let { x } = doStoreReturn(source()); + sink(x); // NOT OK + + doStoreReturn(null); // ensure multiple call sites exist +} + +function doTaintStoreReturn(val) { + return { x: val + "!!" } +} +function callTaintStoreReturnLoadFlow() { + let { x } = doTaintStoreReturn(source()); + sink(x); // NOT OK + + doTaintStoreReturn(null); // ensure multiple call sites exist +} diff --git a/javascript/ql/test/library-tests/TorrentLibraries/ParsedTorrent.ql b/javascript/ql/test/library-tests/TorrentLibraries/ParsedTorrent.ql index 1ad59af1d8b..eb50d8d4414 100644 --- a/javascript/ql/test/library-tests/TorrentLibraries/ParsedTorrent.ql +++ b/javascript/ql/test/library-tests/TorrentLibraries/ParsedTorrent.ql @@ -1,3 +1,3 @@ import javascript -select any(ParseTorrent::ParsedTorrent t) \ No newline at end of file +select any(ParseTorrent::ParsedTorrent t) diff --git a/javascript/ql/test/library-tests/TorrentLibraries/UserControlledTorrentInfo.ql b/javascript/ql/test/library-tests/TorrentLibraries/UserControlledTorrentInfo.ql index 85d08e75896..c18323953bf 100644 --- a/javascript/ql/test/library-tests/TorrentLibraries/UserControlledTorrentInfo.ql +++ b/javascript/ql/test/library-tests/TorrentLibraries/UserControlledTorrentInfo.ql @@ -1,3 +1,3 @@ import javascript -select any(ParseTorrent::UserControlledTorrentInfo i) \ No newline at end of file +select any(ParseTorrent::UserControlledTorrentInfo i) diff --git a/javascript/ql/test/library-tests/TorrentLibraries/parsedTorrentRef.ql b/javascript/ql/test/library-tests/TorrentLibraries/parsedTorrentRef.ql index e72bde720f3..69701796383 100644 --- a/javascript/ql/test/library-tests/TorrentLibraries/parsedTorrentRef.ql +++ b/javascript/ql/test/library-tests/TorrentLibraries/parsedTorrentRef.ql @@ -1,3 +1,3 @@ import javascript -select ParseTorrent::parsedTorrentRef() \ No newline at end of file +select ParseTorrent::parsedTorrentRef() diff --git a/javascript/ql/test/library-tests/TypeScript/CallSignatureTypes/test.ql b/javascript/ql/test/library-tests/TypeScript/CallSignatureTypes/test.ql index 57105b7de6b..03cc288f054 100644 --- a/javascript/ql/test/library-tests/TypeScript/CallSignatureTypes/test.ql +++ b/javascript/ql/test/library-tests/TypeScript/CallSignatureTypes/test.ql @@ -13,7 +13,9 @@ query predicate test_ExprSignature(Expr expr, string type) { type = getASignatureOrElseType(expr.getType()) } -query predicate test_TypeReferenceSig(TypeReference type, SignatureKind kind, int n, CallSignatureType sig) { +query predicate test_TypeReferenceSig( + TypeReference type, SignatureKind kind, int n, CallSignatureType sig +) { sig = type.getSignature(kind, n) } @@ -21,9 +23,7 @@ query predicate test_FunctionCallSig(Function f, CallSignatureType sig) { sig = f.getCallSignature() } -query Type test_getRestParameterType(CallSignatureType sig) { - result = sig.getRestParameterType() -} +query Type test_getRestParameterType(CallSignatureType sig) { result = sig.getRestParameterType() } query Type test_getRestParameterArray(CallSignatureType sig) { result = sig.getRestParameterArrayType() diff --git a/javascript/ql/test/library-tests/TypeScript/CallSignatures/test.ql b/javascript/ql/test/library-tests/TypeScript/CallSignatures/test.ql index f3d79f91feb..e1254629fe1 100644 --- a/javascript/ql/test/library-tests/TypeScript/CallSignatures/test.ql +++ b/javascript/ql/test/library-tests/TypeScript/CallSignatures/test.ql @@ -1,12 +1,16 @@ import javascript -query predicate test_CallSignature(CallSignature call, string declType, ASTNode body, string abstractness) { +query predicate test_CallSignature( + CallSignature call, string declType, ASTNode body, string abstractness +) { (if call.isAbstract() then abstractness = "abstract" else abstractness = "not abstract") and declType = call.getDeclaringType().describe() and body = call.getBody() } -query predicate test_IndexSignature(IndexSignature sig, string declType, ASTNode body, string abstractness) { +query predicate test_IndexSignature( + IndexSignature sig, string declType, ASTNode body, string abstractness +) { (if sig.isAbstract() then abstractness = "abstract" else abstractness = "not abstract") and declType = sig.getDeclaringType().describe() and body = sig.getBody() @@ -21,12 +25,16 @@ query predicate test_MethodOverload(MethodDeclaration method, int index, boolean if method.isOverloaded() then overloaded = true else overloaded = false } -query predicate test_FunctionCallSigOverload(FunctionCallSignature sig, int index, boolean overloaded) { +query predicate test_FunctionCallSigOverload( + FunctionCallSignature sig, int index, boolean overloaded +) { index = sig.getOverloadIndex() and if sig.isOverloaded() then overloaded = true else overloaded = false } -query predicate test_ConstructorCallSigOverload(ConstructorCallSignature sig, int index, boolean overloaded) { +query predicate test_ConstructorCallSigOverload( + ConstructorCallSignature sig, int index, boolean overloaded +) { index = sig.getOverloadIndex() and if sig.isOverloaded() then overloaded = true else overloaded = false -} \ No newline at end of file +} diff --git a/javascript/ql/test/library-tests/TypeScript/ImportMeta/test.ql b/javascript/ql/test/library-tests/TypeScript/ImportMeta/test.ql index 311aa4b4050..3753c8bab98 100644 --- a/javascript/ql/test/library-tests/TypeScript/ImportMeta/test.ql +++ b/javascript/ql/test/library-tests/TypeScript/ImportMeta/test.ql @@ -2,4 +2,4 @@ import javascript from Expr prop where prop instanceof ImportMetaExpr -select prop \ No newline at end of file +select prop diff --git a/javascript/ql/test/library-tests/TypeScript/Namespaces/CheckBindings.ql b/javascript/ql/test/library-tests/TypeScript/Namespaces/CheckBindings.ql index a3c29fc4023..1e313974e97 100644 --- a/javascript/ql/test/library-tests/TypeScript/Namespaces/CheckBindings.ql +++ b/javascript/ql/test/library-tests/TypeScript/Namespaces/CheckBindings.ql @@ -20,8 +20,8 @@ string getNamespaceName(NamespaceDeclaration decl) { result = decl.getStmt(0).(ExprStmt).getExpr().getStringValue() or not decl.getStmt(0).(ExprStmt).getExpr() instanceof ConstantString and - result = "Namespace " + decl.getId() + " on line " + - decl.getFirstToken().getLocation().getStartLine() + result = + "Namespace " + decl.getId() + " on line " + decl.getFirstToken().getLocation().getStartLine() } from ResolveCall resolve diff --git a/javascript/ql/test/library-tests/TypeScript/PathMapping/Imports.ql b/javascript/ql/test/library-tests/TypeScript/PathMapping/Imports.ql index bd272eee884..a271f821ac4 100644 --- a/javascript/ql/test/library-tests/TypeScript/PathMapping/Imports.ql +++ b/javascript/ql/test/library-tests/TypeScript/PathMapping/Imports.ql @@ -1,8 +1,6 @@ import javascript -query predicate symbols(ASTNode astNode, CanonicalName symbol) { - ast_node_symbol(astNode, symbol) -} +query predicate symbols(ASTNode astNode, CanonicalName symbol) { ast_node_symbol(astNode, symbol) } from Import imprt select imprt, imprt.getImportedModule() diff --git a/javascript/ql/test/library-tests/TypeScript/PrivateFields/test.ql b/javascript/ql/test/library-tests/TypeScript/PrivateFields/test.ql index 27147fcb461..bf1f61039f5 100644 --- a/javascript/ql/test/library-tests/TypeScript/PrivateFields/test.ql +++ b/javascript/ql/test/library-tests/TypeScript/PrivateFields/test.ql @@ -1,9 +1,5 @@ import javascript -query PropAccess propAccess(string name) { - result.getPropertyName() = name -} +query PropAccess propAccess(string name) { result.getPropertyName() = name } -query FieldDeclaration fieldDecl(string name) { - result.getName() = name -} +query FieldDeclaration fieldDecl(string name) { result.getName() = name } diff --git a/javascript/ql/test/library-tests/TypeScript/RegressionTests/ImportSelf/test.ql b/javascript/ql/test/library-tests/TypeScript/RegressionTests/ImportSelf/test.ql index 267a0e32c7a..d194e75f71a 100644 --- a/javascript/ql/test/library-tests/TypeScript/RegressionTests/ImportSelf/test.ql +++ b/javascript/ql/test/library-tests/TypeScript/RegressionTests/ImportSelf/test.ql @@ -1,5 +1,5 @@ import javascript // We're mainly testing extraction succeeds, so just test that some types are extracted. - -from Expr e select e, e.getType() +from Expr e +select e, e.getType() diff --git a/javascript/ql/test/library-tests/TypeScript/RegressionTests/TraceResolution/test.ql b/javascript/ql/test/library-tests/TypeScript/RegressionTests/TraceResolution/test.ql index 9b7f3a42e02..574b7c54d4e 100644 --- a/javascript/ql/test/library-tests/TypeScript/RegressionTests/TraceResolution/test.ql +++ b/javascript/ql/test/library-tests/TypeScript/RegressionTests/TraceResolution/test.ql @@ -1,3 +1,4 @@ import javascript -from Expr e select e, e.getType() +from Expr e +select e, e.getType() diff --git a/javascript/ql/test/library-tests/TypeScript/Tokens/PrettyPrintTokens.ql b/javascript/ql/test/library-tests/TypeScript/Tokens/PrettyPrintTokens.ql index 1f5491fc28c..c040343b2ad 100644 --- a/javascript/ql/test/library-tests/TypeScript/Tokens/PrettyPrintTokens.ql +++ b/javascript/ql/test/library-tests/TypeScript/Tokens/PrettyPrintTokens.ql @@ -7,7 +7,8 @@ Token getATokenAtLine(File file, int line) { bindingset[line] string getTokenStringAtLine(File file, int line) { - result = concat(Token tok | + result = + concat(Token tok | tok = getATokenAtLine(file, line) | tok.toString().replaceAll(" ", "~") + " " order by tok.getLocation().getStartColumn() diff --git a/javascript/ql/test/library-tests/TypeScript/TypeAliases/TypeAliases.ql b/javascript/ql/test/library-tests/TypeScript/TypeAliases/TypeAliases.ql index 60b404be4b3..e8133898ff2 100644 --- a/javascript/ql/test/library-tests/TypeScript/TypeAliases/TypeAliases.ql +++ b/javascript/ql/test/library-tests/TypeScript/TypeAliases/TypeAliases.ql @@ -3,18 +3,10 @@ import javascript from TypeAliasDeclaration decl select decl, decl.getIdentifier(), decl.getNumTypeParameter(), decl.getDefinition() -query Type rightHandSide(TypeAliasDeclaration decl) { - result = decl.getDefinition().getType() -} +query Type rightHandSide(TypeAliasDeclaration decl) { result = decl.getDefinition().getType() } -query Type getAliasedType(TypeAliasReference ref) { - result = ref.getAliasedType() -} +query Type getAliasedType(TypeAliasReference ref) { result = ref.getAliasedType() } -query Type getTypeArgument(TypeAliasReference ref, int n) { - result = ref.getTypeArgument(n) -} +query Type getTypeArgument(TypeAliasReference ref, int n) { result = ref.getTypeArgument(n) } -query Type unfold(TypeAliasReference t) { - result = t.unfold() -} +query Type unfold(TypeAliasReference t) { result = t.unfold() } diff --git a/javascript/ql/test/library-tests/TypeScript/TypeAnnotations/PredicateTypeExpr.qll b/javascript/ql/test/library-tests/TypeScript/TypeAnnotations/PredicateTypeExpr.qll index c38bf54ad1f..a3a11cb89b6 100644 --- a/javascript/ql/test/library-tests/TypeScript/TypeAnnotations/PredicateTypeExpr.qll +++ b/javascript/ql/test/library-tests/TypeScript/TypeAnnotations/PredicateTypeExpr.qll @@ -8,6 +8,4 @@ query predicate test_PredicateTypeExpr(PredicateTypeExpr type, VarTypeAccess res res0 = type.getParameterName() } -query predicate test_hasAssertsKeyword(PredicateTypeExpr type) { - type.hasAssertsKeyword() -} +query predicate test_hasAssertsKeyword(PredicateTypeExpr type) { type.hasAssertsKeyword() } diff --git a/javascript/ql/test/library-tests/TypeScript/TypeOnlyImportExport/test.ql b/javascript/ql/test/library-tests/TypeScript/TypeOnlyImportExport/test.ql index 17939b3b380..df364791e6c 100644 --- a/javascript/ql/test/library-tests/TypeScript/TypeOnlyImportExport/test.ql +++ b/javascript/ql/test/library-tests/TypeScript/TypeOnlyImportExport/test.ql @@ -1,22 +1,14 @@ import javascript -query VarRef getAVarReference(Variable v) { - result = v.getAReference() -} +query VarRef getAVarReference(Variable v) { result = v.getAReference() } -query VarRef getAnExportAccess(LocalTypeName t) { - result = t.getAnExportAccess() -} +query VarRef getAnExportAccess(LocalTypeName t) { result = t.getAnExportAccess() } -query TypeDecl getATypeDecl(LocalTypeName t) { - result = t.getADeclaration() -} +query TypeDecl getATypeDecl(LocalTypeName t) { result = t.getADeclaration() } -query Function calls(DataFlow::InvokeNode invoke) { - result = invoke.getACallee() -} +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() + exprt.exportsAs(v, name) and + kind = v.getDeclarationSpace() } diff --git a/javascript/ql/test/library-tests/frameworks/ClientRequests/ClientRequests.expected b/javascript/ql/test/library-tests/frameworks/ClientRequests/ClientRequests.expected index f3017c72a77..2548ec3dd34 100644 --- a/javascript/ql/test/library-tests/frameworks/ClientRequests/ClientRequests.expected +++ b/javascript/ql/test/library-tests/frameworks/ClientRequests/ClientRequests.expected @@ -52,6 +52,9 @@ test_ClientRequest | tst.js:147:5:147:19 | got.stream(url) | | tst.js:151:5:151:23 | superagent.get(url) | | tst.js:160:5:160:17 | xhr.send(url) | +| tst.js:171:2:171:10 | base(url) | +| tst.js:172:2:172:14 | variant1(url) | +| tst.js:173:2:173:14 | variant2(url) | test_getADataNode | tst.js:53:5:53:23 | axios({data: data}) | tst.js:53:18:53:21 | data | | tst.js:57:5:57:39 | axios.p ... data2}) | tst.js:57:19:57:23 | data1 | @@ -144,6 +147,9 @@ test_getUrl | tst.js:147:5:147:19 | got.stream(url) | tst.js:147:16:147:18 | url | | tst.js:151:5:151:23 | superagent.get(url) | tst.js:151:20:151:22 | url | | tst.js:160:5:160:17 | xhr.send(url) | tst.js:160:14:160:16 | url | +| tst.js:171:2:171:10 | base(url) | tst.js:171:7:171:9 | url | +| tst.js:172:2:172:14 | variant1(url) | tst.js:172:11:172:13 | url | +| tst.js:173:2:173:14 | variant2(url) | tst.js:173:11:173:13 | url | test_getAResponseDataNode | tst.js:19:5:19:23 | requestPromise(url) | tst.js:19:5:19:23 | requestPromise(url) | text | true | | tst.js:21:5:21:23 | superagent.get(url) | tst.js:21:5:21:23 | superagent.get(url) | stream | true | diff --git a/javascript/ql/test/library-tests/frameworks/ClientRequests/tst.js b/javascript/ql/test/library-tests/frameworks/ClientRequests/tst.js index 8bd15b3a9eb..70244682892 100644 --- a/javascript/ql/test/library-tests/frameworks/ClientRequests/tst.js +++ b/javascript/ql/test/library-tests/frameworks/ClientRequests/tst.js @@ -163,3 +163,12 @@ import {ClientRequest, net} from 'electron'; xhr.getResponseHeaders(); }); }) + +(function() { + let base = request; + let variant1 = base.defaults({}); + let variant2 = variant1.defaults({}); + base(url); + variant1(url); + variant2(url); +}); diff --git a/javascript/ql/test/library-tests/frameworks/EventEmitter/test.expected b/javascript/ql/test/library-tests/frameworks/EventEmitter/test.expected index c8526d5eecf..34a042bf626 100644 --- a/javascript/ql/test/library-tests/frameworks/EventEmitter/test.expected +++ b/javascript/ql/test/library-tests/frameworks/EventEmitter/test.expected @@ -12,3 +12,5 @@ | tst.js:28:17:28:22 | "blab" | tst.js:25:16:25:20 | event | | tst.js:34:18:34:22 | "BOH" | tst.js:33:17:33:17 | x | | tst.js:40:20:40:27 | "yabity" | tst.js:39:19:39:19 | x | +| tst.js:46:28:46:38 | 'FirstData' | tst.js:43:45:43:49 | first | +| tst.js:47:29:47:40 | 'SecondData' | tst.js:44:37:44:42 | second | diff --git a/javascript/ql/test/library-tests/frameworks/EventEmitter/test.ql b/javascript/ql/test/library-tests/frameworks/EventEmitter/test.ql index 846536672b1..e8de4ff4c53 100644 --- a/javascript/ql/test/library-tests/frameworks/EventEmitter/test.ql +++ b/javascript/ql/test/library-tests/frameworks/EventEmitter/test.ql @@ -1,8 +1,5 @@ import javascript query predicate taintSteps(DataFlow::Node pred, DataFlow::Node succ) { - exists(DataFlow::AdditionalFlowStep step | - step.step(pred, succ) - ) + exists(DataFlow::AdditionalFlowStep step | step.step(pred, succ)) } - diff --git a/javascript/ql/test/library-tests/frameworks/EventEmitter/tst.js b/javascript/ql/test/library-tests/frameworks/EventEmitter/tst.js index d45069b2c8e..229e75b5b57 100644 --- a/javascript/ql/test/library-tests/frameworks/EventEmitter/tst.js +++ b/javascript/ql/test/library-tests/frameworks/EventEmitter/tst.js @@ -37,4 +37,11 @@ class ExtendsMyCustomEmitter extends MyEventEmitter{} var em5 = new ExtendsMyCustomEmitter(); em5.on("yibity", (x) => {}); -em5.emit("yibity", "yabity"); \ No newline at end of file +em5.emit("yibity", "yabity"); + +var process = require('process'); +process.addListener('FirstEvent', function (first) {}); +process.on('SecondEvent', function (second) {}); + +process.emit('FirstEvent', 'FirstData'); +process.emit('SecondEvent', 'SecondData'); diff --git a/javascript/ql/test/library-tests/frameworks/LazyCache/LazyCache.ql b/javascript/ql/test/library-tests/frameworks/LazyCache/LazyCache.ql index bca53d0f163..95c42bf99eb 100644 --- a/javascript/ql/test/library-tests/frameworks/LazyCache/LazyCache.ql +++ b/javascript/ql/test/library-tests/frameworks/LazyCache/LazyCache.ql @@ -1,5 +1,3 @@ import javascript -query DataFlow::Node moduleImport(string name) { - result = DataFlow::moduleImport(name) -} +query DataFlow::Node moduleImport(string name) { result = DataFlow::moduleImport(name) } diff --git a/javascript/ql/test/library-tests/frameworks/SocketIO/ClientSendNode_getAck.qll b/javascript/ql/test/library-tests/frameworks/SocketIO/ClientSendNode_getAck.qll index 5c2d8d9c22f..46b2ee0436d 100644 --- a/javascript/ql/test/library-tests/frameworks/SocketIO/ClientSendNode_getAck.qll +++ b/javascript/ql/test/library-tests/frameworks/SocketIO/ClientSendNode_getAck.qll @@ -1,5 +1,7 @@ import javascript -query predicate test_ClientSendNode_getAck(SocketIOClient::SendNode sn, SocketIOClient::SendCallback res) { +query predicate test_ClientSendNode_getAck( + SocketIOClient::SendNode sn, SocketIOClient::SendCallback res +) { res.getSendNode() = sn } diff --git a/javascript/ql/test/library-tests/frameworks/WebSocket/test.ql b/javascript/ql/test/library-tests/frameworks/WebSocket/test.ql index 5cb76ca7ec2..e7b9d451a55 100644 --- a/javascript/ql/test/library-tests/frameworks/WebSocket/test.ql +++ b/javascript/ql/test/library-tests/frameworks/WebSocket/test.ql @@ -14,4 +14,4 @@ query ServerWebSocket::ReceiveNode serverReceive() { any() } query predicate taintStep(DataFlow::Node pred, DataFlow::Node succ) { any(DataFlow::AdditionalFlowStep s).step(pred, succ) -} \ No newline at end of file +} diff --git a/javascript/ql/test/library-tests/frameworks/typeahead/test.ql b/javascript/ql/test/library-tests/frameworks/typeahead/test.ql index 4b444d66bd0..29b76a1851e 100644 --- a/javascript/ql/test/library-tests/frameworks/typeahead/test.ql +++ b/javascript/ql/test/library-tests/frameworks/typeahead/test.ql @@ -1,13 +1,9 @@ import javascript -query DataFlow::Node url() { - result = any(ClientRequest r).getUrl() -} +query DataFlow::Node url() { result = any(ClientRequest r).getUrl() } query DataFlow::Node response(string responseType, boolean promise) { - result = any(ClientRequest r).getAResponseDataNode(responseType, promise) + result = any(ClientRequest r).getAResponseDataNode(responseType, promise) } -query DataFlow::Node suggestionFunction() { - result = any(Typeahead::TypeaheadSuggestionFunction t) -} \ No newline at end of file +query DataFlow::Node suggestionFunction() { result = any(Typeahead::TypeaheadSuggestionFunction t) } diff --git a/javascript/ql/test/query-tests/Declarations/UnreachableOverloads/UnreachableMethodOverloads.expected b/javascript/ql/test/query-tests/Declarations/UnreachableOverloads/UnreachableMethodOverloads.expected index a8104985696..44bd568e767 100644 --- a/javascript/ql/test/query-tests/Declarations/UnreachableOverloads/UnreachableMethodOverloads.expected +++ b/javascript/ql/test/query-tests/Declarations/UnreachableOverloads/UnreachableMethodOverloads.expected @@ -1,4 +1,5 @@ | tst.ts:3:3:3:30 | method( ... number; | This overload of method() is unreachable, the $@ overload will always be selected. | tst.ts:2:3:2:30 | method( ... string; | previous | | tst.ts:6:3:6:17 | types1(): any[] | This overload of types1() is unreachable, the $@ overload will always be selected. | tst.ts:5:3:5:18 | types1(): T[] | previous | | tst.ts:15:3:15:74 | on(even ... nction; | This overload of on() is unreachable, the $@ overload will always be selected. | tst.ts:14:3:14:74 | on(even ... nction; | previous | -| tst.ts:21:3:21:30 | method( ... number; | This overload of method() is unreachable, the $@ overload will always be selected. | tst.ts:20:3:20:30 | method( ... string; | previous | +| tst.ts:21:3:21:28 | bar(thi ... number; | This overload of bar() is unreachable, the $@ overload will always be selected. | tst.ts:20:3:20:28 | bar(thi ... string; | previous | +| tst.ts:27:3:27:30 | method( ... number; | This overload of method() is unreachable, the $@ overload will always be selected. | tst.ts:26:3:26:30 | method( ... string; | previous | diff --git a/javascript/ql/test/query-tests/Declarations/UnreachableOverloads/tst.ts b/javascript/ql/test/query-tests/Declarations/UnreachableOverloads/tst.ts index 21abdf16966..9455c9a161b 100644 --- a/javascript/ql/test/query-tests/Declarations/UnreachableOverloads/tst.ts +++ b/javascript/ql/test/query-tests/Declarations/UnreachableOverloads/tst.ts @@ -14,6 +14,12 @@ declare class Foobar { on(event: string, fn?: (event?: any, ...args: any[]) => void): Function; on(event: string, fn?: (event?: any, ...args: any[]) => void): Function; // NOT OK. + foo(this: string): string; + foo(this: number): number; // OK + + bar(this: number): string; + bar(this: number): number; // NOT OK + } declare class Base { diff --git a/javascript/ql/test/query-tests/Performance/ReDoS/PolynomialBackTracking.expected b/javascript/ql/test/query-tests/Performance/ReDoS/PolynomialBackTracking.expected new file mode 100644 index 00000000000..98f5cb00ed4 --- /dev/null +++ b/javascript/ql/test/query-tests/Performance/ReDoS/PolynomialBackTracking.expected @@ -0,0 +1,139 @@ +| polynomial-redos.js:7:24:7:26 | \\s+ | it can start matching anywhere | +| polynomial-redos.js:8:17:8:18 | * | it can start matching anywhere | +| polynomial-redos.js:9:19:9:21 | \\s* | it can start matching anywhere | +| polynomial-redos.js:11:19:11:20 | .* | it can start matching anywhere | +| polynomial-redos.js:12:19:12:20 | .* | it can start matching anywhere | +| polynomial-redos.js:15:28:15:35 | [\\s\\S]*? | it can start matching anywhere after the start of the preceeding '\\s*' | +| polynomial-redos.js:18:17:18:22 | [0-9]* | it can start matching anywhere | +| polynomial-redos.js:18:83:18:100 | [\\u0600-\\u06FF\\/]+ | it can start matching anywhere | +| polynomial-redos.js:19:17:19:22 | [0-9]* | it can start matching anywhere | +| polynomial-redos.js:20:56:20:58 | \\d+ | it can start matching anywhere after the start of the preceeding '\\d*' | +| polynomial-redos.js:20:56:20:58 | \\d+ | it can start matching anywhere after the start of the preceeding '\\d+' | +| polynomial-redos.js:22:57:22:59 | \\d+ | it can start matching anywhere after the start of the preceeding '\\d*' | +| polynomial-redos.js:22:57:22:59 | \\d+ | it can start matching anywhere after the start of the preceeding '\\d+' | +| polynomial-redos.js:25:37:25:56 | [a-zA-Z0-9+\\/ \\t\\n]+ | it can start matching anywhere after the start of the preceeding '[ \\t]+' | +| polynomial-redos.js:27:14:27:22 | [A-Z]{2,} | it can start matching anywhere | +| polynomial-redos.js:30:19:30:22 | [?]+ | it can start matching anywhere | +| polynomial-redos.js:31:42:31:43 | -+ | it can start matching anywhere | +| polynomial-redos.js:32:45:32:47 | \\n* | it can start matching anywhere | +| polynomial-redos.js:33:17:33:20 | (.)* | it can start matching anywhere | +| regexplib/address.js:18:26:18:31 | [ \\w]* | it can start matching anywhere after the start of the preceeding '[ \\w]{3,}' | +| regexplib/address.js:20:144:20:147 | [ ]+ | it can start matching anywhere after the start of the preceeding '[a-zA-Z0-9 \\-.]{6,}' | +| regexplib/address.js:24:26:24:31 | [ \\w]* | it can start matching anywhere after the start of the preceeding '[ \\w]{3,}' | +| regexplib/address.js:27:3:27:5 | \\s* | it can start matching anywhere | +| regexplib/address.js:27:48:27:50 | \\s* | it can start matching anywhere | +| regexplib/address.js:27:93:27:95 | \\s* | it can start matching anywhere | +| regexplib/address.js:38:39:38:45 | [ 0-9]* | it can start matching anywhere after the start of the preceeding '[ \|\\.]*' | +| regexplib/address.js:51:235:51:239 | \\x20* | it can start matching anywhere after the start of the preceeding '\\x20*' | +| regexplib/address.js:51:631:51:635 | \\x20* | it can start matching anywhere after the start of the preceeding '\\x20*' | +| regexplib/address.js:51:796:51:798 | \\s+ | it can start matching anywhere after the start of the preceeding '\\s+' | +| regexplib/address.js:67:379:67:755 | [a-zA-Z0-9ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝßàáâãäåæçèéêëìíîïñòóôõöøùúûüýÿ\\.\\,\\-\\/\\' ]+ | it can start matching anywhere after the start of the preceeding '[a-zA-Z0-9ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝßàáâãäåæçèéêëìíîïñòóôõöøùúûüýÿ\\.\\,\\-\\/\\']+' | +| regexplib/address.js:69:3:69:5 | \\s* | it can start matching anywhere | +| regexplib/address.js:69:48:69:50 | \\s* | it can start matching anywhere | +| regexplib/address.js:69:93:69:95 | \\s* | it can start matching anywhere | +| regexplib/address.js:75:235:75:239 | \\x20* | it can start matching anywhere after the start of the preceeding '\\x20*' | +| regexplib/address.js:75:631:75:635 | \\x20* | it can start matching anywhere after the start of the preceeding '\\x20*' | +| regexplib/address.js:75:796:75:798 | \\s+ | it can start matching anywhere after the start of the preceeding '\\s+' | +| regexplib/address.js:85:15:85:49 | ([0-9]\|[ ]\|[-]\|[\\(]\|[\\)]\|ext.\|[,])+ | it can start matching anywhere | +| regexplib/address.js:85:51:85:67 | ([ ]\|[:]\|\\t\|[-])* | it can start matching anywhere after the start of the preceeding '([0-9]\|[ ]\|[-]\|[\\(]\|[\\)]\|ext.\|[,])+' | +| regexplib/address.js:93:3:93:5 | \\s* | it can start matching anywhere | +| regexplib/address.js:93:48:93:50 | \\s* | it can start matching anywhere | +| regexplib/address.js:93:93:93:95 | \\s* | it can start matching anywhere | +| regexplib/address.js:95:379:95:755 | [a-zA-Z0-9ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝßàáâãäåæçèéêëìíîïñòóôõöøùúûüýÿ\\.\\,\\-\\/\\' ]+ | it can start matching anywhere after the start of the preceeding '[a-zA-Z0-9ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝßàáâãäåæçèéêëìíîïñòóôõöøùúûüýÿ\\.\\,\\-\\/\\']+' | +| regexplib/email.js:8:16:8:49 | [^ \\t\\(\\)\\<\\>@,;\\:\\\\\\"\\.\\[\\]\\r\\n]+ | it can start matching anywhere | +| regexplib/email.js:12:2:12:4 | \\w+ | it can start matching anywhere | +| regexplib/email.js:15:28:15:30 | \\w* | it can start matching anywhere after the start of the preceeding '\\w+' | +| regexplib/email.js:20:3:20:6 | \\w+? | it can start matching anywhere | +| regexplib/email.js:28:2:28:4 | \\w+ | it can start matching anywhere | +| regexplib/email.js:28:27:28:29 | \\w* | it can start matching anywhere after the start of the preceeding '\\w+' | +| regexplib/email.js:28:73:28:87 | [0-9a-zA-Z'\\.]+ | it can start matching anywhere | +| regexplib/email.js:28:125:28:139 | [0-9a-zA-Z'\\.]+ | it can start matching anywhere | +| regexplib/email.js:29:2:29:7 | [\\w-]+ | it can start matching anywhere | +| regexplib/markup.js:6:99:6:113 | [\\s\\w\\d\\)\\(\\,]* | it can start matching anywhere after the start of the preceeding '[\\d\\w]+' | +| regexplib/markup.js:19:2:19:12 | (+()\\s-]+\|\\*\|\\[.*?\\])+ | it can start matching anywhere | +| tst.js:66:16:66:31 | [\\w#:.~>+()\\s-]+ | it can start matching anywhere | +| tst.js:66:46:66:48 | \\s* | it can start matching anywhere after the start of the preceeding '[\\w#:.~>+()\\s-]+' | +| tst.js:74:14:74:21 | (b\|a?b)* | it can start matching anywhere | +| tst.js:77:14:77:21 | (a\|aa?)* | it can start matching anywhere | +| tst.js:80:14:80:20 | (.\|\\n)* | it can start matching anywhere | +| tst.js:83:14:83:20 | (.\|\\n)* | it can start matching anywhere | +| tst.js:89:25:89:32 | (a\|aa?)* | it can start matching anywhere | +| tst.js:92:14:92:21 | (a\|aa?)* | it can start matching anywhere | diff --git a/javascript/ql/test/query-tests/Performance/ReDoS/PolynomialBackTracking.ql b/javascript/ql/test/query-tests/Performance/ReDoS/PolynomialBackTracking.ql new file mode 100644 index 00000000000..592e5aad7a8 --- /dev/null +++ b/javascript/ql/test/query-tests/Performance/ReDoS/PolynomialBackTracking.ql @@ -0,0 +1,4 @@ +import semmle.javascript.security.performance.SuperlinearBackTracking + +from PolynomialBackTrackingTerm t +select t, t.getReason() diff --git a/javascript/ql/test/query-tests/Performance/ReDoS/PolynomialReDoS.expected b/javascript/ql/test/query-tests/Performance/ReDoS/PolynomialReDoS.expected new file mode 100644 index 00000000000..4d3a9a12da6 --- /dev/null +++ b/javascript/ql/test/query-tests/Performance/ReDoS/PolynomialReDoS.expected @@ -0,0 +1,74 @@ +nodes +| polynomial-redos.js:5:6:5:32 | tainted | +| polynomial-redos.js:5:16:5:32 | req.query.tainted | +| polynomial-redos.js:5:16:5:32 | req.query.tainted | +| polynomial-redos.js:7:2:7:8 | tainted | +| polynomial-redos.js:7:2:7:8 | tainted | +| polynomial-redos.js:8:2:8:8 | tainted | +| polynomial-redos.js:8:2:8:8 | tainted | +| polynomial-redos.js:9:2:9:8 | tainted | +| polynomial-redos.js:9:2:9:8 | tainted | +| polynomial-redos.js:11:2:11:8 | tainted | +| polynomial-redos.js:11:2:11:8 | tainted | +| polynomial-redos.js:12:2:12:8 | tainted | +| polynomial-redos.js:12:2:12:8 | tainted | +| polynomial-redos.js:15:2:15:8 | tainted | +| polynomial-redos.js:15:2:15:8 | tainted | +| polynomial-redos.js:18:2:18:8 | tainted | +| polynomial-redos.js:18:2:18:8 | tainted | +| polynomial-redos.js:19:2:19:8 | tainted | +| polynomial-redos.js:19:2:19:8 | tainted | +| polynomial-redos.js:20:2:20:8 | tainted | +| polynomial-redos.js:20:2:20:8 | tainted | +| polynomial-redos.js:25:2:25:8 | tainted | +| polynomial-redos.js:25:2:25:8 | tainted | +| polynomial-redos.js:27:77:27:83 | tainted | +| polynomial-redos.js:27:77:27:83 | tainted | +| polynomial-redos.js:30:2:30:8 | tainted | +| polynomial-redos.js:30:2:30:8 | tainted | +| polynomial-redos.js:33:2:33:8 | tainted | +| polynomial-redos.js:33:2:33:8 | tainted | +edges +| polynomial-redos.js:5:6:5:32 | tainted | polynomial-redos.js:7:2:7:8 | tainted | +| polynomial-redos.js:5:6:5:32 | tainted | polynomial-redos.js:7:2:7:8 | tainted | +| polynomial-redos.js:5:6:5:32 | tainted | polynomial-redos.js:8:2:8:8 | tainted | +| polynomial-redos.js:5:6:5:32 | tainted | polynomial-redos.js:8:2:8:8 | tainted | +| polynomial-redos.js:5:6:5:32 | tainted | polynomial-redos.js:9:2:9:8 | tainted | +| polynomial-redos.js:5:6:5:32 | tainted | polynomial-redos.js:9:2:9:8 | tainted | +| polynomial-redos.js:5:6:5:32 | tainted | polynomial-redos.js:11:2:11:8 | tainted | +| polynomial-redos.js:5:6:5:32 | tainted | polynomial-redos.js:11:2:11:8 | tainted | +| polynomial-redos.js:5:6:5:32 | tainted | polynomial-redos.js:12:2:12:8 | tainted | +| polynomial-redos.js:5:6:5:32 | tainted | polynomial-redos.js:12:2:12:8 | tainted | +| polynomial-redos.js:5:6:5:32 | tainted | polynomial-redos.js:15:2:15:8 | tainted | +| polynomial-redos.js:5:6:5:32 | tainted | polynomial-redos.js:15:2:15:8 | tainted | +| polynomial-redos.js:5:6:5:32 | tainted | polynomial-redos.js:18:2:18:8 | tainted | +| polynomial-redos.js:5:6:5:32 | tainted | polynomial-redos.js:18:2:18:8 | tainted | +| polynomial-redos.js:5:6:5:32 | tainted | polynomial-redos.js:19:2:19:8 | tainted | +| polynomial-redos.js:5:6:5:32 | tainted | polynomial-redos.js:19:2:19:8 | tainted | +| polynomial-redos.js:5:6:5:32 | tainted | polynomial-redos.js:20:2:20:8 | tainted | +| polynomial-redos.js:5:6:5:32 | tainted | polynomial-redos.js:20:2:20:8 | tainted | +| polynomial-redos.js:5:6:5:32 | tainted | polynomial-redos.js:25:2:25:8 | tainted | +| polynomial-redos.js:5:6:5:32 | tainted | polynomial-redos.js:25:2:25:8 | tainted | +| polynomial-redos.js:5:6:5:32 | tainted | polynomial-redos.js:27:77:27:83 | tainted | +| polynomial-redos.js:5:6:5:32 | tainted | polynomial-redos.js:27:77:27:83 | tainted | +| polynomial-redos.js:5:6:5:32 | tainted | polynomial-redos.js:30:2:30:8 | tainted | +| polynomial-redos.js:5:6:5:32 | tainted | polynomial-redos.js:30:2:30:8 | tainted | +| polynomial-redos.js:5:6:5:32 | tainted | polynomial-redos.js:33:2:33:8 | tainted | +| polynomial-redos.js:5:6:5:32 | tainted | polynomial-redos.js:33:2:33:8 | tainted | +| polynomial-redos.js:5:16:5:32 | req.query.tainted | polynomial-redos.js:5:6:5:32 | tainted | +| polynomial-redos.js:5:16:5:32 | req.query.tainted | polynomial-redos.js:5:6:5:32 | tainted | +#select +| polynomial-redos.js:7:2:7:8 | tainted | polynomial-redos.js:5:16:5:32 | req.query.tainted | polynomial-redos.js:7:2:7:8 | tainted | This expensive $@ use depends on $@. | polynomial-redos.js:7:24:7:26 | \\s+ | regular expression | polynomial-redos.js:5:16:5:32 | req.query.tainted | a user-provided value | +| polynomial-redos.js:8:2:8:8 | tainted | polynomial-redos.js:5:16:5:32 | req.query.tainted | polynomial-redos.js:8:2:8:8 | tainted | This expensive $@ use depends on $@. | polynomial-redos.js:8:17:8:18 | * | regular expression | polynomial-redos.js:5:16:5:32 | req.query.tainted | a user-provided value | +| polynomial-redos.js:9:2:9:8 | tainted | polynomial-redos.js:5:16:5:32 | req.query.tainted | polynomial-redos.js:9:2:9:8 | tainted | This expensive $@ use depends on $@. | polynomial-redos.js:9:19:9:21 | \\s* | regular expression | polynomial-redos.js:5:16:5:32 | req.query.tainted | a user-provided value | +| polynomial-redos.js:11:2:11:8 | tainted | polynomial-redos.js:5:16:5:32 | req.query.tainted | polynomial-redos.js:11:2:11:8 | tainted | This expensive $@ use depends on $@. | polynomial-redos.js:11:19:11:20 | .* | regular expression | polynomial-redos.js:5:16:5:32 | req.query.tainted | a user-provided value | +| polynomial-redos.js:12:2:12:8 | tainted | polynomial-redos.js:5:16:5:32 | req.query.tainted | polynomial-redos.js:12:2:12:8 | tainted | This expensive $@ use depends on $@. | polynomial-redos.js:12:19:12:20 | .* | regular expression | polynomial-redos.js:5:16:5:32 | req.query.tainted | a user-provided value | +| polynomial-redos.js:15:2:15:8 | tainted | polynomial-redos.js:5:16:5:32 | req.query.tainted | polynomial-redos.js:15:2:15:8 | tainted | This expensive $@ use depends on $@. | polynomial-redos.js:15:28:15:35 | [\\s\\S]*? | regular expression | polynomial-redos.js:5:16:5:32 | req.query.tainted | a user-provided value | +| polynomial-redos.js:18:2:18:8 | tainted | polynomial-redos.js:5:16:5:32 | req.query.tainted | polynomial-redos.js:18:2:18:8 | tainted | This expensive $@ use depends on $@. | polynomial-redos.js:18:17:18:22 | [0-9]* | regular expression | polynomial-redos.js:5:16:5:32 | req.query.tainted | a user-provided value | +| polynomial-redos.js:18:2:18:8 | tainted | polynomial-redos.js:5:16:5:32 | req.query.tainted | polynomial-redos.js:18:2:18:8 | tainted | This expensive $@ use depends on $@. | polynomial-redos.js:18:83:18:100 | [\\u0600-\\u06FF\\/]+ | regular expression | polynomial-redos.js:5:16:5:32 | req.query.tainted | a user-provided value | +| polynomial-redos.js:19:2:19:8 | tainted | polynomial-redos.js:5:16:5:32 | req.query.tainted | polynomial-redos.js:19:2:19:8 | tainted | This expensive $@ use depends on $@. | polynomial-redos.js:19:17:19:22 | [0-9]* | regular expression | polynomial-redos.js:5:16:5:32 | req.query.tainted | a user-provided value | +| polynomial-redos.js:20:2:20:8 | tainted | polynomial-redos.js:5:16:5:32 | req.query.tainted | polynomial-redos.js:20:2:20:8 | tainted | This expensive $@ use depends on $@. | polynomial-redos.js:20:56:20:58 | \\d+ | regular expression | polynomial-redos.js:5:16:5:32 | req.query.tainted | a user-provided value | +| polynomial-redos.js:25:2:25:8 | tainted | polynomial-redos.js:5:16:5:32 | req.query.tainted | polynomial-redos.js:25:2:25:8 | tainted | This expensive $@ use depends on $@. | polynomial-redos.js:25:37:25:56 | [a-zA-Z0-9+\\/ \\t\\n]+ | regular expression | polynomial-redos.js:5:16:5:32 | req.query.tainted | a user-provided value | +| polynomial-redos.js:27:77:27:83 | tainted | polynomial-redos.js:5:16:5:32 | req.query.tainted | polynomial-redos.js:27:77:27:83 | tainted | This expensive $@ use depends on $@. | polynomial-redos.js:27:14:27:22 | [A-Z]{2,} | regular expression | polynomial-redos.js:5:16:5:32 | req.query.tainted | a user-provided value | +| polynomial-redos.js:30:2:30:8 | tainted | polynomial-redos.js:5:16:5:32 | req.query.tainted | polynomial-redos.js:30:2:30:8 | tainted | This expensive $@ use depends on $@. | polynomial-redos.js:30:19:30:22 | [?]+ | regular expression | polynomial-redos.js:5:16:5:32 | req.query.tainted | a user-provided value | +| polynomial-redos.js:33:2:33:8 | tainted | polynomial-redos.js:5:16:5:32 | req.query.tainted | polynomial-redos.js:33:2:33:8 | tainted | This expensive $@ use depends on $@. | polynomial-redos.js:33:17:33:20 | (.)* | regular expression | polynomial-redos.js:5:16:5:32 | req.query.tainted | a user-provided value | diff --git a/javascript/ql/test/query-tests/Performance/ReDoS/PolynomialReDoS.qlref b/javascript/ql/test/query-tests/Performance/ReDoS/PolynomialReDoS.qlref new file mode 100644 index 00000000000..e26045522d1 --- /dev/null +++ b/javascript/ql/test/query-tests/Performance/ReDoS/PolynomialReDoS.qlref @@ -0,0 +1 @@ +Performance/PolynomialReDoS.ql diff --git a/javascript/ql/test/query-tests/Performance/ReDoS/ReDoS.expected b/javascript/ql/test/query-tests/Performance/ReDoS/ReDoS.expected index 6601731045f..f31ed3e638b 100644 --- a/javascript/ql/test/query-tests/Performance/ReDoS/ReDoS.expected +++ b/javascript/ql/test/query-tests/Performance/ReDoS/ReDoS.expected @@ -1,3 +1,5 @@ +| polynomial-redos.js:17:5:17:6 | .* | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of ','. | +| polynomial-redos.js:41:52:41:63 | [\\x21-\\x7E]* | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of '?'. | | regexplib/address.js:51:803:51:811 | [A-Za-z]+ | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of 'A'. | | regexplib/address.js:75:803:75:811 | [A-Za-z]+ | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of 'A'. | | regexplib/dates.js:66:133:66:139 | JANUARY | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of 'JANUARY'. | diff --git a/javascript/ql/test/query-tests/Performance/ReDoS/polynomial-redos.js b/javascript/ql/test/query-tests/Performance/ReDoS/polynomial-redos.js new file mode 100644 index 00000000000..4175bd48a50 --- /dev/null +++ b/javascript/ql/test/query-tests/Performance/ReDoS/polynomial-redos.js @@ -0,0 +1,49 @@ +var express = require('express'); +var app = express(); + +app.use(function(req, res) { + let tainted = req.query.tainted; + + tainted.replace(/^\s+|\s+$/g, ''); // NOT OK + tainted.split(/ *, */); // NOT OK + tainted.replace(/\s*\n\s*/g, ' '); // NOT OK + tainted.split('\n'); // OK + tainted.replace(/.*[/\\]/, ''); // NOT OK + tainted.replace(/.*\./, ''); // NOT OK + tainted.replace(/^.*[/\\]/, ''); // OK + tainted.replace(/^.*\./, ''); // OK + tainted.replace(/^(`+)\s*([\s\S]*?[^`])\s*\1(?!`)/); // NOT OK + tainted.replace(/^(`+)([\s\S]*?[^`])\1(?!`)/); // OK + /^(.*,)+(.+)?$/.test(tainted); // NOT OK - but only flagged by js/redos + tainted.match(/[0-9]*['a-z\u00A0-\u05FF\u0700-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+|[\u0600-\u06FF\/]+(\s*?[\u0600-\u06FF]+){1,2}/i); // NOT OK + tainted.match(/[0-9]*['a-z\u00A0-\u05FF\u0700-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]{1,256}|[\u0600-\u06FF\/]{1,256}(\s*?[\u0600-\u06FF]{1,256}){1,2}/i); // NOT OK (even though it is a proposed fix for the above) + tainted.match(/^(\+|-)?(\d+|(\d*\.\d*))?(E|e)?([-+])?(\d+)?$/); // NOT OK + if (tainted.length < 7000) { + tainted.match(/^(\+|-)?(\d+|(\d*\.\d*))?(E|e)?([-+])?(\d+)?$/); // OK - but flagged + } + + tainted.match(/^([a-z0-9-]+)[ \t]+([a-zA-Z0-9+\/ \t\n]+[=]*)(.*)$/); // NOT OK + tainted.match(/^([a-z0-9-]+)[ \t\n]+([a-zA-Z0-9+\/][a-zA-Z0-9+\/ \t\n=]*)([^a-zA-Z0-9+\/ \t\n=].*)?$/); // OK + /[a-z][A-Z]|[A-Z]{2,}[a-z]|[0-9][a-zA-Z]|[a-zA-Z][0-9]|[^a-zA-Z0-9 ]/.test(tainted); // NOT OK + /[a-z][A-Z]|[A-Z]{2}[a-z]|[0-9][a-zA-Z]|[a-zA-Z][0-9]|[^a-zA-Z0-9 ]/.test(tainted); // OK + + tainted.replace(/[?]+.*$/g, ""); // OK - can not fail - but still flagged + tainted.replace(/\-\-+/g, "-").replace(/-+$/, ""); // OK - indirectly sanitized + tainted.replace(/\n\n\n+/g, "\n").replace(/\n*$/g, ""); // OK - indirectly sanitized + tainted.match(/(.)*solve\/challenges\/server-side(.)*/); // NOT OK + tainted.match(/(?![\s\S]*)/i); // OK + + tainted.match(/<.*class="([^"]+)".*>/); // NOT OK - but not flagged + tainted.match(/<.*style="([^"]+)".*>/); // NOT OK - but not flagged + tainted.match(/<.*href="([^"]+)".*>/); // NOT OK - but not flagged + + tainted.match(/^([^-]+)-([A-Za-z0-9+/]+(?:=?=?))([?\x21-\x7E]*)$/); // NOT OK - but not flagged + tainted.match(/^([^-]+)-([A-Za-z0-9+/=]{44,88})(\?[\x21-\x7E]*)*$/); // NOT OK (it is a fix for the above, but it introduces exponential complexity elsewhere) + + tainted.match(/^([a-z0-9-]+)[ \t]+([a-zA-Z0-9+\/]+[=]*)([\n \t]+([^\n]+))?$/); // NOT OK - but not flagged due to lack of support for inverted character classes + tainted.match(/^([a-z0-9-]+)[ \t]+([a-zA-Z0-9+\/]+[=]*)([ \t]+([^ \t][^\n]*[\n]*)?)?$/); // OK + + tainted.match(/^(?:\.?[a-zA-Z_][a-zA-Z_0-9]*)+$/); // NOT OK - but not flagged + tainted.match(/^(?:\.?[a-zA-Z_][a-zA-Z_0-9]*)(?:\.[a-zA-Z_][a-zA-Z_0-9]*)*$/); // OK + +}); diff --git a/javascript/ql/test/query-tests/Security/CWE-020/UselessCharacterEscape.ql b/javascript/ql/test/query-tests/Security/CWE-020/UselessCharacterEscape.ql index ea18eafe350..9e4f8d9f67f 100644 --- a/javascript/ql/test/query-tests/Security/CWE-020/UselessCharacterEscape.ql +++ b/javascript/ql/test/query-tests/Security/CWE-020/UselessCharacterEscape.ql @@ -6,6 +6,4 @@ where char = getAnIdentityEscapedCharacter(n, _, _) and not hasALikelyRegExpPatternMistake(n) and not char = "\n" // ignore escaped newlines in multiline strings -select n, - "The escape sequence '\\" + char + "' is equivalent to just '" + - char + "'." +select n, "The escape sequence '\\" + char + "' is equivalent to just '" + char + "'." 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 061f3522e34..ec4531fde13 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 @@ -1123,6 +1123,160 @@ nodes | 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:173:7:173:48 | path | +| TaintedPath.js:173:7:173:48 | path | +| TaintedPath.js:173:7:173:48 | path | +| TaintedPath.js:173:7:173:48 | path | +| TaintedPath.js:173:7:173:48 | path | +| TaintedPath.js:173:7:173:48 | path | +| TaintedPath.js:173:7:173:48 | path | +| TaintedPath.js:173:7:173:48 | path | +| TaintedPath.js:173:7:173:48 | path | +| TaintedPath.js:173:7:173:48 | path | +| TaintedPath.js:173:7:173:48 | path | +| TaintedPath.js:173:7:173:48 | path | +| TaintedPath.js:173:7:173:48 | path | +| TaintedPath.js:173:7:173:48 | path | +| TaintedPath.js:173:7:173:48 | path | +| TaintedPath.js:173:7:173:48 | path | +| TaintedPath.js:173:14:173:37 | url.par ... , true) | +| TaintedPath.js:173:14:173:37 | url.par ... , true) | +| TaintedPath.js:173:14:173:37 | url.par ... , true) | +| TaintedPath.js:173:14:173:37 | url.par ... , true) | +| TaintedPath.js:173:14:173:37 | url.par ... , true) | +| TaintedPath.js:173:14:173:37 | url.par ... , true) | +| TaintedPath.js:173:14:173:37 | url.par ... , true) | +| TaintedPath.js:173:14:173:37 | url.par ... , true) | +| TaintedPath.js:173:14:173:37 | url.par ... , true) | +| TaintedPath.js:173:14:173:37 | url.par ... , true) | +| TaintedPath.js:173:14:173:37 | url.par ... , true) | +| TaintedPath.js:173:14:173:37 | url.par ... , true) | +| TaintedPath.js:173:14:173:37 | url.par ... , true) | +| TaintedPath.js:173:14:173:37 | url.par ... , true) | +| TaintedPath.js:173:14:173:37 | url.par ... , true) | +| TaintedPath.js:173:14:173:37 | url.par ... , true) | +| TaintedPath.js:173:14:173:43 | url.par ... ).query | +| TaintedPath.js:173:14:173:43 | url.par ... ).query | +| TaintedPath.js:173:14:173:43 | url.par ... ).query | +| TaintedPath.js:173:14:173:43 | url.par ... ).query | +| TaintedPath.js:173:14:173:43 | url.par ... ).query | +| TaintedPath.js:173:14:173:43 | url.par ... ).query | +| TaintedPath.js:173:14:173:43 | url.par ... ).query | +| TaintedPath.js:173:14:173:43 | url.par ... ).query | +| TaintedPath.js:173:14:173:43 | url.par ... ).query | +| TaintedPath.js:173:14:173:43 | url.par ... ).query | +| TaintedPath.js:173:14:173:43 | url.par ... ).query | +| TaintedPath.js:173:14:173:43 | url.par ... ).query | +| TaintedPath.js:173:14:173:43 | url.par ... ).query | +| TaintedPath.js:173:14:173:43 | url.par ... ).query | +| TaintedPath.js:173:14:173:43 | url.par ... ).query | +| TaintedPath.js:173:14:173:43 | url.par ... ).query | +| TaintedPath.js:173:14:173:48 | url.par ... ry.path | +| TaintedPath.js:173:14:173:48 | url.par ... ry.path | +| TaintedPath.js:173:14:173:48 | url.par ... ry.path | +| TaintedPath.js:173:14:173:48 | url.par ... ry.path | +| TaintedPath.js:173:14:173:48 | url.par ... ry.path | +| TaintedPath.js:173:14:173:48 | url.par ... ry.path | +| TaintedPath.js:173:14:173:48 | url.par ... ry.path | +| TaintedPath.js:173:14:173:48 | url.par ... ry.path | +| TaintedPath.js:173:14:173:48 | url.par ... ry.path | +| TaintedPath.js:173:14:173:48 | url.par ... ry.path | +| TaintedPath.js:173:14:173:48 | url.par ... ry.path | +| TaintedPath.js:173:14:173:48 | url.par ... ry.path | +| TaintedPath.js:173:14:173:48 | url.par ... ry.path | +| TaintedPath.js:173:14:173:48 | url.par ... ry.path | +| TaintedPath.js:173:14:173:48 | url.par ... ry.path | +| TaintedPath.js:173:14:173:48 | url.par ... ry.path | +| TaintedPath.js:173:24:173:30 | req.url | +| TaintedPath.js:173:24:173:30 | req.url | +| TaintedPath.js:173:24:173:30 | req.url | +| TaintedPath.js:173:24:173:30 | req.url | +| TaintedPath.js:173:24:173:30 | req.url | +| TaintedPath.js:177:29:177:32 | path | +| TaintedPath.js:177:29:177:32 | path | +| TaintedPath.js:177:29:177:32 | path | +| TaintedPath.js:177:29:177:32 | path | +| TaintedPath.js:177:29:177:32 | path | +| TaintedPath.js:177:29:177:32 | path | +| TaintedPath.js:177:29:177:32 | path | +| TaintedPath.js:177:29:177:32 | path | +| TaintedPath.js:177:29:177:32 | path | +| TaintedPath.js:177:29:177:32 | path | +| TaintedPath.js:177:29:177:32 | path | +| TaintedPath.js:177:29:177:32 | path | +| TaintedPath.js:177:29:177:32 | path | +| TaintedPath.js:177:29:177:32 | path | +| TaintedPath.js:177:29:177:32 | path | +| TaintedPath.js:177:29:177:32 | path | +| TaintedPath.js:177:29:177:55 | path.re ... /g, '') | +| TaintedPath.js:177:29:177:55 | path.re ... /g, '') | +| TaintedPath.js:177:29:177:55 | path.re ... /g, '') | +| TaintedPath.js:177:29:177:55 | path.re ... /g, '') | +| TaintedPath.js:177:29:177:55 | path.re ... /g, '') | +| TaintedPath.js:177:29:177:55 | path.re ... /g, '') | +| TaintedPath.js:177:29:177:55 | path.re ... /g, '') | +| TaintedPath.js:177:29:177:55 | path.re ... /g, '') | +| TaintedPath.js:177:29:177:55 | path.re ... /g, '') | +| TaintedPath.js:177:29:177:55 | path.re ... /g, '') | +| TaintedPath.js:177:29:177:55 | path.re ... /g, '') | +| TaintedPath.js:177:29:177:55 | path.re ... /g, '') | +| TaintedPath.js:177:29:177:55 | path.re ... /g, '') | +| TaintedPath.js:177:29:177:55 | path.re ... /g, '') | +| TaintedPath.js:177:29:177:55 | path.re ... /g, '') | +| TaintedPath.js:177:29:177:55 | path.re ... /g, '') | +| TaintedPath.js:177:29:177:55 | path.re ... /g, '') | +| TaintedPath.js:183:29:183:32 | path | +| TaintedPath.js:183:29:183:32 | path | +| TaintedPath.js:183:29:183:32 | path | +| TaintedPath.js:183:29:183:32 | path | +| TaintedPath.js:183:29:183:32 | path | +| TaintedPath.js:183:29:183:32 | path | +| TaintedPath.js:183:29:183:32 | path | +| TaintedPath.js:183:29:183:32 | path | +| TaintedPath.js:183:29:183:52 | path.re ... /g, '') | +| TaintedPath.js:183:29:183:52 | path.re ... /g, '') | +| TaintedPath.js:183:29:183:52 | path.re ... /g, '') | +| TaintedPath.js:183:29:183:52 | path.re ... /g, '') | +| TaintedPath.js:183:29:183:52 | path.re ... /g, '') | +| TaintedPath.js:184:29:184:32 | path | +| TaintedPath.js:184:29:184:32 | path | +| TaintedPath.js:184:29:184:32 | path | +| TaintedPath.js:184:29:184:32 | path | +| TaintedPath.js:184:29:184:32 | path | +| TaintedPath.js:184:29:184:32 | path | +| TaintedPath.js:184:29:184:32 | path | +| TaintedPath.js:184:29:184:32 | path | +| TaintedPath.js:184:29:184:53 | path.re ... /g, '') | +| TaintedPath.js:184:29:184:53 | path.re ... /g, '') | +| TaintedPath.js:184:29:184:53 | path.re ... /g, '') | +| TaintedPath.js:184:29:184:53 | path.re ... /g, '') | +| TaintedPath.js:184:29:184:53 | path.re ... /g, '') | +| TaintedPath.js:185:29:185:32 | path | +| TaintedPath.js:185:29:185:32 | path | +| TaintedPath.js:185:29:185:32 | path | +| TaintedPath.js:185:29:185:32 | path | +| TaintedPath.js:185:29:185:32 | path | +| TaintedPath.js:185:29:185:32 | path | +| TaintedPath.js:185:29:185:32 | path | +| TaintedPath.js:185:29:185:32 | path | +| TaintedPath.js:185:29:185:51 | path.re ... /g, '') | +| TaintedPath.js:185:29:185:51 | path.re ... /g, '') | +| TaintedPath.js:185:29:185:51 | path.re ... /g, '') | +| TaintedPath.js:185:29:185:51 | path.re ... /g, '') | +| TaintedPath.js:185:29:185:51 | path.re ... /g, '') | +| TaintedPath.js:186:29:186:32 | path | +| TaintedPath.js:186:29:186:32 | path | +| TaintedPath.js:186:29:186:32 | path | +| TaintedPath.js:186:29:186:32 | path | +| TaintedPath.js:186:29:186:32 | path | +| TaintedPath.js:186:29:186:32 | path | +| TaintedPath.js:186:29:186:32 | path | +| TaintedPath.js:186:29:186:32 | path | +| TaintedPath.js:186:29:186:57 | path.re ... /g, '') | +| TaintedPath.js:186:29:186:57 | path.re ... /g, '') | +| TaintedPath.js:186:29:186:57 | path.re ... /g, '') | +| TaintedPath.js:186:29:186:57 | path.re ... /g, '') | +| TaintedPath.js:186:29:186:57 | path.re ... /g, '') | | normalizedPaths.js:11:7:11:27 | path | | normalizedPaths.js:11:7:11:27 | path | | normalizedPaths.js:11:7:11:27 | path | @@ -1631,6 +1785,261 @@ nodes | normalizedPaths.js:332:19:332:32 | normalizedPath | | normalizedPaths.js:332:19:332:32 | normalizedPath | | normalizedPaths.js:332:19:332:32 | normalizedPath | +| normalizedPaths.js:339:6:339:46 | path | +| normalizedPaths.js:339:6:339:46 | path | +| normalizedPaths.js:339:6:339:46 | path | +| normalizedPaths.js:339:6:339:46 | path | +| normalizedPaths.js:339:13:339:46 | pathMod ... y.path) | +| normalizedPaths.js:339:13:339:46 | pathMod ... y.path) | +| normalizedPaths.js:339:13:339:46 | pathMod ... y.path) | +| normalizedPaths.js:339:13:339:46 | pathMod ... y.path) | +| normalizedPaths.js:339:32:339:45 | req.query.path | +| normalizedPaths.js:339:32:339:45 | req.query.path | +| normalizedPaths.js:339:32:339:45 | req.query.path | +| normalizedPaths.js:339:32:339:45 | req.query.path | +| normalizedPaths.js:339:32:339:45 | req.query.path | +| normalizedPaths.js:341:18:341:21 | path | +| normalizedPaths.js:341:18:341:21 | path | +| normalizedPaths.js:341:18:341:21 | path | +| normalizedPaths.js:341:18:341:21 | path | +| normalizedPaths.js:341:18:341:21 | path | +| normalizedPaths.js:346:19:346:22 | path | +| normalizedPaths.js:346:19:346:22 | path | +| normalizedPaths.js:346:19:346:22 | path | +| normalizedPaths.js:346:19:346:22 | path | +| normalizedPaths.js:346:19:346:22 | path | +| normalizedPaths.js:354:7:354:27 | path | +| normalizedPaths.js:354:7:354:27 | path | +| normalizedPaths.js:354:7:354:27 | path | +| normalizedPaths.js:354:7:354:27 | path | +| normalizedPaths.js:354:14:354:27 | req.query.path | +| normalizedPaths.js:354:14:354:27 | req.query.path | +| normalizedPaths.js:354:14:354:27 | req.query.path | +| normalizedPaths.js:354:14:354:27 | req.query.path | +| normalizedPaths.js:354:14:354:27 | req.query.path | +| normalizedPaths.js:356:19:356:22 | path | +| normalizedPaths.js:356:19:356:22 | path | +| normalizedPaths.js:356:19:356:22 | path | +| normalizedPaths.js:356:19:356:22 | path | +| normalizedPaths.js:356:19:356:22 | path | +| normalizedPaths.js:358:7:358:51 | requestPath | +| normalizedPaths.js:358:7:358:51 | requestPath | +| normalizedPaths.js:358:7:358:51 | requestPath | +| normalizedPaths.js:358:21:358:51 | pathMod ... , path) | +| normalizedPaths.js:358:21:358:51 | pathMod ... , path) | +| normalizedPaths.js:358:21:358:51 | pathMod ... , path) | +| normalizedPaths.js:358:47:358:50 | path | +| normalizedPaths.js:358:47:358:50 | path | +| normalizedPaths.js:358:47:358:50 | path | +| normalizedPaths.js:363:21:363:31 | requestPath | +| normalizedPaths.js:363:21:363:31 | requestPath | +| normalizedPaths.js:363:21:363:31 | requestPath | +| normalizedPaths.js:363:21:363:31 | requestPath | +| other-fs-libraries.js:9:7:9:48 | path | +| other-fs-libraries.js:9:7:9:48 | path | +| other-fs-libraries.js:9:7:9:48 | path | +| other-fs-libraries.js:9:7:9:48 | path | +| other-fs-libraries.js:9:7:9:48 | path | +| other-fs-libraries.js:9:7:9:48 | path | +| other-fs-libraries.js:9:7:9:48 | path | +| other-fs-libraries.js:9:7:9:48 | path | +| other-fs-libraries.js:9:7:9:48 | path | +| other-fs-libraries.js:9:7:9:48 | path | +| other-fs-libraries.js:9:7:9:48 | path | +| other-fs-libraries.js:9:7:9:48 | path | +| other-fs-libraries.js:9:7:9:48 | path | +| other-fs-libraries.js:9:7:9:48 | path | +| other-fs-libraries.js:9:7:9:48 | path | +| other-fs-libraries.js:9:7:9:48 | path | +| other-fs-libraries.js:9:14:9:37 | url.par ... , true) | +| other-fs-libraries.js:9:14:9:37 | url.par ... , true) | +| other-fs-libraries.js:9:14:9:37 | url.par ... , true) | +| other-fs-libraries.js:9:14:9:37 | url.par ... , true) | +| other-fs-libraries.js:9:14:9:37 | url.par ... , true) | +| other-fs-libraries.js:9:14:9:37 | url.par ... , true) | +| other-fs-libraries.js:9:14:9:37 | url.par ... , true) | +| other-fs-libraries.js:9:14:9:37 | url.par ... , true) | +| other-fs-libraries.js:9:14:9:37 | url.par ... , true) | +| other-fs-libraries.js:9:14:9:37 | url.par ... , true) | +| other-fs-libraries.js:9:14:9:37 | url.par ... , true) | +| other-fs-libraries.js:9:14:9:37 | url.par ... , true) | +| other-fs-libraries.js:9:14:9:37 | url.par ... , true) | +| other-fs-libraries.js:9:14:9:37 | url.par ... , true) | +| other-fs-libraries.js:9:14:9:37 | url.par ... , true) | +| other-fs-libraries.js:9:14:9:37 | url.par ... , true) | +| other-fs-libraries.js:9:14:9:43 | url.par ... ).query | +| other-fs-libraries.js:9:14:9:43 | url.par ... ).query | +| other-fs-libraries.js:9:14:9:43 | url.par ... ).query | +| other-fs-libraries.js:9:14:9:43 | url.par ... ).query | +| other-fs-libraries.js:9:14:9:43 | url.par ... ).query | +| other-fs-libraries.js:9:14:9:43 | url.par ... ).query | +| other-fs-libraries.js:9:14:9:43 | url.par ... ).query | +| other-fs-libraries.js:9:14:9:43 | url.par ... ).query | +| other-fs-libraries.js:9:14:9:43 | url.par ... ).query | +| other-fs-libraries.js:9:14:9:43 | url.par ... ).query | +| other-fs-libraries.js:9:14:9:43 | url.par ... ).query | +| other-fs-libraries.js:9:14:9:43 | url.par ... ).query | +| other-fs-libraries.js:9:14:9:43 | url.par ... ).query | +| other-fs-libraries.js:9:14:9:43 | url.par ... ).query | +| other-fs-libraries.js:9:14:9:43 | url.par ... ).query | +| other-fs-libraries.js:9:14:9:43 | url.par ... ).query | +| other-fs-libraries.js:9:14:9:48 | url.par ... ry.path | +| other-fs-libraries.js:9:14:9:48 | url.par ... ry.path | +| other-fs-libraries.js:9:14:9:48 | url.par ... ry.path | +| other-fs-libraries.js:9:14:9:48 | url.par ... ry.path | +| other-fs-libraries.js:9:14:9:48 | url.par ... ry.path | +| other-fs-libraries.js:9:14:9:48 | url.par ... ry.path | +| other-fs-libraries.js:9:14:9:48 | url.par ... ry.path | +| other-fs-libraries.js:9:14:9:48 | url.par ... ry.path | +| other-fs-libraries.js:9:14:9:48 | url.par ... ry.path | +| other-fs-libraries.js:9:14:9:48 | url.par ... ry.path | +| other-fs-libraries.js:9:14:9:48 | url.par ... ry.path | +| other-fs-libraries.js:9:14:9:48 | url.par ... ry.path | +| other-fs-libraries.js:9:14:9:48 | url.par ... ry.path | +| other-fs-libraries.js:9:14:9:48 | url.par ... ry.path | +| other-fs-libraries.js:9:14:9:48 | url.par ... ry.path | +| other-fs-libraries.js:9:14:9:48 | url.par ... ry.path | +| other-fs-libraries.js:9:24:9:30 | req.url | +| other-fs-libraries.js:9:24:9:30 | req.url | +| other-fs-libraries.js:9:24:9:30 | req.url | +| other-fs-libraries.js:9:24:9:30 | req.url | +| other-fs-libraries.js:9:24:9:30 | req.url | +| other-fs-libraries.js:11:19:11:22 | path | +| other-fs-libraries.js:11:19:11:22 | path | +| other-fs-libraries.js:11:19:11:22 | path | +| other-fs-libraries.js:11:19:11:22 | path | +| other-fs-libraries.js:11:19:11:22 | path | +| other-fs-libraries.js:11:19:11:22 | path | +| other-fs-libraries.js:11:19:11:22 | path | +| other-fs-libraries.js:11:19:11:22 | path | +| other-fs-libraries.js:11:19:11:22 | path | +| other-fs-libraries.js:11:19:11:22 | path | +| other-fs-libraries.js:11:19:11:22 | path | +| other-fs-libraries.js:11:19:11:22 | path | +| other-fs-libraries.js:11:19:11:22 | path | +| other-fs-libraries.js:11:19:11:22 | path | +| other-fs-libraries.js:11:19:11:22 | path | +| other-fs-libraries.js:11:19:11:22 | path | +| other-fs-libraries.js:11:19:11:22 | path | +| other-fs-libraries.js:12:27:12:30 | path | +| other-fs-libraries.js:12:27:12:30 | path | +| other-fs-libraries.js:12:27:12:30 | path | +| other-fs-libraries.js:12:27:12:30 | path | +| other-fs-libraries.js:12:27:12:30 | path | +| other-fs-libraries.js:12:27:12:30 | path | +| other-fs-libraries.js:12:27:12:30 | path | +| other-fs-libraries.js:12:27:12:30 | path | +| other-fs-libraries.js:12:27:12:30 | path | +| other-fs-libraries.js:12:27:12:30 | path | +| other-fs-libraries.js:12:27:12:30 | path | +| other-fs-libraries.js:12:27:12:30 | path | +| other-fs-libraries.js:12:27:12:30 | path | +| other-fs-libraries.js:12:27:12:30 | path | +| other-fs-libraries.js:12:27:12:30 | path | +| other-fs-libraries.js:12:27:12:30 | path | +| other-fs-libraries.js:12:27:12:30 | path | +| other-fs-libraries.js:13:24:13:27 | path | +| other-fs-libraries.js:13:24:13:27 | path | +| other-fs-libraries.js:13:24:13:27 | path | +| other-fs-libraries.js:13:24:13:27 | path | +| other-fs-libraries.js:13:24:13:27 | path | +| other-fs-libraries.js:13:24:13:27 | path | +| other-fs-libraries.js:13:24:13:27 | path | +| other-fs-libraries.js:13:24:13:27 | path | +| other-fs-libraries.js:13:24:13:27 | path | +| other-fs-libraries.js:13:24:13:27 | path | +| other-fs-libraries.js:13:24:13:27 | path | +| other-fs-libraries.js:13:24:13:27 | path | +| other-fs-libraries.js:13:24:13:27 | path | +| other-fs-libraries.js:13:24:13:27 | path | +| other-fs-libraries.js:13:24:13:27 | path | +| other-fs-libraries.js:13:24:13:27 | path | +| other-fs-libraries.js:13:24:13:27 | path | +| other-fs-libraries.js:14:27:14:30 | path | +| other-fs-libraries.js:14:27:14:30 | path | +| other-fs-libraries.js:14:27:14:30 | path | +| other-fs-libraries.js:14:27:14:30 | path | +| other-fs-libraries.js:14:27:14:30 | path | +| other-fs-libraries.js:14:27:14:30 | path | +| other-fs-libraries.js:14:27:14:30 | path | +| other-fs-libraries.js:14:27:14:30 | path | +| other-fs-libraries.js:14:27:14:30 | path | +| other-fs-libraries.js:14:27:14:30 | path | +| other-fs-libraries.js:14:27:14:30 | path | +| other-fs-libraries.js:14:27:14:30 | path | +| other-fs-libraries.js:14:27:14:30 | path | +| other-fs-libraries.js:14:27:14:30 | path | +| other-fs-libraries.js:14:27:14:30 | path | +| other-fs-libraries.js:14:27:14:30 | path | +| other-fs-libraries.js:14:27:14:30 | path | +| other-fs-libraries.js:16:34:16:37 | path | +| other-fs-libraries.js:16:34:16:37 | path | +| other-fs-libraries.js:16:34:16:37 | path | +| other-fs-libraries.js:16:34:16:37 | path | +| other-fs-libraries.js:16:34:16:37 | path | +| other-fs-libraries.js:16:34:16:37 | path | +| other-fs-libraries.js:16:34:16:37 | path | +| other-fs-libraries.js:16:34:16:37 | path | +| other-fs-libraries.js:16:34:16:37 | path | +| other-fs-libraries.js:16:34:16:37 | path | +| other-fs-libraries.js:16:34:16:37 | path | +| other-fs-libraries.js:16:34:16:37 | path | +| other-fs-libraries.js:16:34:16:37 | path | +| other-fs-libraries.js:16:34:16:37 | path | +| other-fs-libraries.js:16:34:16:37 | path | +| other-fs-libraries.js:16:34:16:37 | path | +| other-fs-libraries.js:16:34:16:37 | path | +| other-fs-libraries.js:17:35:17:38 | path | +| other-fs-libraries.js:17:35:17:38 | path | +| other-fs-libraries.js:17:35:17:38 | path | +| other-fs-libraries.js:17:35:17:38 | path | +| other-fs-libraries.js:17:35:17:38 | path | +| other-fs-libraries.js:17:35:17:38 | path | +| other-fs-libraries.js:17:35:17:38 | path | +| other-fs-libraries.js:17:35:17:38 | path | +| other-fs-libraries.js:17:35:17:38 | path | +| other-fs-libraries.js:17:35:17:38 | path | +| other-fs-libraries.js:17:35:17:38 | path | +| other-fs-libraries.js:17:35:17:38 | path | +| other-fs-libraries.js:17:35:17:38 | path | +| other-fs-libraries.js:17:35:17:38 | path | +| other-fs-libraries.js:17:35:17:38 | path | +| other-fs-libraries.js:17:35:17:38 | path | +| other-fs-libraries.js:17:35:17:38 | path | +| other-fs-libraries.js:19:56:19:59 | path | +| other-fs-libraries.js:19:56:19:59 | path | +| other-fs-libraries.js:19:56:19:59 | path | +| other-fs-libraries.js:19:56:19:59 | path | +| other-fs-libraries.js:19:56:19:59 | path | +| other-fs-libraries.js:19:56:19:59 | path | +| other-fs-libraries.js:19:56:19:59 | path | +| other-fs-libraries.js:19:56:19:59 | path | +| other-fs-libraries.js:19:56:19:59 | path | +| other-fs-libraries.js:19:56:19:59 | path | +| other-fs-libraries.js:19:56:19:59 | path | +| other-fs-libraries.js:19:56:19:59 | path | +| other-fs-libraries.js:19:56:19:59 | path | +| other-fs-libraries.js:19:56:19:59 | path | +| other-fs-libraries.js:19:56:19:59 | path | +| other-fs-libraries.js:19:56:19:59 | path | +| other-fs-libraries.js:19:56:19:59 | path | +| other-fs-libraries.js:24:35:24:38 | path | +| other-fs-libraries.js:24:35:24:38 | path | +| other-fs-libraries.js:24:35:24:38 | path | +| other-fs-libraries.js:24:35:24:38 | path | +| other-fs-libraries.js:24:35:24:38 | path | +| other-fs-libraries.js:24:35:24:38 | path | +| other-fs-libraries.js:24:35:24:38 | path | +| other-fs-libraries.js:24:35:24:38 | path | +| other-fs-libraries.js:24:35:24:38 | path | +| other-fs-libraries.js:24:35:24:38 | path | +| other-fs-libraries.js:24:35:24:38 | path | +| other-fs-libraries.js:24:35:24:38 | path | +| other-fs-libraries.js:24:35:24:38 | path | +| other-fs-libraries.js:24:35:24:38 | path | +| other-fs-libraries.js:24:35:24:38 | path | +| other-fs-libraries.js:24:35:24:38 | path | +| other-fs-libraries.js:24:35:24:38 | path | | tainted-require.js:7:19:7:37 | req.param("module") | | tainted-require.js:7:19:7:37 | req.param("module") | | tainted-require.js:7:19:7:37 | req.param("module") | @@ -3928,6 +4337,230 @@ edges | 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:173:7:173:48 | path | TaintedPath.js:177:29:177:32 | path | +| TaintedPath.js:173:7:173:48 | path | TaintedPath.js:177:29:177:32 | path | +| TaintedPath.js:173:7:173:48 | path | TaintedPath.js:177:29:177:32 | path | +| TaintedPath.js:173:7:173:48 | path | TaintedPath.js:177:29:177:32 | path | +| TaintedPath.js:173:7:173:48 | path | TaintedPath.js:177:29:177:32 | path | +| TaintedPath.js:173:7:173:48 | path | TaintedPath.js:177:29:177:32 | path | +| TaintedPath.js:173:7:173:48 | path | TaintedPath.js:177:29:177:32 | path | +| TaintedPath.js:173:7:173:48 | path | TaintedPath.js:177:29:177:32 | path | +| TaintedPath.js:173:7:173:48 | path | TaintedPath.js:177:29:177:32 | path | +| TaintedPath.js:173:7:173:48 | path | TaintedPath.js:177:29:177:32 | path | +| TaintedPath.js:173:7:173:48 | path | TaintedPath.js:177:29:177:32 | path | +| TaintedPath.js:173:7:173:48 | path | TaintedPath.js:177:29:177:32 | path | +| TaintedPath.js:173:7:173:48 | path | TaintedPath.js:177:29:177:32 | path | +| TaintedPath.js:173:7:173:48 | path | TaintedPath.js:177:29:177:32 | path | +| TaintedPath.js:173:7:173:48 | path | TaintedPath.js:177:29:177:32 | path | +| TaintedPath.js:173:7:173:48 | path | TaintedPath.js:177:29:177:32 | path | +| TaintedPath.js:173:7:173:48 | path | TaintedPath.js:183:29:183:32 | path | +| TaintedPath.js:173:7:173:48 | path | TaintedPath.js:183:29:183:32 | path | +| TaintedPath.js:173:7:173:48 | path | TaintedPath.js:183:29:183:32 | path | +| TaintedPath.js:173:7:173:48 | path | TaintedPath.js:183:29:183:32 | path | +| TaintedPath.js:173:7:173:48 | path | TaintedPath.js:183:29:183:32 | path | +| TaintedPath.js:173:7:173:48 | path | TaintedPath.js:183:29:183:32 | path | +| TaintedPath.js:173:7:173:48 | path | TaintedPath.js:183:29:183:32 | path | +| TaintedPath.js:173:7:173:48 | path | TaintedPath.js:183:29:183:32 | path | +| TaintedPath.js:173:7:173:48 | path | TaintedPath.js:184:29:184:32 | path | +| TaintedPath.js:173:7:173:48 | path | TaintedPath.js:184:29:184:32 | path | +| TaintedPath.js:173:7:173:48 | path | TaintedPath.js:184:29:184:32 | path | +| TaintedPath.js:173:7:173:48 | path | TaintedPath.js:184:29:184:32 | path | +| TaintedPath.js:173:7:173:48 | path | TaintedPath.js:184:29:184:32 | path | +| TaintedPath.js:173:7:173:48 | path | TaintedPath.js:184:29:184:32 | path | +| TaintedPath.js:173:7:173:48 | path | TaintedPath.js:184:29:184:32 | path | +| TaintedPath.js:173:7:173:48 | path | TaintedPath.js:184:29:184:32 | path | +| TaintedPath.js:173:7:173:48 | path | TaintedPath.js:185:29:185:32 | path | +| TaintedPath.js:173:7:173:48 | path | TaintedPath.js:185:29:185:32 | path | +| TaintedPath.js:173:7:173:48 | path | TaintedPath.js:185:29:185:32 | path | +| TaintedPath.js:173:7:173:48 | path | TaintedPath.js:185:29:185:32 | path | +| TaintedPath.js:173:7:173:48 | path | TaintedPath.js:185:29:185:32 | path | +| TaintedPath.js:173:7:173:48 | path | TaintedPath.js:185:29:185:32 | path | +| TaintedPath.js:173:7:173:48 | path | TaintedPath.js:185:29:185:32 | path | +| TaintedPath.js:173:7:173:48 | path | TaintedPath.js:185:29:185:32 | path | +| TaintedPath.js:173:7:173:48 | path | TaintedPath.js:186:29:186:32 | path | +| TaintedPath.js:173:7:173:48 | path | TaintedPath.js:186:29:186:32 | path | +| TaintedPath.js:173:7:173:48 | path | TaintedPath.js:186:29:186:32 | path | +| TaintedPath.js:173:7:173:48 | path | TaintedPath.js:186:29:186:32 | path | +| TaintedPath.js:173:7:173:48 | path | TaintedPath.js:186:29:186:32 | path | +| TaintedPath.js:173:7:173:48 | path | TaintedPath.js:186:29:186:32 | path | +| TaintedPath.js:173:7:173:48 | path | TaintedPath.js:186:29:186:32 | path | +| TaintedPath.js:173:7:173:48 | path | TaintedPath.js:186:29:186:32 | path | +| TaintedPath.js:173:14:173:37 | url.par ... , true) | TaintedPath.js:173:14:173:43 | url.par ... ).query | +| TaintedPath.js:173:14:173:37 | url.par ... , true) | TaintedPath.js:173:14:173:43 | url.par ... ).query | +| TaintedPath.js:173:14:173:37 | url.par ... , true) | TaintedPath.js:173:14:173:43 | url.par ... ).query | +| TaintedPath.js:173:14:173:37 | url.par ... , true) | TaintedPath.js:173:14:173:43 | url.par ... ).query | +| TaintedPath.js:173:14:173:37 | url.par ... , true) | TaintedPath.js:173:14:173:43 | url.par ... ).query | +| TaintedPath.js:173:14:173:37 | url.par ... , true) | TaintedPath.js:173:14:173:43 | url.par ... ).query | +| TaintedPath.js:173:14:173:37 | url.par ... , true) | TaintedPath.js:173:14:173:43 | url.par ... ).query | +| TaintedPath.js:173:14:173:37 | url.par ... , true) | TaintedPath.js:173:14:173:43 | url.par ... ).query | +| TaintedPath.js:173:14:173:37 | url.par ... , true) | TaintedPath.js:173:14:173:43 | url.par ... ).query | +| TaintedPath.js:173:14:173:37 | url.par ... , true) | TaintedPath.js:173:14:173:43 | url.par ... ).query | +| TaintedPath.js:173:14:173:37 | url.par ... , true) | TaintedPath.js:173:14:173:43 | url.par ... ).query | +| TaintedPath.js:173:14:173:37 | url.par ... , true) | TaintedPath.js:173:14:173:43 | url.par ... ).query | +| TaintedPath.js:173:14:173:37 | url.par ... , true) | TaintedPath.js:173:14:173:43 | url.par ... ).query | +| TaintedPath.js:173:14:173:37 | url.par ... , true) | TaintedPath.js:173:14:173:43 | url.par ... ).query | +| TaintedPath.js:173:14:173:37 | url.par ... , true) | TaintedPath.js:173:14:173:43 | url.par ... ).query | +| TaintedPath.js:173:14:173:37 | url.par ... , true) | TaintedPath.js:173:14:173:43 | url.par ... ).query | +| TaintedPath.js:173:14:173:43 | url.par ... ).query | TaintedPath.js:173:14:173:48 | url.par ... ry.path | +| TaintedPath.js:173:14:173:43 | url.par ... ).query | TaintedPath.js:173:14:173:48 | url.par ... ry.path | +| TaintedPath.js:173:14:173:43 | url.par ... ).query | TaintedPath.js:173:14:173:48 | url.par ... ry.path | +| TaintedPath.js:173:14:173:43 | url.par ... ).query | TaintedPath.js:173:14:173:48 | url.par ... ry.path | +| TaintedPath.js:173:14:173:43 | url.par ... ).query | TaintedPath.js:173:14:173:48 | url.par ... ry.path | +| TaintedPath.js:173:14:173:43 | url.par ... ).query | TaintedPath.js:173:14:173:48 | url.par ... ry.path | +| TaintedPath.js:173:14:173:43 | url.par ... ).query | TaintedPath.js:173:14:173:48 | url.par ... ry.path | +| TaintedPath.js:173:14:173:43 | url.par ... ).query | TaintedPath.js:173:14:173:48 | url.par ... ry.path | +| TaintedPath.js:173:14:173:43 | url.par ... ).query | TaintedPath.js:173:14:173:48 | url.par ... ry.path | +| TaintedPath.js:173:14:173:43 | url.par ... ).query | TaintedPath.js:173:14:173:48 | url.par ... ry.path | +| TaintedPath.js:173:14:173:43 | url.par ... ).query | TaintedPath.js:173:14:173:48 | url.par ... ry.path | +| TaintedPath.js:173:14:173:43 | url.par ... ).query | TaintedPath.js:173:14:173:48 | url.par ... ry.path | +| TaintedPath.js:173:14:173:43 | url.par ... ).query | TaintedPath.js:173:14:173:48 | url.par ... ry.path | +| TaintedPath.js:173:14:173:43 | url.par ... ).query | TaintedPath.js:173:14:173:48 | url.par ... ry.path | +| TaintedPath.js:173:14:173:43 | url.par ... ).query | TaintedPath.js:173:14:173:48 | url.par ... ry.path | +| TaintedPath.js:173:14:173:43 | url.par ... ).query | TaintedPath.js:173:14:173:48 | url.par ... ry.path | +| TaintedPath.js:173:14:173:48 | url.par ... ry.path | TaintedPath.js:173:7:173:48 | path | +| TaintedPath.js:173:14:173:48 | url.par ... ry.path | TaintedPath.js:173:7:173:48 | path | +| TaintedPath.js:173:14:173:48 | url.par ... ry.path | TaintedPath.js:173:7:173:48 | path | +| TaintedPath.js:173:14:173:48 | url.par ... ry.path | TaintedPath.js:173:7:173:48 | path | +| TaintedPath.js:173:14:173:48 | url.par ... ry.path | TaintedPath.js:173:7:173:48 | path | +| TaintedPath.js:173:14:173:48 | url.par ... ry.path | TaintedPath.js:173:7:173:48 | path | +| TaintedPath.js:173:14:173:48 | url.par ... ry.path | TaintedPath.js:173:7:173:48 | path | +| TaintedPath.js:173:14:173:48 | url.par ... ry.path | TaintedPath.js:173:7:173:48 | path | +| TaintedPath.js:173:14:173:48 | url.par ... ry.path | TaintedPath.js:173:7:173:48 | path | +| TaintedPath.js:173:14:173:48 | url.par ... ry.path | TaintedPath.js:173:7:173:48 | path | +| TaintedPath.js:173:14:173:48 | url.par ... ry.path | TaintedPath.js:173:7:173:48 | path | +| TaintedPath.js:173:14:173:48 | url.par ... ry.path | TaintedPath.js:173:7:173:48 | path | +| TaintedPath.js:173:14:173:48 | url.par ... ry.path | TaintedPath.js:173:7:173:48 | path | +| TaintedPath.js:173:14:173:48 | url.par ... ry.path | TaintedPath.js:173:7:173:48 | path | +| TaintedPath.js:173:14:173:48 | url.par ... ry.path | TaintedPath.js:173:7:173:48 | path | +| TaintedPath.js:173:14:173:48 | url.par ... ry.path | TaintedPath.js:173:7:173:48 | path | +| TaintedPath.js:173:24:173:30 | req.url | TaintedPath.js:173:14:173:37 | url.par ... , true) | +| TaintedPath.js:173:24:173:30 | req.url | TaintedPath.js:173:14:173:37 | url.par ... , true) | +| TaintedPath.js:173:24:173:30 | req.url | TaintedPath.js:173:14:173:37 | url.par ... , true) | +| TaintedPath.js:173:24:173:30 | req.url | TaintedPath.js:173:14:173:37 | url.par ... , true) | +| TaintedPath.js:173:24:173:30 | req.url | TaintedPath.js:173:14:173:37 | url.par ... , true) | +| TaintedPath.js:173:24:173:30 | req.url | TaintedPath.js:173:14:173:37 | url.par ... , true) | +| TaintedPath.js:173:24:173:30 | req.url | TaintedPath.js:173:14:173:37 | url.par ... , true) | +| TaintedPath.js:173:24:173:30 | req.url | TaintedPath.js:173:14:173:37 | url.par ... , true) | +| TaintedPath.js:173:24:173:30 | req.url | TaintedPath.js:173:14:173:37 | url.par ... , true) | +| TaintedPath.js:173:24:173:30 | req.url | TaintedPath.js:173:14:173:37 | url.par ... , true) | +| TaintedPath.js:173:24:173:30 | req.url | TaintedPath.js:173:14:173:37 | url.par ... , true) | +| TaintedPath.js:173:24:173:30 | req.url | TaintedPath.js:173:14:173:37 | url.par ... , true) | +| TaintedPath.js:173:24:173:30 | req.url | TaintedPath.js:173:14:173:37 | url.par ... , true) | +| TaintedPath.js:173:24:173:30 | req.url | TaintedPath.js:173:14:173:37 | url.par ... , true) | +| TaintedPath.js:173:24:173:30 | req.url | TaintedPath.js:173:14:173:37 | url.par ... , true) | +| TaintedPath.js:173:24:173:30 | req.url | TaintedPath.js:173:14:173:37 | url.par ... , true) | +| TaintedPath.js:173:24:173:30 | req.url | TaintedPath.js:173:14:173:37 | url.par ... , true) | +| TaintedPath.js:173:24:173:30 | req.url | TaintedPath.js:173:14:173:37 | url.par ... , true) | +| TaintedPath.js:173:24:173:30 | req.url | TaintedPath.js:173:14:173:37 | url.par ... , true) | +| TaintedPath.js:173:24:173:30 | req.url | TaintedPath.js:173:14:173:37 | url.par ... , true) | +| TaintedPath.js:173:24:173:30 | req.url | TaintedPath.js:173:14:173:37 | url.par ... , true) | +| TaintedPath.js:173:24:173:30 | req.url | TaintedPath.js:173:14:173:37 | url.par ... , true) | +| TaintedPath.js:173:24:173:30 | req.url | TaintedPath.js:173:14:173:37 | url.par ... , true) | +| TaintedPath.js:173:24:173:30 | req.url | TaintedPath.js:173:14:173:37 | url.par ... , true) | +| TaintedPath.js:173:24:173:30 | req.url | TaintedPath.js:173:14:173:37 | url.par ... , true) | +| TaintedPath.js:173:24:173:30 | req.url | TaintedPath.js:173:14:173:37 | url.par ... , true) | +| TaintedPath.js:173:24:173:30 | req.url | TaintedPath.js:173:14:173:37 | url.par ... , true) | +| TaintedPath.js:173:24:173:30 | req.url | TaintedPath.js:173:14:173:37 | url.par ... , true) | +| TaintedPath.js:173:24:173:30 | req.url | TaintedPath.js:173:14:173:37 | url.par ... , true) | +| TaintedPath.js:173:24:173:30 | req.url | TaintedPath.js:173:14:173:37 | url.par ... , true) | +| TaintedPath.js:173:24:173:30 | req.url | TaintedPath.js:173:14:173:37 | url.par ... , true) | +| TaintedPath.js:173:24:173:30 | req.url | TaintedPath.js:173:14:173:37 | url.par ... , true) | +| TaintedPath.js:177:29:177:32 | path | TaintedPath.js:177:29:177:55 | path.re ... /g, '') | +| TaintedPath.js:177:29:177:32 | path | TaintedPath.js:177:29:177:55 | path.re ... /g, '') | +| TaintedPath.js:177:29:177:32 | path | TaintedPath.js:177:29:177:55 | path.re ... /g, '') | +| TaintedPath.js:177:29:177:32 | path | TaintedPath.js:177:29:177:55 | path.re ... /g, '') | +| TaintedPath.js:177:29:177:32 | path | TaintedPath.js:177:29:177:55 | path.re ... /g, '') | +| TaintedPath.js:177:29:177:32 | path | TaintedPath.js:177:29:177:55 | path.re ... /g, '') | +| TaintedPath.js:177:29:177:32 | path | TaintedPath.js:177:29:177:55 | path.re ... /g, '') | +| TaintedPath.js:177:29:177:32 | path | TaintedPath.js:177:29:177:55 | path.re ... /g, '') | +| TaintedPath.js:177:29:177:32 | path | TaintedPath.js:177:29:177:55 | path.re ... /g, '') | +| TaintedPath.js:177:29:177:32 | path | TaintedPath.js:177:29:177:55 | path.re ... /g, '') | +| TaintedPath.js:177:29:177:32 | path | TaintedPath.js:177:29:177:55 | path.re ... /g, '') | +| TaintedPath.js:177:29:177:32 | path | TaintedPath.js:177:29:177:55 | path.re ... /g, '') | +| TaintedPath.js:177:29:177:32 | path | TaintedPath.js:177:29:177:55 | path.re ... /g, '') | +| TaintedPath.js:177:29:177:32 | path | TaintedPath.js:177:29:177:55 | path.re ... /g, '') | +| TaintedPath.js:177:29:177:32 | path | TaintedPath.js:177:29:177:55 | path.re ... /g, '') | +| TaintedPath.js:177:29:177:32 | path | TaintedPath.js:177:29:177:55 | path.re ... /g, '') | +| TaintedPath.js:177:29:177:32 | path | TaintedPath.js:177:29:177:55 | path.re ... /g, '') | +| TaintedPath.js:177:29:177:32 | path | TaintedPath.js:177:29:177:55 | path.re ... /g, '') | +| TaintedPath.js:177:29:177:32 | path | TaintedPath.js:177:29:177:55 | path.re ... /g, '') | +| TaintedPath.js:177:29:177:32 | path | TaintedPath.js:177:29:177:55 | path.re ... /g, '') | +| TaintedPath.js:177:29:177:32 | path | TaintedPath.js:177:29:177:55 | path.re ... /g, '') | +| TaintedPath.js:177:29:177:32 | path | TaintedPath.js:177:29:177:55 | path.re ... /g, '') | +| TaintedPath.js:177:29:177:32 | path | TaintedPath.js:177:29:177:55 | path.re ... /g, '') | +| TaintedPath.js:177:29:177:32 | path | TaintedPath.js:177:29:177:55 | path.re ... /g, '') | +| TaintedPath.js:177:29:177:32 | path | TaintedPath.js:177:29:177:55 | path.re ... /g, '') | +| TaintedPath.js:177:29:177:32 | path | TaintedPath.js:177:29:177:55 | path.re ... /g, '') | +| TaintedPath.js:177:29:177:32 | path | TaintedPath.js:177:29:177:55 | path.re ... /g, '') | +| TaintedPath.js:177:29:177:32 | path | TaintedPath.js:177:29:177:55 | path.re ... /g, '') | +| TaintedPath.js:177:29:177:32 | path | TaintedPath.js:177:29:177:55 | path.re ... /g, '') | +| TaintedPath.js:177:29:177:32 | path | TaintedPath.js:177:29:177:55 | path.re ... /g, '') | +| TaintedPath.js:177:29:177:32 | path | TaintedPath.js:177:29:177:55 | path.re ... /g, '') | +| TaintedPath.js:177:29:177:32 | path | TaintedPath.js:177:29:177:55 | path.re ... /g, '') | +| TaintedPath.js:183:29:183:32 | path | TaintedPath.js:183:29:183:52 | path.re ... /g, '') | +| TaintedPath.js:183:29:183:32 | path | TaintedPath.js:183:29:183:52 | path.re ... /g, '') | +| TaintedPath.js:183:29:183:32 | path | TaintedPath.js:183:29:183:52 | path.re ... /g, '') | +| TaintedPath.js:183:29:183:32 | path | TaintedPath.js:183:29:183:52 | path.re ... /g, '') | +| TaintedPath.js:183:29:183:32 | path | TaintedPath.js:183:29:183:52 | path.re ... /g, '') | +| TaintedPath.js:183:29:183:32 | path | TaintedPath.js:183:29:183:52 | path.re ... /g, '') | +| TaintedPath.js:183:29:183:32 | path | TaintedPath.js:183:29:183:52 | path.re ... /g, '') | +| TaintedPath.js:183:29:183:32 | path | TaintedPath.js:183:29:183:52 | path.re ... /g, '') | +| TaintedPath.js:183:29:183:32 | path | TaintedPath.js:183:29:183:52 | path.re ... /g, '') | +| TaintedPath.js:183:29:183:32 | path | TaintedPath.js:183:29:183:52 | path.re ... /g, '') | +| TaintedPath.js:183:29:183:32 | path | TaintedPath.js:183:29:183:52 | path.re ... /g, '') | +| TaintedPath.js:183:29:183:32 | path | TaintedPath.js:183:29:183:52 | path.re ... /g, '') | +| TaintedPath.js:183:29:183:32 | path | TaintedPath.js:183:29:183:52 | path.re ... /g, '') | +| TaintedPath.js:183:29:183:32 | path | TaintedPath.js:183:29:183:52 | path.re ... /g, '') | +| TaintedPath.js:183:29:183:32 | path | TaintedPath.js:183:29:183:52 | path.re ... /g, '') | +| TaintedPath.js:183:29:183:32 | path | TaintedPath.js:183:29:183:52 | path.re ... /g, '') | +| TaintedPath.js:184:29:184:32 | path | TaintedPath.js:184:29:184:53 | path.re ... /g, '') | +| TaintedPath.js:184:29:184:32 | path | TaintedPath.js:184:29:184:53 | path.re ... /g, '') | +| TaintedPath.js:184:29:184:32 | path | TaintedPath.js:184:29:184:53 | path.re ... /g, '') | +| TaintedPath.js:184:29:184:32 | path | TaintedPath.js:184:29:184:53 | path.re ... /g, '') | +| TaintedPath.js:184:29:184:32 | path | TaintedPath.js:184:29:184:53 | path.re ... /g, '') | +| TaintedPath.js:184:29:184:32 | path | TaintedPath.js:184:29:184:53 | path.re ... /g, '') | +| TaintedPath.js:184:29:184:32 | path | TaintedPath.js:184:29:184:53 | path.re ... /g, '') | +| TaintedPath.js:184:29:184:32 | path | TaintedPath.js:184:29:184:53 | path.re ... /g, '') | +| TaintedPath.js:184:29:184:32 | path | TaintedPath.js:184:29:184:53 | path.re ... /g, '') | +| TaintedPath.js:184:29:184:32 | path | TaintedPath.js:184:29:184:53 | path.re ... /g, '') | +| TaintedPath.js:184:29:184:32 | path | TaintedPath.js:184:29:184:53 | path.re ... /g, '') | +| TaintedPath.js:184:29:184:32 | path | TaintedPath.js:184:29:184:53 | path.re ... /g, '') | +| TaintedPath.js:184:29:184:32 | path | TaintedPath.js:184:29:184:53 | path.re ... /g, '') | +| TaintedPath.js:184:29:184:32 | path | TaintedPath.js:184:29:184:53 | path.re ... /g, '') | +| TaintedPath.js:184:29:184:32 | path | TaintedPath.js:184:29:184:53 | path.re ... /g, '') | +| TaintedPath.js:184:29:184:32 | path | TaintedPath.js:184:29:184:53 | path.re ... /g, '') | +| TaintedPath.js:185:29:185:32 | path | TaintedPath.js:185:29:185:51 | path.re ... /g, '') | +| TaintedPath.js:185:29:185:32 | path | TaintedPath.js:185:29:185:51 | path.re ... /g, '') | +| TaintedPath.js:185:29:185:32 | path | TaintedPath.js:185:29:185:51 | path.re ... /g, '') | +| TaintedPath.js:185:29:185:32 | path | TaintedPath.js:185:29:185:51 | path.re ... /g, '') | +| TaintedPath.js:185:29:185:32 | path | TaintedPath.js:185:29:185:51 | path.re ... /g, '') | +| TaintedPath.js:185:29:185:32 | path | TaintedPath.js:185:29:185:51 | path.re ... /g, '') | +| TaintedPath.js:185:29:185:32 | path | TaintedPath.js:185:29:185:51 | path.re ... /g, '') | +| TaintedPath.js:185:29:185:32 | path | TaintedPath.js:185:29:185:51 | path.re ... /g, '') | +| TaintedPath.js:185:29:185:32 | path | TaintedPath.js:185:29:185:51 | path.re ... /g, '') | +| TaintedPath.js:185:29:185:32 | path | TaintedPath.js:185:29:185:51 | path.re ... /g, '') | +| TaintedPath.js:185:29:185:32 | path | TaintedPath.js:185:29:185:51 | path.re ... /g, '') | +| TaintedPath.js:185:29:185:32 | path | TaintedPath.js:185:29:185:51 | path.re ... /g, '') | +| TaintedPath.js:185:29:185:32 | path | TaintedPath.js:185:29:185:51 | path.re ... /g, '') | +| TaintedPath.js:185:29:185:32 | path | TaintedPath.js:185:29:185:51 | path.re ... /g, '') | +| TaintedPath.js:185:29:185:32 | path | TaintedPath.js:185:29:185:51 | path.re ... /g, '') | +| TaintedPath.js:185:29:185:32 | path | TaintedPath.js:185:29:185:51 | path.re ... /g, '') | +| TaintedPath.js:186:29:186:32 | path | TaintedPath.js:186:29:186:57 | path.re ... /g, '') | +| TaintedPath.js:186:29:186:32 | path | TaintedPath.js:186:29:186:57 | path.re ... /g, '') | +| TaintedPath.js:186:29:186:32 | path | TaintedPath.js:186:29:186:57 | path.re ... /g, '') | +| TaintedPath.js:186:29:186:32 | path | TaintedPath.js:186:29:186:57 | path.re ... /g, '') | +| TaintedPath.js:186:29:186:32 | path | TaintedPath.js:186:29:186:57 | path.re ... /g, '') | +| TaintedPath.js:186:29:186:32 | path | TaintedPath.js:186:29:186:57 | path.re ... /g, '') | +| TaintedPath.js:186:29:186:32 | path | TaintedPath.js:186:29:186:57 | path.re ... /g, '') | +| TaintedPath.js:186:29:186:32 | path | TaintedPath.js:186:29:186:57 | path.re ... /g, '') | +| TaintedPath.js:186:29:186:32 | path | TaintedPath.js:186:29:186:57 | path.re ... /g, '') | +| TaintedPath.js:186:29:186:32 | path | TaintedPath.js:186:29:186:57 | path.re ... /g, '') | +| TaintedPath.js:186:29:186:32 | path | TaintedPath.js:186:29:186:57 | path.re ... /g, '') | +| TaintedPath.js:186:29:186:32 | path | TaintedPath.js:186:29:186:57 | path.re ... /g, '') | +| TaintedPath.js:186:29:186:32 | path | TaintedPath.js:186:29:186:57 | path.re ... /g, '') | +| TaintedPath.js:186:29:186:32 | path | TaintedPath.js:186:29:186:57 | path.re ... /g, '') | +| TaintedPath.js:186:29:186:32 | path | TaintedPath.js:186:29:186:57 | path.re ... /g, '') | +| TaintedPath.js:186:29:186:32 | path | TaintedPath.js:186:29:186:57 | path.re ... /g, '') | | 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 | @@ -4531,6 +5164,401 @@ edges | 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) | +| normalizedPaths.js:339:6:339:46 | path | normalizedPaths.js:341:18:341:21 | path | +| normalizedPaths.js:339:6:339:46 | path | normalizedPaths.js:341:18:341:21 | path | +| normalizedPaths.js:339:6:339:46 | path | normalizedPaths.js:341:18:341:21 | path | +| normalizedPaths.js:339:6:339:46 | path | normalizedPaths.js:341:18:341:21 | path | +| normalizedPaths.js:339:6:339:46 | path | normalizedPaths.js:341:18:341:21 | path | +| normalizedPaths.js:339:6:339:46 | path | normalizedPaths.js:341:18:341:21 | path | +| normalizedPaths.js:339:6:339:46 | path | normalizedPaths.js:341:18:341:21 | path | +| normalizedPaths.js:339:6:339:46 | path | normalizedPaths.js:341:18:341:21 | path | +| normalizedPaths.js:339:6:339:46 | path | normalizedPaths.js:346:19:346:22 | path | +| normalizedPaths.js:339:6:339:46 | path | normalizedPaths.js:346:19:346:22 | path | +| normalizedPaths.js:339:6:339:46 | path | normalizedPaths.js:346:19:346:22 | path | +| normalizedPaths.js:339:6:339:46 | path | normalizedPaths.js:346:19:346:22 | path | +| normalizedPaths.js:339:6:339:46 | path | normalizedPaths.js:346:19:346:22 | path | +| normalizedPaths.js:339:6:339:46 | path | normalizedPaths.js:346:19:346:22 | path | +| normalizedPaths.js:339:6:339:46 | path | normalizedPaths.js:346:19:346:22 | path | +| normalizedPaths.js:339:6:339:46 | path | normalizedPaths.js:346:19:346:22 | path | +| normalizedPaths.js:339:13:339:46 | pathMod ... y.path) | normalizedPaths.js:339:6:339:46 | path | +| normalizedPaths.js:339:13:339:46 | pathMod ... y.path) | normalizedPaths.js:339:6:339:46 | path | +| normalizedPaths.js:339:13:339:46 | pathMod ... y.path) | normalizedPaths.js:339:6:339:46 | path | +| normalizedPaths.js:339:13:339:46 | pathMod ... y.path) | normalizedPaths.js:339:6:339:46 | path | +| normalizedPaths.js:339:32:339:45 | req.query.path | normalizedPaths.js:339:13:339:46 | pathMod ... y.path) | +| normalizedPaths.js:339:32:339:45 | req.query.path | normalizedPaths.js:339:13:339:46 | pathMod ... y.path) | +| normalizedPaths.js:339:32:339:45 | req.query.path | normalizedPaths.js:339:13:339:46 | pathMod ... y.path) | +| normalizedPaths.js:339:32:339:45 | req.query.path | normalizedPaths.js:339:13:339:46 | pathMod ... y.path) | +| normalizedPaths.js:339:32:339:45 | req.query.path | normalizedPaths.js:339:13:339:46 | pathMod ... y.path) | +| normalizedPaths.js:339:32:339:45 | req.query.path | normalizedPaths.js:339:13:339:46 | pathMod ... y.path) | +| normalizedPaths.js:339:32:339:45 | req.query.path | normalizedPaths.js:339:13:339:46 | pathMod ... y.path) | +| normalizedPaths.js:339:32:339:45 | req.query.path | normalizedPaths.js:339:13:339:46 | pathMod ... y.path) | +| normalizedPaths.js:354:7:354:27 | path | normalizedPaths.js:356:19:356:22 | path | +| normalizedPaths.js:354:7:354:27 | path | normalizedPaths.js:356:19:356:22 | path | +| normalizedPaths.js:354:7:354:27 | path | normalizedPaths.js:356:19:356:22 | path | +| normalizedPaths.js:354:7:354:27 | path | normalizedPaths.js:356:19:356:22 | path | +| normalizedPaths.js:354:7:354:27 | path | normalizedPaths.js:356:19:356:22 | path | +| normalizedPaths.js:354:7:354:27 | path | normalizedPaths.js:356:19:356:22 | path | +| normalizedPaths.js:354:7:354:27 | path | normalizedPaths.js:356:19:356:22 | path | +| normalizedPaths.js:354:7:354:27 | path | normalizedPaths.js:356:19:356:22 | path | +| normalizedPaths.js:354:7:354:27 | path | normalizedPaths.js:358:47:358:50 | path | +| normalizedPaths.js:354:7:354:27 | path | normalizedPaths.js:358:47:358:50 | path | +| normalizedPaths.js:354:7:354:27 | path | normalizedPaths.js:358:47:358:50 | path | +| normalizedPaths.js:354:14:354:27 | req.query.path | normalizedPaths.js:354:7:354:27 | path | +| normalizedPaths.js:354:14:354:27 | req.query.path | normalizedPaths.js:354:7:354:27 | path | +| normalizedPaths.js:354:14:354:27 | req.query.path | normalizedPaths.js:354:7:354:27 | path | +| normalizedPaths.js:354:14:354:27 | req.query.path | normalizedPaths.js:354:7:354:27 | path | +| normalizedPaths.js:354:14:354:27 | req.query.path | normalizedPaths.js:354:7:354:27 | path | +| normalizedPaths.js:354:14:354:27 | req.query.path | normalizedPaths.js:354:7:354:27 | path | +| normalizedPaths.js:354:14:354:27 | req.query.path | normalizedPaths.js:354:7:354:27 | path | +| normalizedPaths.js:354:14:354:27 | req.query.path | normalizedPaths.js:354:7:354:27 | path | +| normalizedPaths.js:358:7:358:51 | requestPath | normalizedPaths.js:363:21:363:31 | requestPath | +| normalizedPaths.js:358:7:358:51 | requestPath | normalizedPaths.js:363:21:363:31 | requestPath | +| normalizedPaths.js:358:7:358:51 | requestPath | normalizedPaths.js:363:21:363:31 | requestPath | +| normalizedPaths.js:358:7:358:51 | requestPath | normalizedPaths.js:363:21:363:31 | requestPath | +| normalizedPaths.js:358:7:358:51 | requestPath | normalizedPaths.js:363:21:363:31 | requestPath | +| normalizedPaths.js:358:7:358:51 | requestPath | normalizedPaths.js:363:21:363:31 | requestPath | +| normalizedPaths.js:358:21:358:51 | pathMod ... , path) | normalizedPaths.js:358:7:358:51 | requestPath | +| normalizedPaths.js:358:21:358:51 | pathMod ... , path) | normalizedPaths.js:358:7:358:51 | requestPath | +| normalizedPaths.js:358:21:358:51 | pathMod ... , path) | normalizedPaths.js:358:7:358:51 | requestPath | +| normalizedPaths.js:358:47:358:50 | path | normalizedPaths.js:358:21:358:51 | pathMod ... , path) | +| normalizedPaths.js:358:47:358:50 | path | normalizedPaths.js:358:21:358:51 | pathMod ... , path) | +| normalizedPaths.js:358:47:358:50 | path | normalizedPaths.js:358:21:358:51 | pathMod ... , path) | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:11:19:11:22 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:11:19:11:22 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:11:19:11:22 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:11:19:11:22 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:11:19:11:22 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:11:19:11:22 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:11:19:11:22 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:11:19:11:22 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:11:19:11:22 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:11:19:11:22 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:11:19:11:22 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:11:19:11:22 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:11:19:11:22 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:11:19:11:22 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:11:19:11:22 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:11:19:11:22 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:11:19:11:22 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:11:19:11:22 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:11:19:11:22 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:11:19:11:22 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:11:19:11:22 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:11:19:11:22 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:11:19:11:22 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:11:19:11:22 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:11:19:11:22 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:11:19:11:22 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:11:19:11:22 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:11:19:11:22 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:11:19:11:22 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:11:19:11:22 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:11:19:11:22 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:11:19:11:22 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:12:27:12:30 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:12:27:12:30 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:12:27:12:30 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:12:27:12:30 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:12:27:12:30 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:12:27:12:30 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:12:27:12:30 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:12:27:12:30 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:12:27:12:30 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:12:27:12:30 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:12:27:12:30 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:12:27:12:30 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:12:27:12:30 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:12:27:12:30 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:12:27:12:30 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:12:27:12:30 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:12:27:12:30 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:12:27:12:30 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:12:27:12:30 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:12:27:12:30 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:12:27:12:30 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:12:27:12:30 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:12:27:12:30 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:12:27:12:30 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:12:27:12:30 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:12:27:12:30 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:12:27:12:30 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:12:27:12:30 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:12:27:12:30 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:12:27:12:30 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:12:27:12:30 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:12:27:12:30 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:13:24:13:27 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:13:24:13:27 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:13:24:13:27 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:13:24:13:27 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:13:24:13:27 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:13:24:13:27 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:13:24:13:27 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:13:24:13:27 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:13:24:13:27 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:13:24:13:27 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:13:24:13:27 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:13:24:13:27 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:13:24:13:27 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:13:24:13:27 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:13:24:13:27 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:13:24:13:27 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:13:24:13:27 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:13:24:13:27 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:13:24:13:27 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:13:24:13:27 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:13:24:13:27 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:13:24:13:27 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:13:24:13:27 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:13:24:13:27 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:13:24:13:27 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:13:24:13:27 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:13:24:13:27 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:13:24:13:27 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:13:24:13:27 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:13:24:13:27 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:13:24:13:27 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:13:24:13:27 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:14:27:14:30 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:14:27:14:30 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:14:27:14:30 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:14:27:14:30 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:14:27:14:30 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:14:27:14:30 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:14:27:14:30 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:14:27:14:30 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:14:27:14:30 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:14:27:14:30 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:14:27:14:30 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:14:27:14:30 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:14:27:14:30 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:14:27:14:30 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:14:27:14:30 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:14:27:14:30 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:14:27:14:30 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:14:27:14:30 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:14:27:14:30 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:14:27:14:30 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:14:27:14:30 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:14:27:14:30 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:14:27:14:30 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:14:27:14:30 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:14:27:14:30 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:14:27:14:30 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:14:27:14:30 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:14:27:14:30 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:14:27:14:30 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:14:27:14:30 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:14:27:14:30 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:14:27:14:30 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:16:34:16:37 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:16:34:16:37 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:16:34:16:37 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:16:34:16:37 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:16:34:16:37 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:16:34:16:37 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:16:34:16:37 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:16:34:16:37 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:16:34:16:37 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:16:34:16:37 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:16:34:16:37 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:16:34:16:37 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:16:34:16:37 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:16:34:16:37 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:16:34:16:37 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:16:34:16:37 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:16:34:16:37 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:16:34:16:37 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:16:34:16:37 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:16:34:16:37 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:16:34:16:37 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:16:34:16:37 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:16:34:16:37 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:16:34:16:37 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:16:34:16:37 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:16:34:16:37 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:16:34:16:37 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:16:34:16:37 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:16:34:16:37 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:16:34:16:37 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:16:34:16:37 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:16:34:16:37 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:17:35:17:38 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:17:35:17:38 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:17:35:17:38 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:17:35:17:38 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:17:35:17:38 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:17:35:17:38 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:17:35:17:38 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:17:35:17:38 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:17:35:17:38 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:17:35:17:38 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:17:35:17:38 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:17:35:17:38 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:17:35:17:38 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:17:35:17:38 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:17:35:17:38 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:17:35:17:38 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:17:35:17:38 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:17:35:17:38 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:17:35:17:38 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:17:35:17:38 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:17:35:17:38 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:17:35:17:38 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:17:35:17:38 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:17:35:17:38 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:17:35:17:38 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:17:35:17:38 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:17:35:17:38 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:17:35:17:38 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:17:35:17:38 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:17:35:17:38 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:17:35:17:38 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:17:35:17:38 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:19:56:19:59 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:19:56:19:59 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:19:56:19:59 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:19:56:19:59 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:19:56:19:59 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:19:56:19:59 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:19:56:19:59 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:19:56:19:59 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:19:56:19:59 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:19:56:19:59 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:19:56:19:59 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:19:56:19:59 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:19:56:19:59 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:19:56:19:59 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:19:56:19:59 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:19:56:19:59 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:19:56:19:59 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:19:56:19:59 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:19:56:19:59 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:19:56:19:59 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:19:56:19:59 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:19:56:19:59 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:19:56:19:59 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:19:56:19:59 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:19:56:19:59 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:19:56:19:59 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:19:56:19:59 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:19:56:19:59 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:19:56:19:59 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:19:56:19:59 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:19:56:19:59 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:19:56:19:59 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:24:35:24:38 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:24:35:24:38 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:24:35:24:38 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:24:35:24:38 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:24:35:24:38 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:24:35:24:38 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:24:35:24:38 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:24:35:24:38 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:24:35:24:38 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:24:35:24:38 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:24:35:24:38 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:24:35:24:38 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:24:35:24:38 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:24:35:24:38 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:24:35:24:38 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:24:35:24:38 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:24:35:24:38 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:24:35:24:38 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:24:35:24:38 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:24:35:24:38 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:24:35:24:38 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:24:35:24:38 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:24:35:24:38 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:24:35:24:38 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:24:35:24:38 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:24:35:24:38 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:24:35:24:38 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:24:35:24:38 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:24:35:24:38 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:24:35:24:38 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:24:35:24:38 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:24:35:24:38 | path | +| other-fs-libraries.js:9:14:9:37 | url.par ... , true) | other-fs-libraries.js:9:14:9:43 | url.par ... ).query | +| other-fs-libraries.js:9:14:9:37 | url.par ... , true) | other-fs-libraries.js:9:14:9:43 | url.par ... ).query | +| other-fs-libraries.js:9:14:9:37 | url.par ... , true) | other-fs-libraries.js:9:14:9:43 | url.par ... ).query | +| other-fs-libraries.js:9:14:9:37 | url.par ... , true) | other-fs-libraries.js:9:14:9:43 | url.par ... ).query | +| other-fs-libraries.js:9:14:9:37 | url.par ... , true) | other-fs-libraries.js:9:14:9:43 | url.par ... ).query | +| other-fs-libraries.js:9:14:9:37 | url.par ... , true) | other-fs-libraries.js:9:14:9:43 | url.par ... ).query | +| other-fs-libraries.js:9:14:9:37 | url.par ... , true) | other-fs-libraries.js:9:14:9:43 | url.par ... ).query | +| other-fs-libraries.js:9:14:9:37 | url.par ... , true) | other-fs-libraries.js:9:14:9:43 | url.par ... ).query | +| other-fs-libraries.js:9:14:9:37 | url.par ... , true) | other-fs-libraries.js:9:14:9:43 | url.par ... ).query | +| other-fs-libraries.js:9:14:9:37 | url.par ... , true) | other-fs-libraries.js:9:14:9:43 | url.par ... ).query | +| other-fs-libraries.js:9:14:9:37 | url.par ... , true) | other-fs-libraries.js:9:14:9:43 | url.par ... ).query | +| other-fs-libraries.js:9:14:9:37 | url.par ... , true) | other-fs-libraries.js:9:14:9:43 | url.par ... ).query | +| other-fs-libraries.js:9:14:9:37 | url.par ... , true) | other-fs-libraries.js:9:14:9:43 | url.par ... ).query | +| other-fs-libraries.js:9:14:9:37 | url.par ... , true) | other-fs-libraries.js:9:14:9:43 | url.par ... ).query | +| other-fs-libraries.js:9:14:9:37 | url.par ... , true) | other-fs-libraries.js:9:14:9:43 | url.par ... ).query | +| other-fs-libraries.js:9:14:9:37 | url.par ... , true) | other-fs-libraries.js:9:14:9:43 | url.par ... ).query | +| other-fs-libraries.js:9:14:9:43 | url.par ... ).query | other-fs-libraries.js:9:14:9:48 | url.par ... ry.path | +| other-fs-libraries.js:9:14:9:43 | url.par ... ).query | other-fs-libraries.js:9:14:9:48 | url.par ... ry.path | +| other-fs-libraries.js:9:14:9:43 | url.par ... ).query | other-fs-libraries.js:9:14:9:48 | url.par ... ry.path | +| other-fs-libraries.js:9:14:9:43 | url.par ... ).query | other-fs-libraries.js:9:14:9:48 | url.par ... ry.path | +| other-fs-libraries.js:9:14:9:43 | url.par ... ).query | other-fs-libraries.js:9:14:9:48 | url.par ... ry.path | +| other-fs-libraries.js:9:14:9:43 | url.par ... ).query | other-fs-libraries.js:9:14:9:48 | url.par ... ry.path | +| other-fs-libraries.js:9:14:9:43 | url.par ... ).query | other-fs-libraries.js:9:14:9:48 | url.par ... ry.path | +| other-fs-libraries.js:9:14:9:43 | url.par ... ).query | other-fs-libraries.js:9:14:9:48 | url.par ... ry.path | +| other-fs-libraries.js:9:14:9:43 | url.par ... ).query | other-fs-libraries.js:9:14:9:48 | url.par ... ry.path | +| other-fs-libraries.js:9:14:9:43 | url.par ... ).query | other-fs-libraries.js:9:14:9:48 | url.par ... ry.path | +| other-fs-libraries.js:9:14:9:43 | url.par ... ).query | other-fs-libraries.js:9:14:9:48 | url.par ... ry.path | +| other-fs-libraries.js:9:14:9:43 | url.par ... ).query | other-fs-libraries.js:9:14:9:48 | url.par ... ry.path | +| other-fs-libraries.js:9:14:9:43 | url.par ... ).query | other-fs-libraries.js:9:14:9:48 | url.par ... ry.path | +| other-fs-libraries.js:9:14:9:43 | url.par ... ).query | other-fs-libraries.js:9:14:9:48 | url.par ... ry.path | +| other-fs-libraries.js:9:14:9:43 | url.par ... ).query | other-fs-libraries.js:9:14:9:48 | url.par ... ry.path | +| other-fs-libraries.js:9:14:9:43 | url.par ... ).query | other-fs-libraries.js:9:14:9:48 | url.par ... ry.path | +| other-fs-libraries.js:9:14:9:48 | url.par ... ry.path | other-fs-libraries.js:9:7:9:48 | path | +| other-fs-libraries.js:9:14:9:48 | url.par ... ry.path | other-fs-libraries.js:9:7:9:48 | path | +| other-fs-libraries.js:9:14:9:48 | url.par ... ry.path | other-fs-libraries.js:9:7:9:48 | path | +| other-fs-libraries.js:9:14:9:48 | url.par ... ry.path | other-fs-libraries.js:9:7:9:48 | path | +| other-fs-libraries.js:9:14:9:48 | url.par ... ry.path | other-fs-libraries.js:9:7:9:48 | path | +| other-fs-libraries.js:9:14:9:48 | url.par ... ry.path | other-fs-libraries.js:9:7:9:48 | path | +| other-fs-libraries.js:9:14:9:48 | url.par ... ry.path | other-fs-libraries.js:9:7:9:48 | path | +| other-fs-libraries.js:9:14:9:48 | url.par ... ry.path | other-fs-libraries.js:9:7:9:48 | path | +| other-fs-libraries.js:9:14:9:48 | url.par ... ry.path | other-fs-libraries.js:9:7:9:48 | path | +| other-fs-libraries.js:9:14:9:48 | url.par ... ry.path | other-fs-libraries.js:9:7:9:48 | path | +| other-fs-libraries.js:9:14:9:48 | url.par ... ry.path | other-fs-libraries.js:9:7:9:48 | path | +| other-fs-libraries.js:9:14:9:48 | url.par ... ry.path | other-fs-libraries.js:9:7:9:48 | path | +| other-fs-libraries.js:9:14:9:48 | url.par ... ry.path | other-fs-libraries.js:9:7:9:48 | path | +| other-fs-libraries.js:9:14:9:48 | url.par ... ry.path | other-fs-libraries.js:9:7:9:48 | path | +| other-fs-libraries.js:9:14:9:48 | url.par ... ry.path | other-fs-libraries.js:9:7:9:48 | path | +| other-fs-libraries.js:9:14:9:48 | url.par ... ry.path | other-fs-libraries.js:9:7:9:48 | path | +| other-fs-libraries.js:9:24:9:30 | req.url | other-fs-libraries.js:9:14:9:37 | url.par ... , true) | +| other-fs-libraries.js:9:24:9:30 | req.url | other-fs-libraries.js:9:14:9:37 | url.par ... , true) | +| other-fs-libraries.js:9:24:9:30 | req.url | other-fs-libraries.js:9:14:9:37 | url.par ... , true) | +| other-fs-libraries.js:9:24:9:30 | req.url | other-fs-libraries.js:9:14:9:37 | url.par ... , true) | +| other-fs-libraries.js:9:24:9:30 | req.url | other-fs-libraries.js:9:14:9:37 | url.par ... , true) | +| other-fs-libraries.js:9:24:9:30 | req.url | other-fs-libraries.js:9:14:9:37 | url.par ... , true) | +| other-fs-libraries.js:9:24:9:30 | req.url | other-fs-libraries.js:9:14:9:37 | url.par ... , true) | +| other-fs-libraries.js:9:24:9:30 | req.url | other-fs-libraries.js:9:14:9:37 | url.par ... , true) | +| other-fs-libraries.js:9:24:9:30 | req.url | other-fs-libraries.js:9:14:9:37 | url.par ... , true) | +| other-fs-libraries.js:9:24:9:30 | req.url | other-fs-libraries.js:9:14:9:37 | url.par ... , true) | +| other-fs-libraries.js:9:24:9:30 | req.url | other-fs-libraries.js:9:14:9:37 | url.par ... , true) | +| other-fs-libraries.js:9:24:9:30 | req.url | other-fs-libraries.js:9:14:9:37 | url.par ... , true) | +| other-fs-libraries.js:9:24:9:30 | req.url | other-fs-libraries.js:9:14:9:37 | url.par ... , true) | +| other-fs-libraries.js:9:24:9:30 | req.url | other-fs-libraries.js:9:14:9:37 | url.par ... , true) | +| other-fs-libraries.js:9:24:9:30 | req.url | other-fs-libraries.js:9:14:9:37 | url.par ... , true) | +| other-fs-libraries.js:9:24:9:30 | req.url | other-fs-libraries.js:9:14:9:37 | url.par ... , true) | +| other-fs-libraries.js:9:24:9:30 | req.url | other-fs-libraries.js:9:14:9:37 | url.par ... , true) | +| other-fs-libraries.js:9:24:9:30 | req.url | other-fs-libraries.js:9:14:9:37 | url.par ... , true) | +| other-fs-libraries.js:9:24:9:30 | req.url | other-fs-libraries.js:9:14:9:37 | url.par ... , true) | +| other-fs-libraries.js:9:24:9:30 | req.url | other-fs-libraries.js:9:14:9:37 | url.par ... , true) | +| other-fs-libraries.js:9:24:9:30 | req.url | other-fs-libraries.js:9:14:9:37 | url.par ... , true) | +| other-fs-libraries.js:9:24:9:30 | req.url | other-fs-libraries.js:9:14:9:37 | url.par ... , true) | +| other-fs-libraries.js:9:24:9:30 | req.url | other-fs-libraries.js:9:14:9:37 | url.par ... , true) | +| other-fs-libraries.js:9:24:9:30 | req.url | other-fs-libraries.js:9:14:9:37 | url.par ... , true) | +| other-fs-libraries.js:9:24:9:30 | req.url | other-fs-libraries.js:9:14:9:37 | url.par ... , true) | +| other-fs-libraries.js:9:24:9:30 | req.url | other-fs-libraries.js:9:14:9:37 | url.par ... , true) | +| other-fs-libraries.js:9:24:9:30 | req.url | other-fs-libraries.js:9:14:9:37 | url.par ... , true) | +| other-fs-libraries.js:9:24:9:30 | req.url | other-fs-libraries.js:9:14:9:37 | url.par ... , true) | +| other-fs-libraries.js:9:24:9:30 | req.url | other-fs-libraries.js:9:14:9:37 | url.par ... , true) | +| other-fs-libraries.js:9:24:9:30 | req.url | other-fs-libraries.js:9:14:9:37 | url.par ... , true) | +| other-fs-libraries.js:9:24:9:30 | req.url | other-fs-libraries.js:9:14:9:37 | url.par ... , true) | +| other-fs-libraries.js:9:24:9:30 | req.url | other-fs-libraries.js:9:14:9:37 | url.par ... , true) | | 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") | @@ -5358,6 +6386,11 @@ edges | 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 | +| TaintedPath.js:177:29:177:55 | path.re ... /g, '') | TaintedPath.js:173:24:173:30 | req.url | TaintedPath.js:177:29:177:55 | path.re ... /g, '') | This path depends on $@. | TaintedPath.js:173:24:173:30 | req.url | a user-provided value | +| TaintedPath.js:183:29:183:52 | path.re ... /g, '') | TaintedPath.js:173:24:173:30 | req.url | TaintedPath.js:183:29:183:52 | path.re ... /g, '') | This path depends on $@. | TaintedPath.js:173:24:173:30 | req.url | a user-provided value | +| TaintedPath.js:184:29:184:53 | path.re ... /g, '') | TaintedPath.js:173:24:173:30 | req.url | TaintedPath.js:184:29:184:53 | path.re ... /g, '') | This path depends on $@. | TaintedPath.js:173:24:173:30 | req.url | a user-provided value | +| TaintedPath.js:185:29:185:51 | path.re ... /g, '') | TaintedPath.js:173:24:173:30 | req.url | TaintedPath.js:185:29:185:51 | path.re ... /g, '') | This path depends on $@. | TaintedPath.js:173:24:173:30 | req.url | a user-provided value | +| TaintedPath.js:186:29:186:57 | path.re ... /g, '') | TaintedPath.js:173:24:173:30 | req.url | TaintedPath.js:186:29:186:57 | path.re ... /g, '') | This path depends on $@. | TaintedPath.js:173:24:173: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 | @@ -5411,6 +6444,18 @@ edges | 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 | +| normalizedPaths.js:341:18:341:21 | path | normalizedPaths.js:339:32:339:45 | req.query.path | normalizedPaths.js:341:18:341:21 | path | This path depends on $@. | normalizedPaths.js:339:32:339:45 | req.query.path | a user-provided value | +| normalizedPaths.js:346:19:346:22 | path | normalizedPaths.js:339:32:339:45 | req.query.path | normalizedPaths.js:346:19:346:22 | path | This path depends on $@. | normalizedPaths.js:339:32:339:45 | req.query.path | a user-provided value | +| normalizedPaths.js:356:19:356:22 | path | normalizedPaths.js:354:14:354:27 | req.query.path | normalizedPaths.js:356:19:356:22 | path | This path depends on $@. | normalizedPaths.js:354:14:354:27 | req.query.path | a user-provided value | +| normalizedPaths.js:363:21:363:31 | requestPath | normalizedPaths.js:354:14:354:27 | req.query.path | normalizedPaths.js:363:21:363:31 | requestPath | This path depends on $@. | normalizedPaths.js:354:14:354:27 | req.query.path | a user-provided value | +| other-fs-libraries.js:11:19:11:22 | path | other-fs-libraries.js:9:24:9:30 | req.url | other-fs-libraries.js:11:19:11:22 | path | This path depends on $@. | other-fs-libraries.js:9:24:9:30 | req.url | a user-provided value | +| other-fs-libraries.js:12:27:12:30 | path | other-fs-libraries.js:9:24:9:30 | req.url | other-fs-libraries.js:12:27:12:30 | path | This path depends on $@. | other-fs-libraries.js:9:24:9:30 | req.url | a user-provided value | +| other-fs-libraries.js:13:24:13:27 | path | other-fs-libraries.js:9:24:9:30 | req.url | other-fs-libraries.js:13:24:13:27 | path | This path depends on $@. | other-fs-libraries.js:9:24:9:30 | req.url | a user-provided value | +| other-fs-libraries.js:14:27:14:30 | path | other-fs-libraries.js:9:24:9:30 | req.url | other-fs-libraries.js:14:27:14:30 | path | This path depends on $@. | other-fs-libraries.js:9:24:9:30 | req.url | a user-provided value | +| other-fs-libraries.js:16:34:16:37 | path | other-fs-libraries.js:9:24:9:30 | req.url | other-fs-libraries.js:16:34:16:37 | path | This path depends on $@. | other-fs-libraries.js:9:24:9:30 | req.url | a user-provided value | +| other-fs-libraries.js:17:35:17:38 | path | other-fs-libraries.js:9:24:9:30 | req.url | other-fs-libraries.js:17:35:17:38 | path | This path depends on $@. | other-fs-libraries.js:9:24:9:30 | req.url | a user-provided value | +| other-fs-libraries.js:19:56:19:59 | path | other-fs-libraries.js:9:24:9:30 | req.url | other-fs-libraries.js:19:56:19:59 | path | This path depends on $@. | other-fs-libraries.js:9:24:9:30 | req.url | a user-provided value | +| other-fs-libraries.js:24:35:24:38 | path | other-fs-libraries.js:9:24:9:30 | req.url | other-fs-libraries.js:24:35:24:38 | path | This path depends on $@. | other-fs-libraries.js:9:24:9:30 | req.url | 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 | 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 df38860cd8a..0ecf680367f 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 @@ -167,4 +167,28 @@ var server = http.createServer(function(req, res) { fs.readFileSync(split.pop()); // NOT OK +}); + +var server = http.createServer(function(req, res) { + let path = url.parse(req.url, true).query.path; + + // Removal of forward-slash or dots. + res.write(fs.readFileSync(path.replace(/[\]\[*,;'"`<>\\?\/]/g, ''))); // OK. + res.write(fs.readFileSync(path.replace(/[abcd]/g, ''))); // NOT OK + res.write(fs.readFileSync(path.replace(/[./]/g, ''))); // OK + res.write(fs.readFileSync(path.replace(/[foobar/foobar]/g, ''))); // OK + res.write(fs.readFileSync(path.replace(/\//g, ''))); // OK + res.write(fs.readFileSync(path.replace(/\.|\//g, ''))); // OK + + res.write(fs.readFileSync(path.replace(/[.]/g, ''))); // NOT OK (can be absolute) + res.write(fs.readFileSync(path.replace(/[..]/g, ''))); // NOT OK (can be absolute) + res.write(fs.readFileSync(path.replace(/\./g, ''))); // NOT OK (can be absolute) + res.write(fs.readFileSync(path.replace(/\.\.|BLA/g, ''))); // NOT OK (can be absolute) + + if (!pathModule.isAbsolute(path)) { + res.write(fs.readFileSync(path.replace(/[.]/g, ''))); // OK + res.write(fs.readFileSync(path.replace(/[..]/g, ''))); // OK + res.write(fs.readFileSync(path.replace(/\./g, ''))); // OK + res.write(fs.readFileSync(path.replace(/\.\.|BLA/g, ''))); // OK + } }); \ No newline at end of file diff --git a/javascript/ql/test/query-tests/Security/CWE-022/TaintedPath/my-fs-module.js b/javascript/ql/test/query-tests/Security/CWE-022/TaintedPath/my-fs-module.js new file mode 100644 index 00000000000..c57c6341428 --- /dev/null +++ b/javascript/ql/test/query-tests/Security/CWE-022/TaintedPath/my-fs-module.js @@ -0,0 +1,7 @@ +exports.require = function(special) { + if (special) { + return require("fs"); + } else { + return require("original-fs"); + } +}; 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 6a7a143f7bd..92a8841470a 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 @@ -334,3 +334,40 @@ app.get('/pseudo-normalizations', (req, res) => { } }); + +app.get('/yet-another-prefix', (req, res) => { + let path = pathModule.resolve(req.query.path); + + fs.readFileSync(path); // NOT OK + + var abs = pathModule.resolve(path); + + if (abs.indexOf(root) !== 0) { + fs.readFileSync(path); // NOT OK + return; + } + fs.readFileSync(path); // OK +}); + +var rootPath = process.cwd(); +app.get('/yet-another-prefix2', (req, res) => { + let path = req.query.path; + + fs.readFileSync(path); // NOT OK + + var requestPath = pathModule.join(rootPath, path); + + var targetPath; + if (!allowPath(requestPath, rootPath)) { + targetPath = rootPath; + fs.readFileSync(requestPath); // NOT OK + } else { + targetPath = requestPath; + fs.readFileSync(requestPath); // OK + } + fs.readFileSync(targetPath); // OK + + function allowPath(requestPath, rootPath) { + return requestPath.indexOf(rootPath) === 0; + } +}); \ No newline at end of file diff --git a/javascript/ql/test/query-tests/Security/CWE-022/TaintedPath/other-fs-libraries.js b/javascript/ql/test/query-tests/Security/CWE-022/TaintedPath/other-fs-libraries.js new file mode 100644 index 00000000000..611aac86f30 --- /dev/null +++ b/javascript/ql/test/query-tests/Security/CWE-022/TaintedPath/other-fs-libraries.js @@ -0,0 +1,33 @@ +var http = require("http"), + url = require("url"), + fs = require("fs"), + gracefulFs = require("graceful-fs"), + fsExtra = require("fs-extra"), + originalFs = require("original-fs"); + +var server = http.createServer(function(req, res) { + var path = url.parse(req.url, true).query.path; + + fs.readFileSync(path); // NOT OK + gracefulFs.readFileSync(path); // NOT OK + fsExtra.readFileSync(path); // NOT OK + originalFs.readFileSync(path); // NOT OK + + getFsModule(true).readFileSync(path); // NOT OK + getFsModule(false).readFileSync(path); // NOT OK + + require("./my-fs-module").require(true).readFileSync(path); // NOT OK + + let flexibleModuleName = require(process.versions["electron"] + ? "original-fs" + : "fs"); + flexibleModuleName.readFileSync(path); // NOT OK +}); + +function getFsModule(special) { + if (special) { + return require("fs"); + } else { + return require("original-fs"); + } +} diff --git a/javascript/ql/test/query-tests/Security/CWE-078/UselessUseOfCat.expected b/javascript/ql/test/query-tests/Security/CWE-078/UselessUseOfCat.expected new file mode 100644 index 00000000000..4f2dae3d7b1 --- /dev/null +++ b/javascript/ql/test/query-tests/Security/CWE-078/UselessUseOfCat.expected @@ -0,0 +1,112 @@ +readFile +| uselesscat.js:10:1:10:43 | exec("c ... ut) {}) | fs.readFile("foo/bar", function(err, out) {...}) | +| uselesscat.js:12:1:14:2 | exec("c ... ut);\\n}) | fs.readFile("/proc/" + id + "/status", function(err, out) {...}) | +| uselesscat.js:16:1:16:29 | execSyn ... uinfo') | fs.readFileSync("/proc/cpuinfo") | +| uselesscat.js:18:1:18:26 | execSyn ... path}`) | fs.readFileSync(newpath) | +| uselesscat.js:32:1:32:34 | execSyn ... path}`) | fs.readFileSync(`foo/bar/${newpath}`) | +| uselesscat.js:34:1:34:54 | execSyn ... utf8'}) | fs.readFileSync(`foo/bar/${newpath}`, {encoding: 'utf8'}) | +| uselesscat.js:51:9:51:31 | execSyn ... + file) | fs.readFileSync(file) | +| uselesscat.js:59:1:62:2 | execFil ... ut);\\n}) | fs.readFile("pom.xml", function(error, stdout, stderr) {...}) | +| uselesscat.js:69:1:72:2 | execFil ... ut);\\n}) | fs.readFile("pom.xml", {encoding: 'utf8'}, function(error, stdout, stderr) {...}) | +| uselesscat.js:74:1:74:60 | execFil ... utf8'}) | fs.readFileSync("pom.xml", {encoding: 'utf8'}) | +| uselesscat.js:76:1:76:39 | execFil ... xml' ]) | fs.readFileSync("pom.xml") | +| uselesscat.js:79:1:79:46 | execFil ... opts) | fs.readFileSync("pom.xml", opts) | +| uselesscat.js:82:1:82:90 | execFil ... String) | fs.readFileSync("pom.xml", anOptsFileNameThatIsTooLongToBePrintedByToString) | +| uselesscat.js:84:1:84:115 | execFil ... ring'}) | fs.readFileSync("pom.xml", ...) | +| uselesscat.js:86:1:86:75 | execFil ... utf8'}) | fs.readFileSync("foo/" + newPath + "bar", {encoding: 'utf8'}) | +| uselesscat.js:88:1:88:35 | execSyn ... + foo) | fs.readFileSync("/proc/cpuinfo" + foo) | +| uselesscat.js:90:1:90:50 | execFil ... th}` ]) | fs.readFileSync(`foo/bar/${newpath}`) | +| uselesscat.js:94:1:94:43 | exec("c ... ut) {}) | fs.readFile("foo/bar", function(err, out) {...}) | +| uselesscat.js:96:1:96:53 | exec("c ... (out)}) | fs.readFile("foo/bar", (err, out) => {...}) | +| uselesscat.js:98:1:98:55 | exec("c ... h(out)) | fs.readFile("foo/bar", (err, out) => ...) | +| uselesscat.js:121:12:121:64 | exec("c ... (out)}) | fs.readFile("foo/bar", (err, out) => {...}) | +| uselesscat.js:127:14:127:66 | exec("c ... (out)}) | fs.readFile("foo/bar", (err, out) => {...}) | +| uselesscat.js:136:17:138:2 | execSyn ... tf8'\\n}) | fs.readFileSync("/etc/dnsmasq.conf", ...) | +| uselesscat.js:146:1:146:61 | shelljs ... (out)}) | fs.readFile("foo/bar", (err, out) => {...}) | +| uselesscat.js:147:1:147:47 | shelljs ... utf8'}) | fs.readFile("foo/bar", {encoding: 'utf8'}) | +| uselesscat.js:148:1:148:81 | shelljs ... (out)}) | fs.readFile("foo/bar", (err, out) => {...}) | +| uselesscat.js:151:1:151:48 | cspawn( ... tf8' }) | fs.readFile("foo/bar", { encoding: 'utf8' }) | +| uselesscat.js:152:1:152:82 | cspawn( ... (out)}) | fs.readFile("foo/bar", (err, out) => {...}) | +| uselesscat.js:153:1:153:60 | cspawn( ... (out)}) | fs.readFile("foo/bar", (err, out) => {...}) | +| uselesscat.js:154:1:154:26 | cspawn( ... /bar']) | fs.readFile("foo/bar") | +| uselesscat.js:158:16:158:46 | cspawn. ... /bar']) | fs.readFileSync("foo/bar") | +| uselesscat.js:159:16:159:68 | cspawn. ... tf8' }) | fs.readFileSync("foo/bar", { encoding: 'utf8' }) | +| uselesscat.js:162:1:162:56 | execmod ... (out)}) | fs.readFile("foo/bar", (err, out) => {...}) | +| uselesscat.js:163:1:163:42 | execmod ... utf8'}) | fs.readFile("foo/bar") | +| uselesscat.js:164:1:164:76 | execmod ... (out)}) | fs.readFile("foo/bar", {encoding: 'utf8'}, (err, out) => {...}) | +syncCommand +| child_process-test.js:9:5:9:22 | cp.execSync("foo") | +| child_process-test.js:11:5:11:26 | cp.exec ... ("foo") | +| child_process-test.js:13:5:13:23 | cp.spawnSync("foo") | +| child_process-test.js:18:5:18:20 | cp.execSync(cmd) | +| child_process-test.js:20:5:20:24 | cp.execFileSync(cmd) | +| child_process-test.js:22:5:22:21 | cp.spawnSync(cmd) | +| command-line-parameter-command-injection.js:11:2:11:21 | cp.execSync(args[0]) | +| command-line-parameter-command-injection.js:12:2:12:33 | cp.exec ... rgs[0]) | +| command-line-parameter-command-injection.js:15:2:15:26 | cp.exec ... rgs[0]) | +| command-line-parameter-command-injection.js:16:2:16:38 | cp.exec ... rgs[0]) | +| command-line-parameter-command-injection.js:19:2:19:18 | cp.execSync(arg0) | +| command-line-parameter-command-injection.js:20:2:20:30 | cp.exec ... + arg0) | +| command-line-parameter-command-injection.js:26:2:26:51 | cp.exec ... tion"`) | +| command-line-parameter-command-injection.js:27:2:27:58 | cp.exec ... tion"`) | +| other.js:7:5:7:36 | require ... nc(cmd) | +| other.js:9:5:9:35 | require ... nc(cmd) | +| other.js:12:5:12:30 | require ... nc(cmd) | +| third-party-command-injection.js:6:9:6:28 | cp.execSync(command) | +| tst_shell-command-injection-from-environment.js:4:2:4:62 | cp.exec ... emp")]) | +| tst_shell-command-injection-from-environment.js:5:2:5:54 | cp.exec ... temp")) | +| uselesscat.js:16:1:16:29 | execSyn ... uinfo') | +| uselesscat.js:18:1:18:26 | execSyn ... path}`) | +| uselesscat.js:20:1:20:36 | execSyn ... wc -l') | +| uselesscat.js:22:1:22:38 | execSyn ... o/bar') | +| uselesscat.js:24:1:24:35 | execSyn ... o/bar`) | +| uselesscat.js:28:1:28:39 | execSyn ... 1000}) | +| uselesscat.js:32:1:32:34 | execSyn ... path}`) | +| uselesscat.js:34:1:34:54 | execSyn ... utf8'}) | +| uselesscat.js:36:1:36:77 | execSyn ... utf8'}) | +| uselesscat.js:38:1:38:43 | execSyn ... r/baz') | +| uselesscat.js:40:1:40:40 | execSyn ... path}`) | +| uselesscat.js:42:1:42:47 | execSyn ... File}`) | +| uselesscat.js:44:1:44:34 | execSyn ... ' ')}`) | +| uselesscat.js:48:1:48:41 | execSyn ... tool}`) | +| uselesscat.js:51:9:51:31 | execSyn ... + file) | +| uselesscat.js:54:1:54:39 | execSyn ... + "'") | +| uselesscat.js:74:1:74:60 | execFil ... utf8'}) | +| uselesscat.js:76:1:76:39 | execFil ... xml' ]) | +| uselesscat.js:79:1:79:46 | execFil ... opts) | +| uselesscat.js:82:1:82:90 | execFil ... String) | +| uselesscat.js:84:1:84:115 | execFil ... ring'}) | +| uselesscat.js:86:1:86:75 | execFil ... utf8'}) | +| uselesscat.js:88:1:88:35 | execSyn ... + foo) | +| uselesscat.js:90:1:90:50 | execFil ... th}` ]) | +| uselesscat.js:92:1:92:46 | execFil ... th}` ]) | +| uselesscat.js:100:1:100:56 | execFil ... ptions) | +| uselesscat.js:104:1:104:31 | execFil ... cat` ]) | +| uselesscat.js:136:17:138:2 | execSyn ... tf8'\\n}) | +| uselesscat.js:158:16:158:46 | cspawn. ... /bar']) | +| uselesscat.js:159:16:159:68 | cspawn. ... tf8' }) | +options +| child_process-test.js:53:5:53:59 | cp.spaw ... cmd])) | child_process-test.js:53:25:53:58 | ['/C', ... , cmd]) | +| child_process-test.js:54:5:54:50 | cp.spaw ... t(cmd)) | child_process-test.js:54:25:54:49 | ['/C', ... at(cmd) | +| child_process-test.js:64:3:64:21 | cp.spawn(cmd, args) | child_process-test.js:64:17:64:20 | args | +| uselesscat.js:28:1:28:39 | execSyn ... 1000}) | uselesscat.js:28:28:28:38 | {uid: 1000} | +| uselesscat.js:30:1:30:64 | exec('c ... t) { }) | uselesscat.js:30:26:30:38 | { cwd: './' } | +| uselesscat.js:34:1:34:54 | execSyn ... utf8'}) | uselesscat.js:34:36:34:53 | {encoding: 'utf8'} | +| uselesscat.js:36:1:36:77 | execSyn ... utf8'}) | uselesscat.js:36:36:36:76 | { uid: ... 'utf8'} | +| uselesscat.js:69:1:72:2 | execFil ... ut);\\n}) | uselesscat.js:69:38:69:55 | {encoding: 'utf8'} | +| uselesscat.js:74:1:74:60 | execFil ... utf8'}) | uselesscat.js:74:42:74:59 | {encoding: 'utf8'} | +| uselesscat.js:79:1:79:46 | execFil ... opts) | uselesscat.js:79:42:79:45 | opts | +| uselesscat.js:82:1:82:90 | execFil ... String) | uselesscat.js:82:42:82:89 | anOptsF ... oString | +| uselesscat.js:84:1:84:115 | execFil ... ring'}) | uselesscat.js:84:42:84:114 | {encodi ... tring'} | +| uselesscat.js:86:1:86:75 | execFil ... utf8'}) | uselesscat.js:86:57:86:74 | {encoding: 'utf8'} | +| uselesscat.js:100:1:100:56 | execFil ... ptions) | uselesscat.js:100:42:100:55 | unknownOptions | +| uselesscat.js:111:1:111:51 | spawn(' ... it'] }) | uselesscat.js:111:14:111:50 | { stdio ... rit'] } | +| uselesscat.js:136:17:138:2 | execSyn ... tf8'\\n}) | uselesscat.js:136:51:138:1 | { // NO ... utf8'\\n} | +| uselesscat.js:147:1:147:47 | shelljs ... utf8'}) | uselesscat.js:147:29:147:46 | {encoding: 'utf8'} | +| uselesscat.js:151:1:151:48 | cspawn( ... tf8' }) | uselesscat.js:151:28:151:47 | { encoding: 'utf8' } | +| uselesscat.js:156:1:156:35 | cspawn( ... tf8' }) | uselesscat.js:156:15:156:34 | { encoding: 'utf8' } | +| uselesscat.js:159:16:159:68 | cspawn. ... tf8' }) | uselesscat.js:159:48:159:67 | { encoding: 'utf8' } | +| uselesscat.js:164:1:164:76 | execmod ... (out)}) | uselesscat.js:164:24:164:41 | {encoding: 'utf8'} | +#select +| False negative | uselesscat.js:54:42:54:69 | // NOT ... lagged] | +| False positive | uselesscat.js:44:37:44:85 | // OK [ ... le read | diff --git a/javascript/ql/test/query-tests/Security/CWE-078/UselessUseOfCat.ql b/javascript/ql/test/query-tests/Security/CWE-078/UselessUseOfCat.ql new file mode 100644 index 00000000000..781125f3206 --- /dev/null +++ b/javascript/ql/test/query-tests/Security/CWE-078/UselessUseOfCat.ql @@ -0,0 +1,24 @@ +import javascript +import semmle.javascript.security.UselessUseOfCat + +from LineComment comment, string msg +where + comment.getFile().getAbsolutePath().regexpMatch(".*/uselesscat.js") and + ( + comment.getText().regexpMatch(".*NOT OK.*") and + not any(UselessCat cat).asExpr().getLocation().getStartLine() = + comment.getLocation().getStartLine() and + msg = "False negative" + or + comment.getText().regexpMatch(".* OK.*") and + not comment.getText().regexpMatch(".*NOT OK.*") and + any(UselessCat cat).asExpr().getLocation().getStartLine() = comment.getLocation().getStartLine() and + msg = "False positive" + ) +select msg, comment + +query string readFile(UselessCat cat) { result = PrettyPrintCatCall::createReadFileCall(cat) } + +query SystemCommandExecution syncCommand() { result.isSync() } + +query DataFlow::Node options(SystemCommandExecution sys) { result = sys.getOptionsArg() } diff --git a/javascript/ql/test/query-tests/Security/CWE-078/uselesscat.js b/javascript/ql/test/query-tests/Security/CWE-078/uselesscat.js new file mode 100644 index 00000000000..ace4784b380 --- /dev/null +++ b/javascript/ql/test/query-tests/Security/CWE-078/uselesscat.js @@ -0,0 +1,166 @@ +var express = require('express'); +var child_process = require('child_process'); +var execSync = child_process.execSync; +var exec = child_process.exec; +var spawn = child_process.spawn; +var spawnSync = child_process.spawnSync; +var fs = require('fs'); +var app = express(); + +exec("cat foo/bar", function (err, out) {}); // NOT OK + +exec("cat /proc/" + id + "/status", function (err, out) { // NOT OK + console.log(out); +}); + +execSync('cat /proc/cpuinfo').toString(); // NOT OK. + +execSync(`cat ${newpath}`) // NOT OK + +execSync('cat package.json | wc -l'); // OK - pipes! + +execSync('cat /proc/cpuinfo /foo/bar').toString(); // OK multiple files. + +execSync(`cat ${newpath} /foo/bar`).toString(); // OK multiple files. + +exec(`cat ${newpath} | grep foo`, function (err, out) { }) // OK - pipes + +execSync(`cat ${newpath}`, {uid: 1000}) // OK - non trivial options + +exec('cat *.js | wc -l', { cwd: './' }, function (err, out) { }); // OK - wildcard and pipes + +execSync(`cat foo/bar/${newpath}`); // NOT OK ("encoding" is used EXACTLY the same way in fs.readFileSync) + +execSync(`cat foo/bar/${newpath}`, {encoding: 'utf8'}); // NOT OK ("encoding" is used EXACTLY the same way in fs.readFileSync) + +execSync("/bin/cat /proc/cpuinfo", { uid: 1000, gid: 1000, encoding: 'utf8'}); // OK (fs.readFileSync cannot emulate uid / gid)) + +execSync('cat /proc/cpuinfo > foo/bar/baz').toString(); // OK. + +execSync(`cat ${newpath} > ${destpath}`).toString(); // OK. + +execSync(`cat ${files.join(' ')} > ${outFile}`); // OK + +execSync(`cat ${files.join(' ')}`); // OK [but flagged] - not just a simple file read + +exec("cat /proc/cpuinfo | grep name"); // OK - pipes + +execSync(`cat ${newpath} | ${othertool}`); // OK - pipes + +function cat(file) { + return execSync('cat ' + file).toString(); // NOT OK +} + +execSync("sh -c 'cat " + newpath + "'"); // NOT OK. [but not flagged] + +var execFile = child_process.execFile; +var execFileSync = child_process.execFileSync; + +execFile('/bin/cat', [ 'pom.xml' ], function(error, stdout, stderr ) { // NOT OK + // Not using stderr + console.log(stdout); +}); + +execFile('/bin/cat', [ 'pom.xml' ], function(error, stdout, stderr ) { // OK. - stderr is used. + console.log(stderr); +}); + + +execFile('/bin/cat', [ 'pom.xml' ], {encoding: 'utf8'}, function(error, stdout, stderr ) { // NOT OK + // Not using stderr + console.log(stdout); +}); + +execFileSync('/bin/cat', [ 'pom.xml' ], {encoding: 'utf8'}); // NOT OK + +execFileSync('/bin/cat', [ 'pom.xml' ]); // NOT OK + +var opts = {encoding: 'utf8'}; +execFileSync('/bin/cat', [ 'pom.xml' ], opts); // NOT OK + +var anOptsFileNameThatIsTooLongToBePrintedByToString = {encoding: 'utf8'}; +execFileSync('/bin/cat', [ 'pom.xml' ], anOptsFileNameThatIsTooLongToBePrintedByToString); // NOT OK + +execFileSync('/bin/cat', [ 'pom.xml' ], {encoding: 'someEncodingValueThatIsCompletelyBogusAndTooLongForToString'}); // NOT OK + +execFileSync('/bin/cat', [ "foo/" + newPath + "bar" ], {encoding: 'utf8'}); // NOT OK + +execSync('cat /proc/cpuinfo' + foo).toString(); // NOT OK. + +execFileSync('/bin/cat', [ `foo/bar/${newpath}` ]); // NOT OK + +execFileSync('node', [ `foo/bar/${newpath}` ]); // OK - not a call to cat + +exec("cat foo/bar", function (err, out) {}); // NOT OK + +exec("cat foo/bar", (err, out) => {console.log(out)}); // NOT OK + +exec("cat foo/bar", (err, out) => doSomethingWith(out)); // NOT OK + +execFileSync('/bin/cat', [ 'pom.xml' ], unknownOptions); // OK - unknown options. + +exec("node foo/bar", (err, out) => doSomethingWith(out)); // OK - Not a call to cat + +execFileSync('node', [ `cat` ]); // OK - not a call to cat + +exec("cat foo/bar&", function (err, out) {}); // OK - contains & +exec("cat foo/bar,", function (err, out) {}); // OK - contains , +exec("cat foo/bar$", function (err, out) {}); // OK - contains $ +exec("cat foo/bar`", function (err, out) {}); // OK - contains ` + +spawn('cat', { stdio: ['pipe', stdin, 'inherit'] }); // OK - Non trivial use. (But weird API use.) + +(function () { + const cat = spawn('cat', [filename]); // OK - non trivial use. + cat.stdout.on('data', (data) => { + res.write(data); + }); + cat.stdout.on('end', () => res.end()); +})(); + +var dead = exec("cat foo/bar", (err, out) => {console.log(out)}); // NOT OK + +var notDead = exec("cat foo/bar", (err, out) => {console.log(out)}); // OK +console.log(notDead); + +(function () { + var dead = exec("cat foo/bar", (err, out) => {console.log(out)}); // NOT OK + + someCall( + exec("cat foo/bar", (err, out) => {console.log(out)}) // OK - non-trivial use of returned proccess. + ); + + return exec("cat foo/bar", (err, out) => {console.log(out)}); // OK - non-trivial use of returned proccess. +})(); + +const stdout2 = execSync('cat /etc/dnsmasq.conf', { // NOT OK. + encoding: 'utf8' +}); + +exec('/bin/cat', function (e, s) {}); // OK + +spawn("cat") // OK + + +var shelljs = require("shelljs"); +shelljs.exec("cat foo/bar", (err, out) => {console.log(out)}); // NOT OK +shelljs.exec("cat foo/bar", {encoding: 'utf8'}); // NOT OK +shelljs.exec("cat foo/bar", {encoding: 'utf8'}, (err, out) => {console.log(out)}); // NOT OK + +let cspawn = require('cross-spawn'); +cspawn('cat', ['foo/bar'], { encoding: 'utf8' }); // NOT OK +cspawn('cat', ['foo/bar'], { encoding: 'utf8' }, (err, out) => {console.log(out)}); // NOT OK +cspawn('cat', ['foo/bar'], (err, out) => {console.log(out)}); // NOT OK +cspawn('cat', ['foo/bar']); // NOT OK +cspawn('cat', (err, out) => {console.log(out)}); // OK +cspawn('cat', { encoding: 'utf8' }); // OK + +let myResult = cspawn.sync('cat', ['foo/bar']); // NOT OK +let myResult = cspawn.sync('cat', ['foo/bar'], { encoding: 'utf8' }); // NOT OK + +var execmod = require('exec'); +execmod("cat foo/bar", (err, out) => {console.log(out)}); // NOT OK +execmod("cat foo/bar", {encoding: 'utf8'}); // NOT OK +execmod("cat foo/bar", {encoding: 'utf8'}, (err, out) => {console.log(out)}); // NOT OK + + \ No newline at end of file diff --git a/javascript/ql/test/query-tests/Security/CWE-079/ReflectedXss.expected b/javascript/ql/test/query-tests/Security/CWE-079/ReflectedXss.expected index e283a5676fa..56a3c4dc6ca 100644 --- a/javascript/ql/test/query-tests/Security/CWE-079/ReflectedXss.expected +++ b/javascript/ql/test/query-tests/Security/CWE-079/ReflectedXss.expected @@ -3,6 +3,22 @@ nodes | ReflectedXss.js:8:14:8:45 | "Unknow ... rams.id | | ReflectedXss.js:8:33:8:45 | req.params.id | | ReflectedXss.js:8:33:8:45 | req.params.id | +| ReflectedXssContentTypes.js:10:14:10:36 | "FOO: " ... rams.id | +| ReflectedXssContentTypes.js:10:14:10:36 | "FOO: " ... rams.id | +| ReflectedXssContentTypes.js:10:24:10:36 | req.params.id | +| ReflectedXssContentTypes.js:10:24:10:36 | req.params.id | +| ReflectedXssContentTypes.js:20:14:20:36 | "FOO: " ... rams.id | +| ReflectedXssContentTypes.js:20:14:20:36 | "FOO: " ... rams.id | +| ReflectedXssContentTypes.js:20:24:20:36 | req.params.id | +| ReflectedXssContentTypes.js:20:24:20:36 | req.params.id | +| ReflectedXssContentTypes.js:39:13:39:35 | "FOO: " ... rams.id | +| ReflectedXssContentTypes.js:39:13:39:35 | "FOO: " ... rams.id | +| ReflectedXssContentTypes.js:39:23:39:35 | req.params.id | +| ReflectedXssContentTypes.js:39:23:39:35 | req.params.id | +| ReflectedXssContentTypes.js:70:12:70:34 | "FOO: " ... rams.id | +| ReflectedXssContentTypes.js:70:12:70:34 | "FOO: " ... rams.id | +| ReflectedXssContentTypes.js:70:22:70:34 | req.params.id | +| ReflectedXssContentTypes.js:70:22:70:34 | req.params.id | | etherpad.js:9:5:9:53 | response | | etherpad.js:9:16:9:30 | req.query.jsonp | | etherpad.js:9:16:9:30 | req.query.jsonp | @@ -75,6 +91,22 @@ edges | ReflectedXss.js:8:33:8:45 | req.params.id | ReflectedXss.js:8:14:8:45 | "Unknow ... rams.id | | ReflectedXss.js:8:33:8:45 | req.params.id | ReflectedXss.js:8:14:8:45 | "Unknow ... rams.id | | ReflectedXss.js:8:33:8:45 | req.params.id | ReflectedXss.js:8:14:8:45 | "Unknow ... rams.id | +| ReflectedXssContentTypes.js:10:24:10:36 | req.params.id | ReflectedXssContentTypes.js:10:14:10:36 | "FOO: " ... rams.id | +| ReflectedXssContentTypes.js:10:24:10:36 | req.params.id | ReflectedXssContentTypes.js:10:14:10:36 | "FOO: " ... rams.id | +| ReflectedXssContentTypes.js:10:24:10:36 | req.params.id | ReflectedXssContentTypes.js:10:14:10:36 | "FOO: " ... rams.id | +| ReflectedXssContentTypes.js:10:24:10:36 | req.params.id | ReflectedXssContentTypes.js:10:14:10:36 | "FOO: " ... rams.id | +| ReflectedXssContentTypes.js:20:24:20:36 | req.params.id | ReflectedXssContentTypes.js:20:14:20:36 | "FOO: " ... rams.id | +| ReflectedXssContentTypes.js:20:24:20:36 | req.params.id | ReflectedXssContentTypes.js:20:14:20:36 | "FOO: " ... rams.id | +| ReflectedXssContentTypes.js:20:24:20:36 | req.params.id | ReflectedXssContentTypes.js:20:14:20:36 | "FOO: " ... rams.id | +| ReflectedXssContentTypes.js:20:24:20:36 | req.params.id | ReflectedXssContentTypes.js:20:14:20:36 | "FOO: " ... rams.id | +| ReflectedXssContentTypes.js:39:23:39:35 | req.params.id | ReflectedXssContentTypes.js:39:13:39:35 | "FOO: " ... rams.id | +| ReflectedXssContentTypes.js:39:23:39:35 | req.params.id | ReflectedXssContentTypes.js:39:13:39:35 | "FOO: " ... rams.id | +| ReflectedXssContentTypes.js:39:23:39:35 | req.params.id | ReflectedXssContentTypes.js:39:13:39:35 | "FOO: " ... rams.id | +| ReflectedXssContentTypes.js:39:23:39:35 | req.params.id | ReflectedXssContentTypes.js:39:13:39:35 | "FOO: " ... rams.id | +| ReflectedXssContentTypes.js:70:22:70:34 | req.params.id | ReflectedXssContentTypes.js:70:12:70:34 | "FOO: " ... rams.id | +| ReflectedXssContentTypes.js:70:22:70:34 | req.params.id | ReflectedXssContentTypes.js:70:12:70:34 | "FOO: " ... rams.id | +| ReflectedXssContentTypes.js:70:22:70:34 | req.params.id | ReflectedXssContentTypes.js:70:12:70:34 | "FOO: " ... rams.id | +| ReflectedXssContentTypes.js:70:22:70:34 | req.params.id | ReflectedXssContentTypes.js:70:12:70:34 | "FOO: " ... rams.id | | etherpad.js:9:5:9:53 | response | etherpad.js:11:12:11:19 | response | | etherpad.js:9:5:9:53 | response | etherpad.js:11:12:11:19 | response | | etherpad.js:9:16:9:30 | req.query.jsonp | etherpad.js:9:16:9:53 | req.que ... e + ")" | @@ -134,6 +166,10 @@ edges | tst2.js:14:9:14:9 | p | tst2.js:14:7:14:24 | p | #select | ReflectedXss.js:8:14:8:45 | "Unknow ... rams.id | ReflectedXss.js:8:33:8:45 | req.params.id | ReflectedXss.js:8:14:8:45 | "Unknow ... rams.id | Cross-site scripting vulnerability due to $@. | ReflectedXss.js:8:33:8:45 | req.params.id | user-provided value | +| ReflectedXssContentTypes.js:10:14:10:36 | "FOO: " ... rams.id | ReflectedXssContentTypes.js:10:24:10:36 | req.params.id | ReflectedXssContentTypes.js:10:14:10:36 | "FOO: " ... rams.id | Cross-site scripting vulnerability due to $@. | ReflectedXssContentTypes.js:10:24:10:36 | req.params.id | user-provided value | +| ReflectedXssContentTypes.js:20:14:20:36 | "FOO: " ... rams.id | ReflectedXssContentTypes.js:20:24:20:36 | req.params.id | ReflectedXssContentTypes.js:20:14:20:36 | "FOO: " ... rams.id | Cross-site scripting vulnerability due to $@. | ReflectedXssContentTypes.js:20:24:20:36 | req.params.id | user-provided value | +| ReflectedXssContentTypes.js:39:13:39:35 | "FOO: " ... rams.id | ReflectedXssContentTypes.js:39:23:39:35 | req.params.id | ReflectedXssContentTypes.js:39:13:39:35 | "FOO: " ... rams.id | Cross-site scripting vulnerability due to $@. | ReflectedXssContentTypes.js:39:23:39:35 | req.params.id | user-provided value | +| ReflectedXssContentTypes.js:70:12:70:34 | "FOO: " ... rams.id | ReflectedXssContentTypes.js:70:22:70:34 | req.params.id | ReflectedXssContentTypes.js:70:12:70:34 | "FOO: " ... rams.id | Cross-site scripting vulnerability due to $@. | ReflectedXssContentTypes.js:70:22:70:34 | req.params.id | user-provided value | | etherpad.js:11:12:11:19 | response | etherpad.js:9:16:9:30 | req.query.jsonp | etherpad.js:11:12:11:19 | response | Cross-site scripting vulnerability due to $@. | etherpad.js:9:16:9:30 | req.query.jsonp | user-provided value | | exception-xss.js:190:12:190:24 | req.params.id | exception-xss.js:190:12:190:24 | req.params.id | exception-xss.js:190:12:190:24 | req.params.id | Cross-site scripting vulnerability due to $@. | exception-xss.js:190:12:190:24 | req.params.id | user-provided value | | formatting.js:6:14:6:47 | util.fo ... , evil) | formatting.js:4:16:4:29 | req.query.evil | formatting.js:6:14:6:47 | util.fo ... , evil) | Cross-site scripting vulnerability due to $@. | formatting.js:4:16:4:29 | req.query.evil | user-provided value | diff --git a/javascript/ql/test/query-tests/Security/CWE-079/ReflectedXssContentTypes.js b/javascript/ql/test/query-tests/Security/CWE-079/ReflectedXssContentTypes.js new file mode 100644 index 00000000000..64acfded0a6 --- /dev/null +++ b/javascript/ql/test/query-tests/Security/CWE-079/ReflectedXssContentTypes.js @@ -0,0 +1,79 @@ +var express = require('express'); +var app = express(); + +app.get('/user/:id', function (req, res) { + if (whatever) { + res.set('Content-Type', 'text/plain'); + res.send("FOO: " + req.params.id); // OK - content type is plain text + } else { + res.set('Content-Type', 'text/html'); + res.send("FOO: " + req.params.id); // NOT OK - content type is HTML. + } +}); + +app.get('/user/:id', function (req, res) { + if (whatever) { + res.writeHead(200, {'Content-Type': 'application/json'}); + res.send("FOO: " + req.params.id); // OK - content type is JSON + } else { + res.writeHead(404); + res.send("FOO: " + req.params.id); // NOT OK - content type is not set. + } +}); + + +app.get('/user/:id', function (req, res) { + res.writeHead(200, {'Content-Type': 'application/json'}); + if (whatever) { + res.send("FOO: " + req.params.id); // OK - content type is JSON + } else { + res.send("FOO: " + req.params.id); // OK - content type is still JSON + } + res.send("FOO: " + req.params.id); // OK - content type is still JSON +}); + + +app.get('/user/:id', function (req, res) { + if (err) { + res.statusCode = 404; + res.end("FOO: " + req.params.id); // NOT OK + } else { + res.setHeader('Content-Type', 'text/plain;charset=utf8'); + res.end("FOO: " + req.params.id); // OK + } +}); + +function textContentType() { + result = "text/plain"; +} + +app.get('/user/:id', function (req, res) { + if (err) { + res.header({'Content-Type': textContentType()}); + res.end("FOO: " + req.params.id); // OK + } else { + res.setHeader('Content-Type', 'text/plain;charset=utf8'); + res.end("FOO: " + req.params.id); // OK + } +}); + +app.get('/user/:id', function (req, res) { + if (err) { + res.writeHead(200, {'Content-Type': 'application/json'}); + res.send("FOO: " + req.params.id); // OK - content type is JSON + return; + } + doSomething(); + somethingMore(); + while(Math.random()) {}; + res.writeHead(404); + res.send("FOO: " + req.params.id); // NOT OK - content type is not set. +}); + +app.get('/user/:id', function (req, res) { + res.header({'Content-Type': textContentType()}); + myFancyFunction(() => { + res.send("FOO: " + req.params.id); // OK + }); + res.end("FOO: " + req.params.id); // OK +}); \ No newline at end of file diff --git a/javascript/ql/test/query-tests/Security/CWE-079/ReflectedXssWithCustomSanitizer.expected b/javascript/ql/test/query-tests/Security/CWE-079/ReflectedXssWithCustomSanitizer.expected index 560dca58553..e8d727f51f8 100644 --- a/javascript/ql/test/query-tests/Security/CWE-079/ReflectedXssWithCustomSanitizer.expected +++ b/javascript/ql/test/query-tests/Security/CWE-079/ReflectedXssWithCustomSanitizer.expected @@ -1,4 +1,8 @@ | ReflectedXss.js:8:14:8:45 | "Unknow ... rams.id | Cross-site scripting vulnerability due to $@. | ReflectedXss.js:8:33:8:45 | req.params.id | user-provided value | +| ReflectedXssContentTypes.js:10:14:10:36 | "FOO: " ... rams.id | Cross-site scripting vulnerability due to $@. | ReflectedXssContentTypes.js:10:24:10:36 | req.params.id | user-provided value | +| ReflectedXssContentTypes.js:20:14:20:36 | "FOO: " ... rams.id | Cross-site scripting vulnerability due to $@. | ReflectedXssContentTypes.js:20:24:20:36 | req.params.id | user-provided value | +| ReflectedXssContentTypes.js:39:13:39:35 | "FOO: " ... rams.id | Cross-site scripting vulnerability due to $@. | ReflectedXssContentTypes.js:39:23:39:35 | req.params.id | user-provided value | +| ReflectedXssContentTypes.js:70:12:70:34 | "FOO: " ... rams.id | Cross-site scripting vulnerability due to $@. | ReflectedXssContentTypes.js:70:22:70:34 | req.params.id | user-provided value | | exception-xss.js:190:12:190:24 | req.params.id | Cross-site scripting vulnerability due to $@. | exception-xss.js:190:12:190:24 | req.params.id | user-provided value | | formatting.js:6:14:6:47 | util.fo ... , evil) | Cross-site scripting vulnerability due to $@. | formatting.js:4:16:4:29 | req.query.evil | user-provided value | | formatting.js:7:14:7:53 | require ... , evil) | Cross-site scripting vulnerability due to $@. | formatting.js:4:16:4:29 | req.query.evil | user-provided value | diff --git a/javascript/ql/test/query-tests/Security/CWE-079/XssWithAdditionalSources.ql b/javascript/ql/test/query-tests/Security/CWE-079/XssWithAdditionalSources.ql index 543c2537253..96dee32082b 100644 --- a/javascript/ql/test/query-tests/Security/CWE-079/XssWithAdditionalSources.ql +++ b/javascript/ql/test/query-tests/Security/CWE-079/XssWithAdditionalSources.ql @@ -16,7 +16,6 @@ import semmle.javascript.security.dataflow.DomBasedXss::DomBasedXss import DataFlow::PathGraph import semmle.javascript.heuristics.AdditionalSources - from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink where cfg.hasFlowPath(source, sink) and source.getNode() instanceof HeuristicSource select sink.getNode(), source, sink, 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 2a40a00d1ef..68da6ad6cae 100644 --- a/javascript/ql/test/query-tests/Security/CWE-400/PrototypePollutionUtility.expected +++ b/javascript/ql/test/query-tests/Security/CWE-400/PrototypePollutionUtility.expected @@ -1,4 +1,85 @@ nodes +| PrototypePollutionUtility/path-assignment.js:8:13:8:25 | key | +| PrototypePollutionUtility/path-assignment.js:8:13:8:25 | key | +| PrototypePollutionUtility/path-assignment.js:8:19:8:25 | keys[i] | +| PrototypePollutionUtility/path-assignment.js:8:19:8:25 | keys[i] | +| PrototypePollutionUtility/path-assignment.js:8:19:8:25 | keys[i] | +| PrototypePollutionUtility/path-assignment.js:13:13:13:32 | target | +| PrototypePollutionUtility/path-assignment.js:13:13:13:32 | target | +| PrototypePollutionUtility/path-assignment.js:13:22:13:27 | target | +| PrototypePollutionUtility/path-assignment.js:13:22:13:27 | target | +| PrototypePollutionUtility/path-assignment.js:13:22:13:32 | target[key] | +| PrototypePollutionUtility/path-assignment.js:13:22:13:32 | target[key] | +| PrototypePollutionUtility/path-assignment.js:13:29:13:31 | key | +| PrototypePollutionUtility/path-assignment.js:13:29:13:31 | key | +| PrototypePollutionUtility/path-assignment.js:15:13:15:18 | target | +| PrototypePollutionUtility/path-assignment.js:15:13:15:18 | target | +| PrototypePollutionUtility/path-assignment.js:15:13:15:18 | target | +| PrototypePollutionUtility/path-assignment.js:15:20:15:22 | key | +| PrototypePollutionUtility/path-assignment.js:15:20:15:22 | key | +| PrototypePollutionUtility/path-assignment.js:15:20:15:22 | key | +| PrototypePollutionUtility/path-assignment.js:41:13:41:25 | key | +| PrototypePollutionUtility/path-assignment.js:41:13:41:25 | key | +| PrototypePollutionUtility/path-assignment.js:41:19:41:25 | keys[i] | +| PrototypePollutionUtility/path-assignment.js:41:19:41:25 | keys[i] | +| PrototypePollutionUtility/path-assignment.js:41:19:41:25 | keys[i] | +| PrototypePollutionUtility/path-assignment.js:42:9:42:48 | target | +| PrototypePollutionUtility/path-assignment.js:42:9:42:48 | target | +| PrototypePollutionUtility/path-assignment.js:42:18:42:23 | target | +| PrototypePollutionUtility/path-assignment.js:42:18:42:23 | target | +| PrototypePollutionUtility/path-assignment.js:42:18:42:23 | target | +| PrototypePollutionUtility/path-assignment.js:42:18:42:48 | target[ ... ] \|\| {} | +| PrototypePollutionUtility/path-assignment.js:42:18:42:48 | target[ ... ] \|\| {} | +| PrototypePollutionUtility/path-assignment.js:42:25:42:27 | key | +| PrototypePollutionUtility/path-assignment.js:42:25:42:27 | key | +| PrototypePollutionUtility/path-assignment.js:42:25:42:27 | key | +| PrototypePollutionUtility/path-assignment.js:42:32:42:37 | target | +| PrototypePollutionUtility/path-assignment.js:42:32:42:37 | target | +| PrototypePollutionUtility/path-assignment.js:42:32:42:42 | target[key] | +| PrototypePollutionUtility/path-assignment.js:42:32:42:42 | target[key] | +| PrototypePollutionUtility/path-assignment.js:42:32:42:48 | target[key] \|\| {} | +| PrototypePollutionUtility/path-assignment.js:42:32:42:48 | target[key] \|\| {} | +| PrototypePollutionUtility/path-assignment.js:42:32:42:48 | target[key] \|\| {} | +| PrototypePollutionUtility/path-assignment.js:42:39:42:41 | key | +| PrototypePollutionUtility/path-assignment.js:42:39:42:41 | key | +| PrototypePollutionUtility/path-assignment.js:44:5:44:10 | target | +| PrototypePollutionUtility/path-assignment.js:44:5:44:10 | target | +| PrototypePollutionUtility/path-assignment.js:44:5:44:10 | target | +| PrototypePollutionUtility/path-assignment.js:44:12:44:18 | keys[i] | +| PrototypePollutionUtility/path-assignment.js:44:12:44:18 | keys[i] | +| PrototypePollutionUtility/path-assignment.js:44:12:44:18 | keys[i] | +| PrototypePollutionUtility/path-assignment.js:44:12:44:18 | keys[i] | +| PrototypePollutionUtility/path-assignment.js:58:13:58:25 | key | +| PrototypePollutionUtility/path-assignment.js:58:13:58:25 | key | +| PrototypePollutionUtility/path-assignment.js:58:19:58:25 | keys[i] | +| PrototypePollutionUtility/path-assignment.js:58:19:58:25 | keys[i] | +| PrototypePollutionUtility/path-assignment.js:58:19:58:25 | keys[i] | +| PrototypePollutionUtility/path-assignment.js:59:9:59:48 | target | +| PrototypePollutionUtility/path-assignment.js:59:9:59:48 | target | +| PrototypePollutionUtility/path-assignment.js:59:18:59:23 | target | +| PrototypePollutionUtility/path-assignment.js:59:18:59:23 | target | +| PrototypePollutionUtility/path-assignment.js:59:18:59:23 | target | +| PrototypePollutionUtility/path-assignment.js:59:18:59:48 | target[ ... ] \|\| {} | +| PrototypePollutionUtility/path-assignment.js:59:18:59:48 | target[ ... ] \|\| {} | +| PrototypePollutionUtility/path-assignment.js:59:25:59:27 | key | +| PrototypePollutionUtility/path-assignment.js:59:25:59:27 | key | +| PrototypePollutionUtility/path-assignment.js:59:25:59:27 | key | +| PrototypePollutionUtility/path-assignment.js:59:32:59:37 | target | +| PrototypePollutionUtility/path-assignment.js:59:32:59:37 | target | +| PrototypePollutionUtility/path-assignment.js:59:32:59:42 | target[key] | +| PrototypePollutionUtility/path-assignment.js:59:32:59:42 | target[key] | +| PrototypePollutionUtility/path-assignment.js:59:32:59:48 | target[key] \|\| {} | +| PrototypePollutionUtility/path-assignment.js:59:32:59:48 | target[key] \|\| {} | +| PrototypePollutionUtility/path-assignment.js:59:32:59:48 | target[key] \|\| {} | +| PrototypePollutionUtility/path-assignment.js:59:39:59:41 | key | +| PrototypePollutionUtility/path-assignment.js:59:39:59:41 | key | +| PrototypePollutionUtility/path-assignment.js:61:5:61:10 | target | +| PrototypePollutionUtility/path-assignment.js:61:5:61:10 | target | +| PrototypePollutionUtility/path-assignment.js:61:5:61:10 | target | +| PrototypePollutionUtility/path-assignment.js:61:12:61:18 | keys[i] | +| PrototypePollutionUtility/path-assignment.js:61:12:61:18 | keys[i] | +| PrototypePollutionUtility/path-assignment.js:61:12:61:18 | keys[i] | +| PrototypePollutionUtility/path-assignment.js:61:12:61:18 | keys[i] | | PrototypePollutionUtility/tests.js:3:25:3:27 | dst | | PrototypePollutionUtility/tests.js:3:25:3:27 | dst | | PrototypePollutionUtility/tests.js:3:30:3:32 | src | @@ -1173,6 +1254,94 @@ nodes | examples/PrototypePollutionUtility_fixed.js:7:28:7:30 | key | | examples/PrototypePollutionUtility_fixed.js:7:28:7:30 | key | edges +| PrototypePollutionUtility/path-assignment.js:8:13:8:25 | key | PrototypePollutionUtility/path-assignment.js:13:29:13:31 | key | +| PrototypePollutionUtility/path-assignment.js:8:13:8:25 | key | PrototypePollutionUtility/path-assignment.js:13:29:13:31 | key | +| PrototypePollutionUtility/path-assignment.js:8:13:8:25 | key | PrototypePollutionUtility/path-assignment.js:15:20:15:22 | key | +| PrototypePollutionUtility/path-assignment.js:8:13:8:25 | key | PrototypePollutionUtility/path-assignment.js:15:20:15:22 | key | +| PrototypePollutionUtility/path-assignment.js:8:13:8:25 | key | PrototypePollutionUtility/path-assignment.js:15:20:15:22 | key | +| PrototypePollutionUtility/path-assignment.js:8:13:8:25 | key | PrototypePollutionUtility/path-assignment.js:15:20:15:22 | key | +| PrototypePollutionUtility/path-assignment.js:8:19:8:25 | keys[i] | PrototypePollutionUtility/path-assignment.js:8:13:8:25 | key | +| PrototypePollutionUtility/path-assignment.js:8:19:8:25 | keys[i] | PrototypePollutionUtility/path-assignment.js:8:13:8:25 | key | +| PrototypePollutionUtility/path-assignment.js:8:19:8:25 | keys[i] | PrototypePollutionUtility/path-assignment.js:8:13:8:25 | key | +| PrototypePollutionUtility/path-assignment.js:8:19:8:25 | keys[i] | PrototypePollutionUtility/path-assignment.js:8:13:8:25 | key | +| PrototypePollutionUtility/path-assignment.js:13:13:13:32 | target | PrototypePollutionUtility/path-assignment.js:13:22:13:27 | target | +| PrototypePollutionUtility/path-assignment.js:13:13:13:32 | target | PrototypePollutionUtility/path-assignment.js:13:22:13:27 | target | +| PrototypePollutionUtility/path-assignment.js:13:13:13:32 | target | PrototypePollutionUtility/path-assignment.js:15:13:15:18 | target | +| PrototypePollutionUtility/path-assignment.js:13:13:13:32 | target | PrototypePollutionUtility/path-assignment.js:15:13:15:18 | target | +| PrototypePollutionUtility/path-assignment.js:13:13:13:32 | target | PrototypePollutionUtility/path-assignment.js:15:13:15:18 | target | +| PrototypePollutionUtility/path-assignment.js:13:13:13:32 | target | PrototypePollutionUtility/path-assignment.js:15:13:15:18 | target | +| PrototypePollutionUtility/path-assignment.js:13:22:13:27 | target | PrototypePollutionUtility/path-assignment.js:13:22:13:32 | target[key] | +| PrototypePollutionUtility/path-assignment.js:13:22:13:27 | target | PrototypePollutionUtility/path-assignment.js:13:22:13:32 | target[key] | +| PrototypePollutionUtility/path-assignment.js:13:22:13:32 | target[key] | PrototypePollutionUtility/path-assignment.js:13:13:13:32 | target | +| PrototypePollutionUtility/path-assignment.js:13:22:13:32 | target[key] | PrototypePollutionUtility/path-assignment.js:13:13:13:32 | target | +| PrototypePollutionUtility/path-assignment.js:13:29:13:31 | key | PrototypePollutionUtility/path-assignment.js:13:22:13:32 | target[key] | +| PrototypePollutionUtility/path-assignment.js:13:29:13:31 | key | PrototypePollutionUtility/path-assignment.js:13:22:13:32 | target[key] | +| PrototypePollutionUtility/path-assignment.js:41:13:41:25 | key | PrototypePollutionUtility/path-assignment.js:42:25:42:27 | key | +| PrototypePollutionUtility/path-assignment.js:41:13:41:25 | key | PrototypePollutionUtility/path-assignment.js:42:25:42:27 | key | +| PrototypePollutionUtility/path-assignment.js:41:13:41:25 | key | PrototypePollutionUtility/path-assignment.js:42:25:42:27 | key | +| PrototypePollutionUtility/path-assignment.js:41:13:41:25 | key | PrototypePollutionUtility/path-assignment.js:42:25:42:27 | key | +| PrototypePollutionUtility/path-assignment.js:41:13:41:25 | key | PrototypePollutionUtility/path-assignment.js:42:39:42:41 | key | +| PrototypePollutionUtility/path-assignment.js:41:13:41:25 | key | PrototypePollutionUtility/path-assignment.js:42:39:42:41 | key | +| PrototypePollutionUtility/path-assignment.js:41:19:41:25 | keys[i] | PrototypePollutionUtility/path-assignment.js:41:13:41:25 | key | +| PrototypePollutionUtility/path-assignment.js:41:19:41:25 | keys[i] | PrototypePollutionUtility/path-assignment.js:41:13:41:25 | key | +| PrototypePollutionUtility/path-assignment.js:41:19:41:25 | keys[i] | PrototypePollutionUtility/path-assignment.js:41:13:41:25 | key | +| PrototypePollutionUtility/path-assignment.js:41:19:41:25 | keys[i] | PrototypePollutionUtility/path-assignment.js:41:13:41:25 | key | +| PrototypePollutionUtility/path-assignment.js:42:9:42:48 | target | PrototypePollutionUtility/path-assignment.js:42:18:42:23 | target | +| PrototypePollutionUtility/path-assignment.js:42:9:42:48 | target | PrototypePollutionUtility/path-assignment.js:42:18:42:23 | target | +| PrototypePollutionUtility/path-assignment.js:42:9:42:48 | target | PrototypePollutionUtility/path-assignment.js:42:18:42:23 | target | +| PrototypePollutionUtility/path-assignment.js:42:9:42:48 | target | PrototypePollutionUtility/path-assignment.js:42:18:42:23 | target | +| PrototypePollutionUtility/path-assignment.js:42:9:42:48 | target | PrototypePollutionUtility/path-assignment.js:42:32:42:37 | target | +| PrototypePollutionUtility/path-assignment.js:42:9:42:48 | target | PrototypePollutionUtility/path-assignment.js:42:32:42:37 | target | +| PrototypePollutionUtility/path-assignment.js:42:9:42:48 | target | PrototypePollutionUtility/path-assignment.js:44:5:44:10 | target | +| PrototypePollutionUtility/path-assignment.js:42:9:42:48 | target | PrototypePollutionUtility/path-assignment.js:44:5:44:10 | target | +| PrototypePollutionUtility/path-assignment.js:42:9:42:48 | target | PrototypePollutionUtility/path-assignment.js:44:5:44:10 | target | +| PrototypePollutionUtility/path-assignment.js:42:9:42:48 | target | PrototypePollutionUtility/path-assignment.js:44:5:44:10 | target | +| PrototypePollutionUtility/path-assignment.js:42:18:42:48 | target[ ... ] \|\| {} | PrototypePollutionUtility/path-assignment.js:42:9:42:48 | target | +| PrototypePollutionUtility/path-assignment.js:42:18:42:48 | target[ ... ] \|\| {} | PrototypePollutionUtility/path-assignment.js:42:9:42:48 | target | +| PrototypePollutionUtility/path-assignment.js:42:32:42:37 | target | PrototypePollutionUtility/path-assignment.js:42:32:42:42 | target[key] | +| PrototypePollutionUtility/path-assignment.js:42:32:42:37 | target | PrototypePollutionUtility/path-assignment.js:42:32:42:42 | target[key] | +| PrototypePollutionUtility/path-assignment.js:42:32:42:42 | target[key] | PrototypePollutionUtility/path-assignment.js:42:32:42:48 | target[key] \|\| {} | +| PrototypePollutionUtility/path-assignment.js:42:32:42:42 | target[key] | PrototypePollutionUtility/path-assignment.js:42:32:42:48 | target[key] \|\| {} | +| PrototypePollutionUtility/path-assignment.js:42:32:42:42 | target[key] | PrototypePollutionUtility/path-assignment.js:42:32:42:48 | target[key] \|\| {} | +| PrototypePollutionUtility/path-assignment.js:42:32:42:42 | target[key] | PrototypePollutionUtility/path-assignment.js:42:32:42:48 | target[key] \|\| {} | +| PrototypePollutionUtility/path-assignment.js:42:32:42:48 | target[key] \|\| {} | PrototypePollutionUtility/path-assignment.js:42:18:42:48 | target[ ... ] \|\| {} | +| PrototypePollutionUtility/path-assignment.js:42:32:42:48 | target[key] \|\| {} | PrototypePollutionUtility/path-assignment.js:42:18:42:48 | target[ ... ] \|\| {} | +| PrototypePollutionUtility/path-assignment.js:42:39:42:41 | key | PrototypePollutionUtility/path-assignment.js:42:32:42:42 | target[key] | +| PrototypePollutionUtility/path-assignment.js:42:39:42:41 | key | PrototypePollutionUtility/path-assignment.js:42:32:42:42 | target[key] | +| PrototypePollutionUtility/path-assignment.js:44:12:44:18 | keys[i] | PrototypePollutionUtility/path-assignment.js:44:12:44:18 | keys[i] | +| PrototypePollutionUtility/path-assignment.js:58:13:58:25 | key | PrototypePollutionUtility/path-assignment.js:59:25:59:27 | key | +| PrototypePollutionUtility/path-assignment.js:58:13:58:25 | key | PrototypePollutionUtility/path-assignment.js:59:25:59:27 | key | +| PrototypePollutionUtility/path-assignment.js:58:13:58:25 | key | PrototypePollutionUtility/path-assignment.js:59:25:59:27 | key | +| PrototypePollutionUtility/path-assignment.js:58:13:58:25 | key | PrototypePollutionUtility/path-assignment.js:59:25:59:27 | key | +| PrototypePollutionUtility/path-assignment.js:58:13:58:25 | key | PrototypePollutionUtility/path-assignment.js:59:39:59:41 | key | +| PrototypePollutionUtility/path-assignment.js:58:13:58:25 | key | PrototypePollutionUtility/path-assignment.js:59:39:59:41 | key | +| PrototypePollutionUtility/path-assignment.js:58:19:58:25 | keys[i] | PrototypePollutionUtility/path-assignment.js:58:13:58:25 | key | +| PrototypePollutionUtility/path-assignment.js:58:19:58:25 | keys[i] | PrototypePollutionUtility/path-assignment.js:58:13:58:25 | key | +| PrototypePollutionUtility/path-assignment.js:58:19:58:25 | keys[i] | PrototypePollutionUtility/path-assignment.js:58:13:58:25 | key | +| PrototypePollutionUtility/path-assignment.js:58:19:58:25 | keys[i] | PrototypePollutionUtility/path-assignment.js:58:13:58:25 | key | +| PrototypePollutionUtility/path-assignment.js:59:9:59:48 | target | PrototypePollutionUtility/path-assignment.js:59:18:59:23 | target | +| PrototypePollutionUtility/path-assignment.js:59:9:59:48 | target | PrototypePollutionUtility/path-assignment.js:59:18:59:23 | target | +| PrototypePollutionUtility/path-assignment.js:59:9:59:48 | target | PrototypePollutionUtility/path-assignment.js:59:18:59:23 | target | +| PrototypePollutionUtility/path-assignment.js:59:9:59:48 | target | PrototypePollutionUtility/path-assignment.js:59:18:59:23 | target | +| PrototypePollutionUtility/path-assignment.js:59:9:59:48 | target | PrototypePollutionUtility/path-assignment.js:59:32:59:37 | target | +| PrototypePollutionUtility/path-assignment.js:59:9:59:48 | target | PrototypePollutionUtility/path-assignment.js:59:32:59:37 | target | +| PrototypePollutionUtility/path-assignment.js:59:9:59:48 | target | PrototypePollutionUtility/path-assignment.js:61:5:61:10 | target | +| PrototypePollutionUtility/path-assignment.js:59:9:59:48 | target | PrototypePollutionUtility/path-assignment.js:61:5:61:10 | target | +| PrototypePollutionUtility/path-assignment.js:59:9:59:48 | target | PrototypePollutionUtility/path-assignment.js:61:5:61:10 | target | +| PrototypePollutionUtility/path-assignment.js:59:9:59:48 | target | PrototypePollutionUtility/path-assignment.js:61:5:61:10 | target | +| PrototypePollutionUtility/path-assignment.js:59:18:59:48 | target[ ... ] \|\| {} | PrototypePollutionUtility/path-assignment.js:59:9:59:48 | target | +| PrototypePollutionUtility/path-assignment.js:59:18:59:48 | target[ ... ] \|\| {} | PrototypePollutionUtility/path-assignment.js:59:9:59:48 | target | +| PrototypePollutionUtility/path-assignment.js:59:32:59:37 | target | PrototypePollutionUtility/path-assignment.js:59:32:59:42 | target[key] | +| PrototypePollutionUtility/path-assignment.js:59:32:59:37 | target | PrototypePollutionUtility/path-assignment.js:59:32:59:42 | target[key] | +| PrototypePollutionUtility/path-assignment.js:59:32:59:42 | target[key] | PrototypePollutionUtility/path-assignment.js:59:32:59:48 | target[key] \|\| {} | +| PrototypePollutionUtility/path-assignment.js:59:32:59:42 | target[key] | PrototypePollutionUtility/path-assignment.js:59:32:59:48 | target[key] \|\| {} | +| PrototypePollutionUtility/path-assignment.js:59:32:59:42 | target[key] | PrototypePollutionUtility/path-assignment.js:59:32:59:48 | target[key] \|\| {} | +| PrototypePollutionUtility/path-assignment.js:59:32:59:42 | target[key] | PrototypePollutionUtility/path-assignment.js:59:32:59:48 | target[key] \|\| {} | +| PrototypePollutionUtility/path-assignment.js:59:32:59:48 | target[key] \|\| {} | PrototypePollutionUtility/path-assignment.js:59:18:59:48 | target[ ... ] \|\| {} | +| PrototypePollutionUtility/path-assignment.js:59:32:59:48 | target[key] \|\| {} | PrototypePollutionUtility/path-assignment.js:59:18:59:48 | target[ ... ] \|\| {} | +| PrototypePollutionUtility/path-assignment.js:59:39:59:41 | key | PrototypePollutionUtility/path-assignment.js:59:32:59:42 | target[key] | +| PrototypePollutionUtility/path-assignment.js:59:39:59:41 | key | PrototypePollutionUtility/path-assignment.js:59:32:59:42 | target[key] | +| PrototypePollutionUtility/path-assignment.js:61:12:61:18 | keys[i] | PrototypePollutionUtility/path-assignment.js:61:12:61:18 | keys[i] | | PrototypePollutionUtility/tests.js:3:25:3:27 | dst | PrototypePollutionUtility/tests.js:6:28:6:30 | dst | | PrototypePollutionUtility/tests.js:3:25:3:27 | dst | PrototypePollutionUtility/tests.js:6:28:6:30 | dst | | PrototypePollutionUtility/tests.js:3:25:3:27 | dst | PrototypePollutionUtility/tests.js:8:13:8:15 | dst | @@ -2669,6 +2838,9 @@ edges | examples/PrototypePollutionUtility_fixed.js:7:28:7:30 | key | examples/PrototypePollutionUtility_fixed.js:7:24:7:31 | src[key] | | examples/PrototypePollutionUtility_fixed.js:7:28:7:30 | key | examples/PrototypePollutionUtility_fixed.js:7:24:7:31 | src[key] | #select +| PrototypePollutionUtility/path-assignment.js:15:13:15:18 | target | PrototypePollutionUtility/path-assignment.js:8:19:8:25 | keys[i] | PrototypePollutionUtility/path-assignment.js:15:13:15:18 | target | The property chain $@ is recursively assigned to $@ without guarding against prototype pollution. | PrototypePollutionUtility/path-assignment.js:8:19:8:25 | keys[i] | here | PrototypePollutionUtility/path-assignment.js:15:13:15:18 | target | target | +| PrototypePollutionUtility/path-assignment.js:44:5:44:10 | target | PrototypePollutionUtility/path-assignment.js:41:19:41:25 | keys[i] | PrototypePollutionUtility/path-assignment.js:44:5:44:10 | target | The property chain $@ is recursively assigned to $@ without guarding against prototype pollution. | PrototypePollutionUtility/path-assignment.js:41:19:41:25 | keys[i] | here | PrototypePollutionUtility/path-assignment.js:44:5:44:10 | target | target | +| PrototypePollutionUtility/path-assignment.js:61:5:61:10 | target | PrototypePollutionUtility/path-assignment.js:58:19:58:25 | keys[i] | PrototypePollutionUtility/path-assignment.js:61:5:61:10 | target | The property chain $@ is recursively assigned to $@ without guarding against prototype pollution. | PrototypePollutionUtility/path-assignment.js:58:19:58:25 | keys[i] | here | PrototypePollutionUtility/path-assignment.js:61:5:61:10 | target | target | | PrototypePollutionUtility/tests.js:8:13:8:15 | dst | PrototypePollutionUtility/tests.js:4:14:4:16 | key | PrototypePollutionUtility/tests.js:8:13:8:15 | dst | Properties are copied from $@ to $@ without guarding against prototype pollution. | PrototypePollutionUtility/tests.js:4:21:4:23 | src | src | PrototypePollutionUtility/tests.js:8:13:8:15 | dst | dst | | PrototypePollutionUtility/tests.js:18:13:18:15 | dst | PrototypePollutionUtility/tests.js:14:30:14:32 | key | PrototypePollutionUtility/tests.js:18:13:18:15 | dst | Properties are copied from $@ to $@ without guarding against prototype pollution. | PrototypePollutionUtility/tests.js:14:17:14:19 | src | src | PrototypePollutionUtility/tests.js:18:13:18:15 | dst | dst | | PrototypePollutionUtility/tests.js:36:9:36:11 | dst | PrototypePollutionUtility/tests.js:25:18:25:20 | key | PrototypePollutionUtility/tests.js:36:9:36:11 | dst | Properties are copied from $@ to $@ without guarding against prototype pollution. | PrototypePollutionUtility/tests.js:25:25:25:30 | source | source | PrototypePollutionUtility/tests.js:36:9:36:11 | dst | dst | @@ -2677,7 +2849,7 @@ edges | PrototypePollutionUtility/tests.js:109:13:109:15 | dst | PrototypePollutionUtility/tests.js:102:14:102:16 | key | PrototypePollutionUtility/tests.js:109:13:109:15 | dst | Properties are copied from $@ to $@ without guarding against prototype pollution. | PrototypePollutionUtility/tests.js:102:21:102:23 | src | src | PrototypePollutionUtility/tests.js:109:13:109:15 | dst | dst | | PrototypePollutionUtility/tests.js:154:13:154:15 | dst | PrototypePollutionUtility/tests.js:150:14:150:16 | key | PrototypePollutionUtility/tests.js:154:13:154:15 | dst | Properties are copied from $@ to $@ without guarding against prototype pollution. | PrototypePollutionUtility/tests.js:150:21:150:23 | src | src | PrototypePollutionUtility/tests.js:154:13:154:15 | dst | dst | | PrototypePollutionUtility/tests.js:196:13:196:15 | dst | PrototypePollutionUtility/tests.js:192:19:192:25 | keys[i] | PrototypePollutionUtility/tests.js:196:13:196:15 | dst | Properties are copied from $@ to $@ without guarding against prototype pollution. | PrototypePollutionUtility/tests.js:190:28:190:30 | src | src | PrototypePollutionUtility/tests.js:196:13:196:15 | dst | dst | -| PrototypePollutionUtility/tests.js:233:5:233:13 | map[key1] | PrototypePollutionUtility/tests.js:238:14:238:16 | key | PrototypePollutionUtility/tests.js:233:5:233:13 | map[key1] | Properties are copied from $@ to $@ without guarding against prototype pollution. | PrototypePollutionUtility/tests.js:238:21:238:24 | data | data | PrototypePollutionUtility/tests.js:233:5:233:13 | map[key1] | this object | +| PrototypePollutionUtility/tests.js:233:5:233:13 | map[key1] | PrototypePollutionUtility/tests.js:238:14:238:16 | key | PrototypePollutionUtility/tests.js:233:5:233:13 | map[key1] | Properties are copied from $@ to $@ without guarding against prototype pollution. | PrototypePollutionUtility/tests.js:238:21:238:24 | data | data | PrototypePollutionUtility/tests.js:233:5:233:13 | map[key1] | here | | PrototypePollutionUtility/tests.js:270:13:270:15 | dst | PrototypePollutionUtility/tests.js:265:19:265:26 | entry[0] | PrototypePollutionUtility/tests.js:270:13:270:15 | dst | Properties are copied from $@ to $@ without guarding against prototype pollution. | PrototypePollutionUtility/tests.js:264:20:264:22 | src | src | PrototypePollutionUtility/tests.js:270:13:270:15 | dst | dst | | PrototypePollutionUtility/tests.js:280:13:280:15 | dst | PrototypePollutionUtility/tests.js:276:34:276:36 | key | PrototypePollutionUtility/tests.js:280:13:280:15 | dst | Properties are copied from $@ to $@ without guarding against prototype pollution. | PrototypePollutionUtility/tests.js:276:21:276:23 | src | src | PrototypePollutionUtility/tests.js:280:13:280:15 | dst | dst | | PrototypePollutionUtility/tests.js:308:17:308:19 | dst | PrototypePollutionUtility/tests.js:302:14:302:16 | key | PrototypePollutionUtility/tests.js:308:17:308:19 | dst | Properties are copied from $@ to $@ without guarding against prototype pollution. | PrototypePollutionUtility/tests.js:302:21:302:23 | src | src | PrototypePollutionUtility/tests.js:308:17:308:19 | dst | dst | diff --git a/javascript/ql/test/query-tests/Security/CWE-400/PrototypePollutionUtility/path-assignment.js b/javascript/ql/test/query-tests/Security/CWE-400/PrototypePollutionUtility/path-assignment.js new file mode 100644 index 00000000000..0cc279f0ed5 --- /dev/null +++ b/javascript/ql/test/query-tests/Security/CWE-400/PrototypePollutionUtility/path-assignment.js @@ -0,0 +1,62 @@ +function isSafe(key) { + return key !== "__proto__" && key !== "constructor" && key !== "prototype"; +} + +function assignToPath(target, path, value) { + let keys = path.split('.'); + for (let i = 0; i < keys.length; ++i) { + let key = keys[i]; + if (i < keys.length - 1) { + if (!target[key]) { + target[key] = {}; + } + target = target[key]; + } else { + target[key] = value; // NOT OK + } + } +} + +function assignToPathSafe(target, path, value) { + let keys = path.split('.'); + for (let i = 0; i < keys.length; ++i) { + let key = keys[i]; + if (!isSafe(key)) return; + if (i < keys.length - 1) { + if (!target[key]) { + target[key] = {}; + } + target = target[key]; + } else { + target[key] = value; // OK + } + } +} + + +function assignToPathAfterLoop(target, path, value) { + let keys = path.split('.'); + let i; + for (i = 0; i < keys.length - 1; ++i) { + let key = keys[i]; + target = target[key] = target[key] || {}; + } + target[keys[i]] = value; // NOT OK +} + +function splitHelper(path, sep) { + let parts = typeof path === 'string' ? path.split(sep || '.') : path; + let result = []; + result.push(...parts); + return result; +} + +function assignToPathWithHelper(target, path, value, sep) { + let keys = splitHelper(path, sep) + let i; + for (i = 0; i < keys.length - 1; ++i) { + let key = keys[i]; + target = target[key] = target[key] || {}; + } + target[keys[i]] = value; // NOT OK +} diff --git a/javascript/ql/test/query-tests/Security/Summaries/ExtractFlowStepSummaries.qlref b/javascript/ql/test/query-tests/Security/Summaries/ExtractFlowStepSummaries.qlref index 8491bf549de..da6baf2b26d 100644 --- a/javascript/ql/test/query-tests/Security/Summaries/ExtractFlowStepSummaries.qlref +++ b/javascript/ql/test/query-tests/Security/Summaries/ExtractFlowStepSummaries.qlref @@ -1 +1 @@ -Security/Summaries/ExtractFlowStepSummaries.ql +experimental/Summaries/ExtractFlowStepSummaries.ql diff --git a/javascript/ql/test/query-tests/Security/Summaries/ExtractSinkSummaries.qlref b/javascript/ql/test/query-tests/Security/Summaries/ExtractSinkSummaries.qlref index 9b5d6745f2c..90b31eefa80 100644 --- a/javascript/ql/test/query-tests/Security/Summaries/ExtractSinkSummaries.qlref +++ b/javascript/ql/test/query-tests/Security/Summaries/ExtractSinkSummaries.qlref @@ -1 +1 @@ -Security/Summaries/ExtractSinkSummaries.ql +experimental/Summaries/ExtractSinkSummaries.ql diff --git a/javascript/ql/test/query-tests/Security/Summaries/ExtractSourceSummaries.qlref b/javascript/ql/test/query-tests/Security/Summaries/ExtractSourceSummaries.qlref index 36bf7bd8369..26661a51f11 100644 --- a/javascript/ql/test/query-tests/Security/Summaries/ExtractSourceSummaries.qlref +++ b/javascript/ql/test/query-tests/Security/Summaries/ExtractSourceSummaries.qlref @@ -1 +1 @@ -Security/Summaries/ExtractSourceSummaries.ql +experimental/Summaries/ExtractSourceSummaries.ql diff --git a/python/ql/src/Exceptions/CatchingBaseException.ql b/python/ql/src/Exceptions/CatchingBaseException.ql index 4d5be501ecf..d175297f1c9 100644 --- a/python/ql/src/Exceptions/CatchingBaseException.ql +++ b/python/ql/src/Exceptions/CatchingBaseException.ql @@ -19,7 +19,7 @@ predicate doesnt_reraise(ExceptStmt ex) { } predicate catches_base_exception(ExceptStmt ex) { - ex.getType().refersTo(theBaseExceptionType()) + ex.getType().pointsTo(ClassValue::baseException()) or not exists(ex.getType()) } diff --git a/python/ql/src/Exceptions/EmptyExcept.ql b/python/ql/src/Exceptions/EmptyExcept.ql index 9e2b16a49e7..a753181af71 100755 --- a/python/ql/src/Exceptions/EmptyExcept.ql +++ b/python/ql/src/Exceptions/EmptyExcept.ql @@ -31,7 +31,7 @@ predicate no_comment(ExceptStmt ex) { } predicate non_local_control_flow(ExceptStmt ex) { - ex.getType().refersTo(theStopIterationType()) + ex.getType().pointsTo(ClassValue::stopIteration()) } predicate try_has_normal_exit(Try try) { @@ -64,32 +64,29 @@ predicate subscript(Stmt s) { s.(Delete).getATarget() instanceof Subscript } -predicate encode_decode(Expr ex, ClassObject type) { +predicate encode_decode(Call ex, ClassValue type) { exists(string name | - ex.(Call).getFunc().(Attribute).getName() = name | - name = "encode" and type = Object::builtin("UnicodeEncodeError") + ex.getFunc().(Attribute).getName() = name | + name = "encode" and type = ClassValue::unicodeEncodeError() or - name = "decode" and type = Object::builtin("UnicodeDecodeError") + name = "decode" and type = ClassValue::unicodeDecodeError() ) } -predicate small_handler(ExceptStmt ex, Stmt s, ClassObject type) { +predicate small_handler(ExceptStmt ex, Stmt s, ClassValue type) { not exists(ex.getTry().getStmt(1)) and s = ex.getTry().getStmt(0) and - ex.getType().refersTo(type) + ex.getType().pointsTo(type) } -/** Holds if this exception handler is sufficiently small in scope to not need a comment - * as to what it is doing. - */ predicate focussed_handler(ExceptStmt ex) { - exists(Stmt s, ClassObject type | + exists(Stmt s, ClassValue type | small_handler(ex, s, type) | - subscript(s) and type.getAnImproperSuperType() = theLookupErrorType() + subscript(s) and type.getASuperType() = ClassValue::lookupError() or - attribute_access(s) and type = theAttributeErrorType() + attribute_access(s) and type = ClassValue::attributeError() or - s.(ExprStmt).getValue() instanceof Name and type = theNameErrorType() + s.(ExprStmt).getValue() instanceof Name and type = ClassValue::nameError() or encode_decode(s.(ExprStmt).getValue(), type) ) diff --git a/python/ql/src/Exceptions/IllegalRaise.ql b/python/ql/src/Exceptions/IllegalRaise.ql index 673289b9e97..da1bc56ae33 100644 --- a/python/ql/src/Exceptions/IllegalRaise.ql +++ b/python/ql/src/Exceptions/IllegalRaise.ql @@ -15,7 +15,7 @@ import python import Raising import Exceptions.NotImplemented -from Raise r, ClassObject t -where type_or_typeof(r, t, _) and not t.isLegalExceptionType() and not t.failedInference() and not use_of_not_implemented_in_raise(r, _) +from Raise r, ClassValue t +where type_or_typeof(r, t, _) and not t.isLegalExceptionType() and not t.failedInference(_) and not use_of_not_implemented_in_raise(r, _) select r, "Illegal class '" + t.getName() + "' raised; will result in a TypeError being raised instead." diff --git a/python/ql/src/Exceptions/NotImplemented.qll b/python/ql/src/Exceptions/NotImplemented.qll index 1a799c37db0..09b3c37998b 100644 --- a/python/ql/src/Exceptions/NotImplemented.qll +++ b/python/ql/src/Exceptions/NotImplemented.qll @@ -3,7 +3,7 @@ import python /** Holds if `notimpl` refers to `NotImplemented` or `NotImplemented()` in the `raise` statement */ predicate use_of_not_implemented_in_raise(Raise raise, Expr notimpl) { - notimpl.refersTo(Object::notImplemented()) and + notimpl.pointsTo(Value::named("NotImplemented")) and ( notimpl = raise.getException() or notimpl = raise.getException().(Call).getFunc() diff --git a/python/ql/src/Exceptions/Raising.qll b/python/ql/src/Exceptions/Raising.qll index b820bfa24e9..7e53c0bbd27 100644 --- a/python/ql/src/Exceptions/Raising.qll +++ b/python/ql/src/Exceptions/Raising.qll @@ -1,14 +1,16 @@ import python /** Whether the raise statement 'r' raises 'type' from origin 'orig' */ -predicate type_or_typeof(Raise r, ClassObject type, AstNode orig) { +predicate type_or_typeof(Raise r, ClassValue type, AstNode orig) { exists(Expr exception | exception = r.getRaised() | - exception.refersTo(type, _, orig) + exception.pointsTo(type, orig) or - not exists(ClassObject exc_type | exception.refersTo(exc_type)) and - not type = theTypeType() and // First value is an unknown exception type - exception.refersTo(_, type, orig) + not exists(ClassValue exc_type | exception.pointsTo(exc_type)) and + not type = ClassValue::type() and // First value is an unknown exception type + exists(Value val | exception.pointsTo(val, orig) | + val.getClass() = type + ) ) } diff --git a/python/ql/src/Exceptions/RaisingTuple.ql b/python/ql/src/Exceptions/RaisingTuple.ql index 8bf5c7705b5..ab197fe1665 100644 --- a/python/ql/src/Exceptions/RaisingTuple.ql +++ b/python/ql/src/Exceptions/RaisingTuple.ql @@ -11,8 +11,9 @@ import python -from Raise r, AstNode origin -where r.getException().refersTo(_, theTupleType(), origin) and +from Raise r, Value v, AstNode origin +where r.getException().pointsTo(v, origin) and +v.getClass() = ClassValue::tuple() and major_version() = 2 /* Raising a tuple is a type error in Python 3, so is handled by the IllegalRaise query. */ select r, "Raising $@ will result in the first element (recursively) being raised and all other elements being discarded.", origin, "a tuple" \ No newline at end of file diff --git a/python/ql/src/Expressions/CallArgs.qll b/python/ql/src/Expressions/CallArgs.qll index 72326a1c30c..5dcf50ac8f4 100644 --- a/python/ql/src/Expressions/CallArgs.qll +++ b/python/ql/src/Expressions/CallArgs.qll @@ -13,6 +13,17 @@ private int varargs_length_objectapi(Call call) { result = count(call.getStarargs().(List).getAnElt()) } +private int varargs_length(Call call) { + not exists(call.getStarargs()) and result = 0 + or + exists(TupleValue t | + call.getStarargs().pointsTo(t) | + result = t.length() + ) + or + result = count(call.getStarargs().(List).getAnElt()) +} + /** Gets a keyword argument that is not a keyword-only parameter. */ private Keyword not_keyword_only_arg_objectapi(Call call, FunctionObject func) { func.getACall().getNode() = call and @@ -20,13 +31,20 @@ private Keyword not_keyword_only_arg_objectapi(Call call, FunctionObject func) { not func.getFunction().getAKeywordOnlyArg().getId() = result.getArg() } +/** Gets a keyword argument that is not a keyword-only parameter. */ +private Keyword not_keyword_only_arg(Call call, FunctionValue func) { + func.getACall().getNode() = call and + result = call.getAKeyword() and + not func.getScope().getAKeywordOnlyArg().getId() = result.getArg() +} + /** Gets the count of arguments that are passed as positional parameters even if they * are named in the call. * This is the sum of the number of positional arguments, the number of elements in any explicit tuple passed as *arg * 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_objectapi_for_call_objectapi(Call call, Object callable) { +private int positional_arg_count_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_objectapi(callable) | @@ -40,10 +58,34 @@ private int positional_arg_count_objectapi_for_call_objectapi(Call call, Object ) } +/** Gets the count of arguments that are passed as positional parameters even if they + * are named in the call. + * This is the sum of the number of positional arguments, the number of elements in any explicit tuple passed as *arg + * 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, Value callable) { + call = get_a_call(callable).getNode() and + exists(int positional_keywords | + exists(FunctionValue func | func = get_function_or_initializer(callable) | + not func.getScope().hasKwArg() and + positional_keywords = count(not_keyword_only_arg(call, func)) + or + func.getScope().hasKwArg() and positional_keywords = 0 + ) + | + result = count(call.getAnArg()) + varargs_length_objectapi(call) + positional_keywords + ) +} + int arg_count_objectapi(Call call) { result = count(call.getAnArg()) + varargs_length_objectapi(call) + count(call.getAKeyword()) } +int arg_count(Call call) { + result = count(call.getAnArg()) + varargs_length(call) + count(call.getAKeyword()) +} + /* Gets a call corresponding to the given class or function*/ private ControlFlowNode get_a_call_objectapi(Object callable) { result = callable.(ClassObject).getACall() @@ -51,6 +93,13 @@ private ControlFlowNode get_a_call_objectapi(Object callable) { result = callable.(FunctionObject).getACall() } +/* Gets a call corresponding to the given class or function*/ +private ControlFlowNode get_a_call(Value callable) { + result = callable.(ClassValue).getACall() + or + result = callable.(FunctionValue).getACall() +} + /* Gets the function object corresponding to the given class or function*/ FunctionObject get_function_or_initializer_objectapi(Object func_or_cls) { result = func_or_cls.(FunctionObject) @@ -58,6 +107,13 @@ FunctionObject get_function_or_initializer_objectapi(Object func_or_cls) { result = func_or_cls.(ClassObject).declaredAttribute("__init__") } +/* Gets the function object corresponding to the given class or function*/ +FunctionValue get_function_or_initializer(Value func_or_cls) { + result = func_or_cls.(FunctionValue) + or + result = func_or_cls.(ClassValue).declaredAttribute("__init__") +} + /**Whether there is an illegally named parameter called `name` in the `call` to `func` */ predicate illegally_named_parameter_objectapi(Call call, Object func, string name) { @@ -67,6 +123,14 @@ predicate illegally_named_parameter_objectapi(Call call, Object func, string nam not get_function_or_initializer_objectapi(func).isLegalArgumentName(name) } +/**Whether there is an illegally named parameter called `name` in the `call` to `func` */ +predicate illegally_named_parameter(Call call, Value func, string name) { + not func.isBuiltin() and + name = call.getANamedArgumentName() and + call.getAFlowNode() = get_a_call(func) and + not get_function_or_initializer(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_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' @@ -88,6 +152,27 @@ predicate too_few_args_objectapi(Call call, Object callable, int limit) { ) } +/**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, Value 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 exists(call.getStarargs()) and not exists(call.getKwargs()) and + arg_count(call) < limit and + exists(FunctionValue func | func = get_function_or_initializer(callable) | + call = func.getACall().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 + * pass 1 too few arguments to the mocked function. + */ + not (useOfMoxInModule(call.getEnclosingModule()) and func.isNormalMethod()) + or + call = func.getACall().getNode() and limit = func.minParameters() - 1 + or + callable instanceof ClassValue and + call.getAFlowNode() = get_a_call(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_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' @@ -103,7 +188,25 @@ predicate too_many_args_objectapi(Call call, Object callable, int limit) { callable instanceof ClassObject and call.getAFlowNode() = get_a_call_objectapi(callable) and limit = func.maxParameters() - 1 ) and - positional_arg_count_objectapi_for_call_objectapi(call, callable) > limit + positional_arg_count_for_call_objectapi(call, callable) > limit +} + +/**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, Value 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 + exists(FunctionValue func | + func = get_function_or_initializer(callable) and + not func.getScope().hasVarArg() and limit >= 0 + | + call = func.getACall().getNode() and limit = func.maxParameters() + or + call = func.getACall().getNode() and limit = func.maxParameters() - 1 + or + callable instanceof ClassValue and + call.getAFlowNode() = get_a_call(callable) and limit = func.maxParameters() - 1 + ) and + positional_arg_count_for_call(call, callable) > limit } /** Holds if `call` has too many or too few arguments for `func` */ @@ -113,6 +216,13 @@ predicate wrong_args_objectapi(Call call, FunctionObject func, int limit, string too_many_args_objectapi(call, func, limit) and too = "too many" } +/** Holds if `call` has too many or too few arguments for `func` */ +predicate wrong_args(Call call, FunctionValue func, int limit, string too) { + too_few_args(call, func, limit) and too = "too few" + or + too_many_args(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`. */ @@ -123,8 +233,25 @@ predicate correct_args_if_called_as_method_objectapi(Call call, FunctionObject f arg_count_objectapi(call) < func.maxParameters() } +/** 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, FunctionValue func) { + arg_count(call)+1 >= func.minParameters() + and + arg_count(call) < func.maxParameters() +} + /** Holds if `call` is a call to `overriding`, which overrides `func`. */ predicate overridden_call_objectapi(FunctionObject func, FunctionObject overriding, Call call) { overriding.overrides(func) and overriding.getACall().getNode() = call } + +/** Holds if `call` is a call to `overriding`, which overrides `func`. */ +predicate overridden_call(FunctionValue func, FunctionValue overriding, Call call) { + overriding.overrides(func) and + overriding.getACall().getNode() = call +} + diff --git a/python/ql/src/Expressions/CallToSuperWrongClass.ql b/python/ql/src/Expressions/CallToSuperWrongClass.ql index a42cbcefe4b..57ae36fa30e 100644 --- a/python/ql/src/Expressions/CallToSuperWrongClass.ql +++ b/python/ql/src/Expressions/CallToSuperWrongClass.ql @@ -17,13 +17,13 @@ import python from CallNode call_to_super, string name where exists(GlobalVariable gv, ControlFlowNode cn | - call_to_super = theSuperType().getACall() and + call_to_super = ClassValue::super_().getACall() and gv.getId() = "super" and cn = call_to_super.getArg(0) and name = call_to_super.getScope().getScope().(Class).getName() and - exists(ClassObject other | - cn.refersTo(other) and - not other.getPyClass().getName() = name + exists(ClassValue other | + cn.pointsTo(other) and + not other.getScope().getName() = name ) ) select call_to_super.getNode(), "First argument to super() should be " + name + "." diff --git a/python/ql/src/Expressions/Formatting/AdvancedFormatting.qll b/python/ql/src/Expressions/Formatting/AdvancedFormatting.qll index 3fe71313460..f32b338c8ac 100644 --- a/python/ql/src/Expressions/Formatting/AdvancedFormatting.qll +++ b/python/ql/src/Expressions/Formatting/AdvancedFormatting.qll @@ -107,10 +107,10 @@ private predicate brace_pair(PossibleAdvancedFormatString fmt, int start, int en private predicate advanced_format_call(Call format_expr, PossibleAdvancedFormatString fmt, int args) { exists(CallNode call | call = format_expr.getAFlowNode() | - call.getFunction().refersTo(Object::builtin("format")) and call.getArg(0).refersTo(_, fmt.getAFlowNode()) and + call.getFunction().pointsTo(Value::named("format")) and call.getArg(0).pointsTo(_, fmt.getAFlowNode()) and args = count(format_expr.getAnArg()) - 1 or - call.getFunction().(AttrNode).getObject("format").refersTo(_, fmt.getAFlowNode()) and + call.getFunction().(AttrNode).getObject("format").pointsTo(_, fmt.getAFlowNode()) and args = count(format_expr.getAnArg()) ) } @@ -139,4 +139,3 @@ class AdvancedFormattingCall extends Call { } } - diff --git a/python/ql/src/Expressions/IsComparisons.qll b/python/ql/src/Expressions/IsComparisons.qll index d310ed6782a..bf9d8bb71db 100644 --- a/python/ql/src/Expressions/IsComparisons.qll +++ b/python/ql/src/Expressions/IsComparisons.qll @@ -28,14 +28,14 @@ predicate probablySingleton(ClassValue cls) { predicate invalid_to_use_is_portably(ClassValue c) { overrides_eq_or_cmp(c) and - /* Exclude type/builtin-function/bool as it is legitimate to compare them using 'is' but they implement __eq__ */ + // Exclude type/builtin-function/bool as it is legitimate to compare them using 'is' but they implement __eq__ not c = Value::named("type") and not c = ClassValue::builtinFunction() and not c = Value::named("bool") and - /* OK to compare with 'is' if a singleton */ + // OK to compare with 'is' if a singleton not probablySingleton(c) } predicate simple_constant(ControlFlowNode f) { - exists(Object obj | f.refersTo(obj) | obj = theTrueObject() or obj = theFalseObject() or obj = theNoneObject()) + exists(Value val | f.pointsTo(val) | val = Value::named("True") or val = Value::named("False") or val = Value::named("None")) } private predicate cpython_interned_value(Expr e) { @@ -66,14 +66,14 @@ private predicate universally_interned_value(Expr e) { predicate cpython_interned_constant(Expr e) { exists(Expr const | - e.refersTo(_, const) | + e.pointsTo(_, const) | cpython_interned_value(const) ) } predicate universally_interned_constant(Expr e) { exists(Expr const | - e.refersTo(_, const) | + e.pointsTo(_, const) | universally_interned_value(const) ) } @@ -95,7 +95,7 @@ private predicate comparison_one_type(Compare comp, Cmpop op, ClassValue cls) { } predicate invalid_portable_is_comparison(Compare comp, Cmpop op, ClassValue cls) { - /* OK to use 'is' when defining '__eq__' */ + // OK to use 'is' when defining '__eq__' not exists(Function eq | eq.getName() = "__eq__" or eq.getName() = "__ne__" | eq = comp.getScope().getScope*()) and ( @@ -107,24 +107,24 @@ predicate invalid_portable_is_comparison(Compare comp, Cmpop op, ClassValue cls) ) ) and - /* OK to use 'is' when comparing items from a known set of objects */ - not exists(Expr left, Expr right, Object obj | + // OK to use 'is' when comparing items from a known set of objects + not exists(Expr left, Expr right, Value val | comp.compares(left, op, right) and - exists(ImmutableLiteral il | il.getLiteralObject() = obj) | - left.refersTo(obj) and right.refersTo(obj) + exists(ImmutableLiteral il | il.getLiteralValue() = val) | + left.pointsTo(val) and right.pointsTo(val) or - /* Simple constant in module, probably some sort of sentinel */ + // Simple constant in module, probably some sort of sentinel exists(AstNode origin | - not left.refersTo(_) and right.refersTo(obj, origin) and + not left.pointsTo(_) and right.pointsTo(val, origin) and origin.getScope().getEnclosingModule() = comp.getScope().getEnclosingModule() ) ) and - /* OK to use 'is' when comparing with a member of an enum */ + // OK to use 'is' when comparing with a member of an enum not exists(Expr left, Expr right, AstNode origin | comp.compares(left, op, right) and enum_member(origin) | - left.refersTo(_, origin) or right.refersTo(_, origin) + left.pointsTo(_, origin) or right.pointsTo(_, origin) ) } @@ -135,4 +135,3 @@ private predicate enum_member(AstNode obj) { asgn.getValue() = obj ) } - diff --git a/python/ql/src/Expressions/TruncatedDivision.ql b/python/ql/src/Expressions/TruncatedDivision.ql index 3d4deb9ba54..471b5244de0 100644 --- a/python/ql/src/Expressions/TruncatedDivision.ql +++ b/python/ql/src/Expressions/TruncatedDivision.ql @@ -18,19 +18,20 @@ where // Only relevant for Python 2, as all later versions implement true division major_version() = 2 and - exists(BinaryExprNode bin, Object lobj, Object robj | + exists(BinaryExprNode bin, Value lval, Value rval | bin = div.getAFlowNode() and bin.getNode().getOp() instanceof Div - and bin.getLeft().refersTo(lobj, theIntType(), left) - and bin.getRight().refersTo(robj, theIntType(), right) + and bin.getLeft().pointsTo(lval, left) + and lval.getClass() = ClassValue::int_() + and bin.getRight().pointsTo(rval, right) + and rval.getClass() = ClassValue::int_() // Ignore instances where integer division leaves no remainder - and not lobj.(NumericObject).intValue() % robj.(NumericObject).intValue() = 0 + and not lval.(NumericValue).getIntValue() % rval.(NumericValue).getIntValue() = 0 and not bin.getNode().getEnclosingModule().hasFromFuture("division") // Filter out results wrapped in `int(...)` - and not exists(CallNode c, ClassObject cls | - c.getAnArg() = bin - and c.getFunction().refersTo(cls) - and cls.getName() = "int" + and not exists(CallNode c | + c = ClassValue::int_().getACall() + and c.getAnArg() = bin ) ) select div, "Result of division may be truncated as its $@ and $@ arguments may both be integers.", diff --git a/python/ql/src/Expressions/UseofApply.ql b/python/ql/src/Expressions/UseofApply.ql index 8f913e6980d..7a0d72b43cf 100644 --- a/python/ql/src/Expressions/UseofApply.ql +++ b/python/ql/src/Expressions/UseofApply.ql @@ -10,8 +10,8 @@ */ import python +private import semmle.python.types.Builtins from CallNode call, ControlFlowNode func -where -major_version() = 2 and call.getFunction() = func and func.refersTo(Object::builtin("apply")) +where major_version() = 2 and call.getFunction() = func and func.pointsTo(Value::named("apply")) select call, "Call to the obsolete builtin function 'apply'." diff --git a/python/ql/src/Expressions/UseofInput.qhelp b/python/ql/src/Expressions/UseofInput.qhelp index 44baace8c43..f622a3c584b 100644 --- a/python/ql/src/Expressions/UseofInput.qhelp +++ b/python/ql/src/Expressions/UseofInput.qhelp @@ -3,20 +3,20 @@ "qhelp.dtd"> -

    A call to the input() function, input(prompt) is equivalent to eval(raw_input(prompt)). Evaluating user input without any checking can be a serious security flaw.

    +

    In Python 2, a call to the input() function, input(prompt) is equivalent to eval(raw_input(prompt)). Evaluating user input without any checking can be a serious security flaw.

    -

    Get user input with raw_input(prompt) and then validate that input before evaluating. If the expected input is a number or +

    Get user input with raw_input(prompt) and then validate that input before evaluating. If the expected input is a number or string, then ast.literal_eval() can always be used safely.

    -
  • Python Standard Library: input, - ast.literal_eval.
  • +
  • Python Standard Library: input, + ast.literal_eval.
  • Wikipedia: Data validation.
  • diff --git a/python/ql/src/Expressions/UseofInput.ql b/python/ql/src/Expressions/UseofInput.ql index 8f601d5fafa..dc67458a083 100644 --- a/python/ql/src/Expressions/UseofInput.ql +++ b/python/ql/src/Expressions/UseofInput.ql @@ -1,6 +1,6 @@ /** - * @name 'input' function used - * @description The built-in function 'input' is used which can allow arbitrary code to be run. + * @name 'input' function used in Python 2 + * @description The built-in function 'input' is used which, in Python 2, can allow arbitrary code to be run. * @kind problem * @tags security * correctness @@ -18,4 +18,4 @@ where call.getFunction() = func and func.pointsTo(context, Value::named("input"), _) and not func.pointsTo(context, Value::named("raw_input"), _) -select call, "The unsafe built-in function 'input' is used." +select call, "The unsafe built-in function 'input' is used in Python 2." diff --git a/python/ql/src/Expressions/WrongNumberArgumentsForFormat.ql b/python/ql/src/Expressions/WrongNumberArgumentsForFormat.ql index aa163d91544..71d5bec2fca 100644 --- a/python/ql/src/Expressions/WrongNumberArgumentsForFormat.ql +++ b/python/ql/src/Expressions/WrongNumberArgumentsForFormat.ql @@ -15,29 +15,30 @@ import python import semmle.python.strings -predicate string_format(BinaryExpr operation, StrConst str, Object args, AstNode origin) { - exists(Object fmt, Context ctx | operation.getOp() instanceof Mod | - operation.getLeft().refersTo(ctx, fmt, _, str) and - operation.getRight().refersTo(ctx, args, _, origin) +predicate string_format(BinaryExpr operation, StrConst str, Value args, AstNode origin) { + operation.getOp() instanceof Mod and + exists(Value fmt, Context ctx | + operation.getLeft().pointsTo(ctx, fmt, str) and + operation.getRight().pointsTo(ctx, args, origin) ) } -int sequence_length(Object args) { +int sequence_length(Value args) { /* Guess length of sequence */ - exists(Tuple seq | - seq = args.getOrigin() | + exists(Tuple seq, AstNode origin | + seq.pointsTo(args,origin) | result = strictcount(seq.getAnElt()) and not seq.getAnElt() instanceof Starred ) or exists(ImmutableLiteral i | - i.getLiteralObject() = args | + i.getLiteralValue() = args | result = 1 ) } -from BinaryExpr operation, StrConst fmt, Object args, int slen, int alen, AstNode origin, string provided +from BinaryExpr operation, StrConst fmt, Value args, int slen, int alen, AstNode origin, string provided where string_format(operation, fmt, args, origin) and slen = sequence_length(args) and alen = format_items(fmt) and slen != alen and (if slen = 1 then provided = " is provided." else provided = " are provided.") select operation, "Wrong number of $@ for string format. Format $@ takes " + alen.toString() + ", but " + slen.toString() + provided, diff --git a/python/ql/src/Functions/SignatureSpecialMethods.ql b/python/ql/src/Functions/SignatureSpecialMethods.ql index 1301949768a..3e718ce2a3b 100644 --- a/python/ql/src/Functions/SignatureSpecialMethods.ql +++ b/python/ql/src/Functions/SignatureSpecialMethods.ql @@ -112,7 +112,7 @@ predicate is_quad_op(string name) { name = "__setslice__" or name = "__exit__" } -int argument_count(PyFunctionObject f, string name, ClassObject cls) { +int argument_count(PythonFunctionValue f, string name, ClassValue cls) { cls.declaredAttribute(name) = f and ( is_unary_op(name) and result = 1 @@ -125,7 +125,7 @@ int argument_count(PyFunctionObject f, string name, ClassObject cls) { ) } -predicate incorrect_special_method_defn(PyFunctionObject func, string message, boolean show_counts, string name, ClassObject owner) { +predicate incorrect_special_method_defn(PythonFunctionValue func, string message, boolean show_counts, string name, ClassValue owner) { exists(int required | required = argument_count(func, name, owner) | /* actual_non_default <= actual */ @@ -133,14 +133,14 @@ predicate incorrect_special_method_defn(PyFunctionObject func, string message, b (message = "Too few parameters" and show_counts = true) else if required < func.minParameters() then (message = "Too many parameters" and show_counts = true) - else if (func.minParameters() < required and not func.getFunction().hasVarArg()) then + else if (func.minParameters() < required and not func.getScope().hasVarArg()) then (message = (required -func.minParameters()) + " default values(s) will never be used" and show_counts = false) else none() ) } -predicate incorrect_pow(FunctionObject func, string message, boolean show_counts, ClassObject owner) { +predicate incorrect_pow(FunctionValue func, string message, boolean show_counts, ClassValue owner) { owner.declaredAttribute("__pow__") = func and ( func.maxParameters() < 2 and message = "Too few parameters" and show_counts = true @@ -153,19 +153,19 @@ predicate incorrect_pow(FunctionObject func, string message, boolean show_counts ) } -predicate incorrect_get(FunctionObject func, string message, boolean show_counts, ClassObject owner) { +predicate incorrect_get(FunctionValue func, string message, boolean show_counts, ClassValue owner) { owner.declaredAttribute("__get__") = func and ( func.maxParameters() < 3 and message = "Too few parameters" and show_counts = true or func.minParameters() > 3 and message = "Too many parameters" and show_counts = true or - func.minParameters() < 2 and not func.getFunction().hasVarArg() and + func.minParameters() < 2 and not func.getScope().hasVarArg() and message = (2 - func.minParameters()) + " default value(s) will never be used" and show_counts = false ) } -string should_have_parameters(PyFunctionObject f, string name, ClassObject owner) { +string should_have_parameters(PythonFunctionValue f, string name, ClassValue owner) { exists(int i | i = argument_count(f, name, owner) | result = i.toString() ) @@ -173,7 +173,7 @@ string should_have_parameters(PyFunctionObject f, string name, ClassObject owner owner.declaredAttribute(name) = f and (name = "__get__" or name = "__pow__") and result = "2 or 3" } -string has_parameters(PyFunctionObject f) { +string has_parameters(PythonFunctionValue f) { exists(int i | i = f.minParameters() | i = 0 and result = "no parameters" or @@ -183,7 +183,7 @@ string has_parameters(PyFunctionObject f) { ) } -from PyFunctionObject f, string message, string sizes, boolean show_counts, string name, ClassObject owner +from PythonFunctionValue f, string message, string sizes, boolean show_counts, string name, ClassValue owner where ( incorrect_special_method_defn(f, message, show_counts, name, owner) diff --git a/python/ql/src/Functions/UseImplicitNoneReturnValue.ql b/python/ql/src/Functions/UseImplicitNoneReturnValue.ql index d2c954cb4c9..ab797844533 100644 --- a/python/ql/src/Functions/UseImplicitNoneReturnValue.ql +++ b/python/ql/src/Functions/UseImplicitNoneReturnValue.ql @@ -23,12 +23,12 @@ predicate is_used(Call c) { ) } -from Call c, FunctionObject func +from Call c, FunctionValue func where /* Call result is used, but callee is a procedure */ -is_used(c) and c.getFunc().refersTo(func) and func.getFunction().isProcedure() and +is_used(c) and c.getFunc().pointsTo(func) and func.getScope().isProcedure() and /* All callees are procedures */ -forall(FunctionObject callee | c.getFunc().refersTo(callee) | callee.getFunction().isProcedure()) and +forall(FunctionValue callee | c.getFunc().pointsTo(callee) | callee.getScope().isProcedure()) and /* Mox return objects have an `AndReturn` method */ not useOfMoxInModule(c.getEnclosingModule()) select c, "The result of '$@' is used even though it is always None.", func, func.getQualifiedName() diff --git a/python/ql/src/Statements/AssertLiteralConstant.ql b/python/ql/src/Statements/AssertLiteralConstant.ql index bf575dd0e25..cdb39ff0955 100644 --- a/python/ql/src/Statements/AssertLiteralConstant.ql +++ b/python/ql/src/Statements/AssertLiteralConstant.ql @@ -26,7 +26,5 @@ where value = test.(NameConstant).toString() ) and /* Exclude asserts appearing at the end of a chain of `elif`s */ - not exists(If i | - i.getElif().getAnOrelse() = a - ) + not exists(If i | i.getElif().getAnOrelse() = a) select a, "Assert of literal constant " + value + "." diff --git a/python/ql/src/Statements/AssertOnTuple.ql b/python/ql/src/Statements/AssertOnTuple.ql index 8ca00f2391e..0a2c83f986c 100644 --- a/python/ql/src/Statements/AssertOnTuple.ql +++ b/python/ql/src/Statements/AssertOnTuple.ql @@ -14,11 +14,14 @@ import python from Assert a, string b, string non -where a.getTest() instanceof Tuple and - (if exists(((Tuple)a.getTest()).getAnElt()) then - (b = "True" and non = "non-") - else - (b = "False" and non = "") - ) +where + a.getTest() instanceof Tuple and + ( + if exists(a.getTest().(Tuple).getAnElt()) + then ( + b = "True" and non = "non-" + ) else ( + b = "False" and non = "" + ) + ) select a, "Assertion of " + non + "empty tuple is always " + b + "." - diff --git a/python/ql/src/Statements/BreakOrReturnInFinally.ql b/python/ql/src/Statements/BreakOrReturnInFinally.ql index 1d9bc7296c9..7dbc2fa8edd 100644 --- a/python/ql/src/Statements/BreakOrReturnInFinally.ql +++ b/python/ql/src/Statements/BreakOrReturnInFinally.ql @@ -15,13 +15,13 @@ import python from Stmt s, string kind -where -s instanceof Return and kind = "return" and exists(Try t | t.getFinalbody().contains(s)) -or -s instanceof Break and kind = "break" and -exists(Try t | t.getFinalbody().contains(s) | - not exists(For loop | loop.contains(s) and t.getFinalbody().contains(loop)) - and - not exists(While loop | loop.contains(s) and t.getFinalbody().contains(loop)) -) +where + s instanceof Return and kind = "return" and exists(Try t | t.getFinalbody().contains(s)) + or + s instanceof Break and + kind = "break" and + exists(Try t | t.getFinalbody().contains(s) | + not exists(For loop | loop.contains(s) and t.getFinalbody().contains(loop)) and + not exists(While loop | loop.contains(s) and t.getFinalbody().contains(loop)) + ) select s, "'" + kind + "' in a finally block will swallow any exceptions raised." diff --git a/python/ql/src/Statements/C_StyleParentheses.ql b/python/ql/src/Statements/C_StyleParentheses.ql index c670876e15a..1c0f27bf8b6 100644 --- a/python/ql/src/Statements/C_StyleParentheses.ql +++ b/python/ql/src/Statements/C_StyleParentheses.ql @@ -14,19 +14,22 @@ import python from Expr e, Location l, string kind, string what -where e.isParenthesized() and -not e instanceof Tuple and -( - exists(If i | i.getTest() = e) and kind = "if" and what = "condition" - or - exists(While w | w.getTest() = e) and kind = "while" and what = "condition" - or - exists(Return r | r.getValue() = e) and kind = "return" and what = "value" - or - exists(Assert a | a.getTest() = e and not exists(a.getMsg())) and kind = "assert" and what = "test" -) -and -// These require parentheses -(not e instanceof Yield and not e instanceof YieldFrom and not e instanceof GeneratorExp) and -l = e.getLocation() and l.getStartLine() = l.getEndLine() +where + e.isParenthesized() and + not e instanceof Tuple and + ( + exists(If i | i.getTest() = e) and kind = "if" and what = "condition" + or + exists(While w | w.getTest() = e) and kind = "while" and what = "condition" + or + exists(Return r | r.getValue() = e) and kind = "return" and what = "value" + or + exists(Assert a | a.getTest() = e and not exists(a.getMsg())) and + kind = "assert" and + what = "test" + ) and + // These require parentheses + (not e instanceof Yield and not e instanceof YieldFrom and not e instanceof GeneratorExp) and + l = e.getLocation() and + l.getStartLine() = l.getEndLine() select e, "Parenthesized " + what + " in '" + kind + "' statement." diff --git a/python/ql/src/Statements/ConstantInConditional.ql b/python/ql/src/Statements/ConstantInConditional.ql index 06a63cf037a..e01e693467a 100644 --- a/python/ql/src/Statements/ConstantInConditional.ql +++ b/python/ql/src/Statements/ConstantInConditional.ql @@ -15,16 +15,15 @@ import python - predicate is_condition(Expr cond) { - exists(If i | i.getTest() = cond) or - exists(IfExp ie | ie.getTest() = cond) + exists(If i | i.getTest() = cond) or + exists(IfExp ie | ie.getTest() = cond) } /* Treat certain unmodified builtins as constants as well. */ predicate effective_constant(Name cond) { exists(GlobalVariable var | var = cond.getVariable() and not exists(NameNode f | f.defines(var)) | - var.getId() = "True" or var.getId() = "False" or var.getId() = "NotImplemented" + var.getId() = "True" or var.getId() = "False" or var.getId() = "NotImplemented" ) } @@ -34,9 +33,10 @@ predicate test_makes_code_unreachable(Expr cond) { exists(While w | w.getTest() = cond and w.getStmt(0).isUnreachable()) } - from Expr cond -where is_condition(cond) and (cond.isConstant() or effective_constant(cond)) and -/* Ignore cases where test makes code unreachable, as that is handled in different query */ -not test_makes_code_unreachable(cond) +where + is_condition(cond) and + (cond.isConstant() or effective_constant(cond)) and + /* Ignore cases where test makes code unreachable, as that is handled in different query */ + not test_makes_code_unreachable(cond) select cond, "Testing a constant will always give the same result." diff --git a/python/ql/src/Statements/DocStrings.ql b/python/ql/src/Statements/DocStrings.ql index 4bf458bd22b..d6a1d812300 100644 --- a/python/ql/src/Statements/DocStrings.ql +++ b/python/ql/src/Statements/DocStrings.ql @@ -9,7 +9,9 @@ * @precision medium * @id py/missing-docstring */ -/* NOTE: precision of 'medium' reflects the lack of precision in the underlying rule. + +/* + * NOTE: precision of 'medium' reflects the lack of precision in the underlying rule. * Do we care whether a function has a docstring? That often depends on the reader of that docstring. */ @@ -18,25 +20,26 @@ import python predicate needs_docstring(Scope s) { s.isPublic() and ( - not s instanceof Function - or - function_needs_docstring(s) + not s instanceof Function + or + function_needs_docstring(s) ) } predicate function_needs_docstring(Function f) { - not exists(FunctionObject fo, FunctionObject base | fo.overrides(base) and fo.getFunction() = f | - not function_needs_docstring(base.getFunction())) and + not exists(FunctionValue fo, FunctionValue base | fo.overrides(base) and fo.getScope() = f | + not function_needs_docstring(base.getScope()) + ) and f.getName() != "lambda" and - (f.getMetrics().getNumberOfLinesOfCode() - count(f.getADecorator())) > 2 - and not exists(PythonPropertyObject p | + (f.getMetrics().getNumberOfLinesOfCode() - count(f.getADecorator())) > 2 and + not exists(PythonPropertyObject p | p.getGetter().getFunction() = f or p.getSetter().getFunction() = f ) } string scope_type(Scope s) { - result = "Module" and s instanceof Module and not ((Module)s).isPackage() + result = "Module" and s instanceof Module and not s.(Module).isPackage() or result = "Class" and s instanceof Class or @@ -46,5 +49,3 @@ string scope_type(Scope s) { from Scope s where needs_docstring(s) and not exists(s.getDocString()) select s, scope_type(s) + " " + s.getName() + " does not have a docstring" - - diff --git a/python/ql/src/Statements/ExecUsed.ql b/python/ql/src/Statements/ExecUsed.ql index 37d92b77c54..44d4da02e32 100644 --- a/python/ql/src/Statements/ExecUsed.ql +++ b/python/ql/src/Statements/ExecUsed.ql @@ -19,9 +19,9 @@ string message() { } predicate exec_function_call(Call c) { - exists(GlobalVariable exec | exec = ((Name)c.getFunc()).getVariable() and exec.getId() = "exec") + exists(GlobalVariable exec | exec = c.getFunc().(Name).getVariable() and exec.getId() = "exec") } from AstNode exec where exec_function_call(exec) or exec instanceof Exec -select exec, message() \ No newline at end of file +select exec, message() diff --git a/python/ql/src/Statements/IterableStringOrSequence.ql b/python/ql/src/Statements/IterableStringOrSequence.ql index a44c3ae7286..76d601dc600 100644 --- a/python/ql/src/Statements/IterableStringOrSequence.ql +++ b/python/ql/src/Statements/IterableStringOrSequence.ql @@ -13,18 +13,20 @@ import python -predicate is_a_string_type(ClassObject seqtype) { - seqtype = theBytesType() and major_version() = 2 - or - seqtype = theUnicodeType() +predicate has_string_type(Value v) { + v.getClass() = ClassValue::str() + or + v.getClass() = ClassValue::unicode() and major_version() = 2 } -from For loop, ControlFlowNode iter, Object str, Object seq, ControlFlowNode seq_origin, ClassObject strtype, ClassObject seqtype, ControlFlowNode str_origin -where loop.getIter().getAFlowNode() = iter and -iter.refersTo(str, strtype, str_origin) and -iter.refersTo(seq, seqtype, seq_origin) and -is_a_string_type(strtype) and -seqtype.isIterable() and -not is_a_string_type(seqtype) - -select loop, "Iteration over $@, of class " + seqtype.getName() + ", may also iterate over $@.", seq_origin, "sequence", str_origin, "string" \ No newline at end of file +from + For loop, ControlFlowNode iter, Value str, Value seq, ControlFlowNode seq_origin, ControlFlowNode str_origin +where + loop.getIter().getAFlowNode() = iter and + iter.pointsTo(str, str_origin) and + iter.pointsTo(seq, seq_origin) and + has_string_type(str) and + seq.getClass().isIterable() and + not has_string_type(seq) +select loop, "Iteration over $@, of class " + seq.getClass().getName() + ", may also iterate over $@.", + seq_origin, "sequence", str_origin, "string" diff --git a/python/ql/src/Statements/MismatchInMultipleAssignment.ql b/python/ql/src/Statements/MismatchInMultipleAssignment.ql index 8dee6d5eb5f..157ddf1270b 100644 --- a/python/ql/src/Statements/MismatchInMultipleAssignment.ql +++ b/python/ql/src/Statements/MismatchInMultipleAssignment.ql @@ -14,18 +14,19 @@ import python -private int len(ExprList el) { - result = count(el.getAnItem()) -} +private int len(ExprList el) { result = count(el.getAnItem()) } predicate mismatched(Assign a, int lcount, int rcount, Location loc, string sequenceType) { exists(ExprList l, ExprList r | - (a.getATarget().(Tuple).getElts() = l or - a.getATarget().(List).getElts() = l) - and - ((a.getValue().(Tuple).getElts() = r and sequenceType = "tuple") or - (a.getValue().(List).getElts() = r and sequenceType = "list")) - and + ( + a.getATarget().(Tuple).getElts() = l or + a.getATarget().(List).getElts() = l + ) and + ( + a.getValue().(Tuple).getElts() = r and sequenceType = "tuple" + or + a.getValue().(List).getElts() = r and sequenceType = "list" + ) and loc = a.getValue().getLocation() and lcount = len(l) and rcount = len(r) and @@ -35,24 +36,26 @@ predicate mismatched(Assign a, int lcount, int rcount, Location loc, string sequ } predicate mismatched_tuple_rhs(Assign a, int lcount, int rcount, Location loc) { - exists(ExprList l, TupleObject r, AstNode origin | - (a.getATarget().(Tuple).getElts() = l or - a.getATarget().(List).getElts() = l) - and - a.getValue().refersTo(r, origin) and + exists(ExprList l, TupleValue r, AstNode origin | + ( + a.getATarget().(Tuple).getElts() = l or + a.getATarget().(List).getElts() = l + ) and + a.getValue().pointsTo(r, origin) and loc = origin.getLocation() and lcount = len(l) and - rcount = r.getLength() and + rcount = r.length() and lcount != rcount and not exists(Starred s | l.getAnItem() = s) ) } - from Assign a, int lcount, int rcount, Location loc, string sequenceType where mismatched(a, lcount, rcount, loc, sequenceType) or mismatched_tuple_rhs(a, lcount, rcount, loc) and sequenceType = "tuple" -select a, "Left hand side of assignment contains " + lcount + " variables, but right hand side is a $@ of length " + rcount + "." , loc, sequenceType +select a, + "Left hand side of assignment contains " + lcount + + " variables, but right hand side is a $@ of length " + rcount + ".", loc, sequenceType diff --git a/python/ql/src/Statements/ModificationOfLocals.ql b/python/ql/src/Statements/ModificationOfLocals.ql index a20843410ef..2117748f3e2 100644 --- a/python/ql/src/Statements/ModificationOfLocals.ql +++ b/python/ql/src/Statements/ModificationOfLocals.ql @@ -12,24 +12,18 @@ import python -Object aFunctionLocalsObject() { - exists(Call c, Name n, GlobalVariable v | - c = result.getOrigin() and - n = c.getFunc() and - n.getVariable() = v and - v.getId() = "locals" and - c.getScope() instanceof FastLocalsFunction - ) +predicate originIsLocals(ControlFlowNode n) { + n.pointsTo(_, _, Value::named("locals").getACall()) } - - predicate modification_of_locals(ControlFlowNode f) { - f.(SubscriptNode).getObject().refersTo(aFunctionLocalsObject()) and (f.isStore() or f.isDelete()) + originIsLocals(f.(SubscriptNode).getObject()) and + (f.isStore() or f.isDelete()) or exists(string mname, AttrNode attr | attr = f.(CallNode).getFunction() and - attr.getObject(mname).refersTo(aFunctionLocalsObject(), _) | + originIsLocals(attr.getObject(mname)) + | mname = "pop" or mname = "popitem" or mname = "update" or @@ -39,5 +33,4 @@ predicate modification_of_locals(ControlFlowNode f) { from AstNode a, ControlFlowNode f where modification_of_locals(f) and a = f.getNode() - select a, "Modification of the locals() dictionary will have no effect on the local variables." diff --git a/python/ql/src/Statements/NestedLoopsSameVariable.ql b/python/ql/src/Statements/NestedLoopsSameVariable.ql index 6c1ed0f68ff..8e966077c16 100644 --- a/python/ql/src/Statements/NestedLoopsSameVariable.ql +++ b/python/ql/src/Statements/NestedLoopsSameVariable.ql @@ -10,20 +10,21 @@ * @precision very-high * @id py/nested-loops-with-same-variable */ + import python -predicate loop_variable(For f, Variable v) { - f.getTarget().defines(v) -} +predicate loop_variable(For f, Variable v) { f.getTarget().defines(v) } predicate variableUsedInNestedLoops(For inner, For outer, Variable v) { /* Only treat loops in body as inner loops. Loops in the else clause are ignored. */ - outer.getBody().contains(inner) and loop_variable(inner, v) and loop_variable(outer, v) + outer.getBody().contains(inner) and + loop_variable(inner, v) and + loop_variable(outer, v) and /* Ignore cases where there is no use of the variable or the only use is in the inner loop */ - and exists(Name n | n.uses(v) and outer.contains(n) and not inner.contains(n)) + exists(Name n | n.uses(v) and outer.contains(n) and not inner.contains(n)) } from For inner, For outer, Variable v where variableUsedInNestedLoops(inner, outer, v) -select inner, "Nested for statement uses loop variable '" + v.getId() + "' of enclosing $@.", - outer, "for statement" +select inner, "Nested for statement uses loop variable '" + v.getId() + "' of enclosing $@.", outer, + "for statement" diff --git a/python/ql/src/Statements/NestedLoopsSameVariableWithReuse.ql b/python/ql/src/Statements/NestedLoopsSameVariableWithReuse.ql index 0082f8c3c1a..400c43d1d94 100644 --- a/python/ql/src/Statements/NestedLoopsSameVariableWithReuse.ql +++ b/python/ql/src/Statements/NestedLoopsSameVariableWithReuse.ql @@ -19,18 +19,18 @@ predicate loop_variable_ssa(For f, Variable v, SsaVariable s) { predicate variableUsedInNestedLoops(For inner, For outer, Variable v, Name n) { /* Ignore cases where there is no use of the variable or the only use is in the inner loop. */ - outer.contains(n) - and not inner.contains(n) + outer.contains(n) and + not inner.contains(n) and /* Only treat loops in body as inner loops. Loops in the else clause are ignored. */ - and outer.getBody().contains(inner) - and exists(SsaVariable s | - loop_variable_ssa(inner, v, s.getAnUltimateDefinition()) - and loop_variable_ssa(outer, v, _) - and s.getAUse().getNode() = n + outer.getBody().contains(inner) and + exists(SsaVariable s | + loop_variable_ssa(inner, v, s.getAnUltimateDefinition()) and + loop_variable_ssa(outer, v, _) and + s.getAUse().getNode() = n ) } from For inner, For outer, Variable v, Name n where variableUsedInNestedLoops(inner, outer, v, n) -select inner, "Nested for statement $@ loop variable '" + v.getId() + "' of enclosing $@.", n, "uses", - outer, "for statement" \ No newline at end of file +select inner, "Nested for statement $@ loop variable '" + v.getId() + "' of enclosing $@.", n, + "uses", outer, "for statement" diff --git a/python/ql/src/Statements/NonIteratorInForLoop.qhelp b/python/ql/src/Statements/NonIteratorInForLoop.qhelp index 0165db9fea9..5be3ff5d21f 100644 --- a/python/ql/src/Statements/NonIteratorInForLoop.qhelp +++ b/python/ql/src/Statements/NonIteratorInForLoop.qhelp @@ -17,7 +17,7 @@ for addressing the defect.

    -In this example, the loop may attempt to iterate over None, which is not an iterator. +In this example, the loop may attempt to iterate over None, which is not an iterable. It is likely that the programmer forgot to test for None before the loop.

    diff --git a/python/ql/src/Statements/NonIteratorInForLoop.ql b/python/ql/src/Statements/NonIteratorInForLoop.ql index 4c398c5e644..85982ccc030 100644 --- a/python/ql/src/Statements/NonIteratorInForLoop.ql +++ b/python/ql/src/Statements/NonIteratorInForLoop.ql @@ -14,10 +14,12 @@ import python from For loop, ControlFlowNode iter, Value v, ClassValue t, ControlFlowNode origin -where loop.getIter().getAFlowNode() = iter and -iter.pointsTo(_, v, origin) and v.getClass() = t and -not t.isIterable() and not t.failedInference(_) and -not v = Value::named("None") and -not t.isDescriptorType() - -select loop, "$@ of class '$@' may be used in for-loop.", origin, "Non-iterator", t, t.getName() +where + loop.getIter().getAFlowNode() = iter and + iter.pointsTo(_, v, origin) and + v.getClass() = t and + not t.isIterable() and + not t.failedInference(_) and + not v = Value::named("None") and + not t.isDescriptorType() +select loop, "$@ of class '$@' may be used in for-loop.", origin, "Non-iterable", t, t.getName() diff --git a/python/ql/src/Statements/RedundantAssignment.ql b/python/ql/src/Statements/RedundantAssignment.ql index 9395bf73141..0751e9b8253 100644 --- a/python/ql/src/Statements/RedundantAssignment.ql +++ b/python/ql/src/Statements/RedundantAssignment.ql @@ -12,8 +12,8 @@ */ import python -predicate assignment(AssignStmt a, Expr left, Expr right) -{ + +predicate assignment(AssignStmt a, Expr left, Expr right) { a.getATarget() = left and a.getValue() = right } @@ -23,7 +23,8 @@ predicate corresponding(Expr left, Expr right) { exists(Attribute la, Attribute ra | corresponding(la, ra) and left = la.getObject() and - right = ra.getObject()) + right = ra.getObject() + ) } predicate same_value(Expr left, Expr right) { @@ -33,34 +34,41 @@ predicate same_value(Expr left, Expr right) { } predicate maybe_defined_in_outer_scope(Name n) { - exists(SsaVariable v | v.getAUse().getNode() = n | - v.maybeUndefined() - ) + exists(SsaVariable v | v.getAUse().getNode() = n | v.maybeUndefined()) } -Variable relevant_var(Name n) { - n.getVariable() = result and - (corresponding(n, _) or corresponding(_, n)) +/* Protection against FPs in projects that offer compatibility between Python 2 and 3, + * since many of them make assignments such as + * + * if PY2: + * bytes = str + * else: + * bytes = bytes + * + */ +predicate isBuiltin(string name) { + exists(Value v | v = Value::named(name) and v.isBuiltin()) } predicate same_name(Name n1, Name n2) { corresponding(n1, n2) and - relevant_var(n1) = relevant_var(n2) and - not exists(Object::builtin(n1.getId())) and + n1.getVariable() = n2.getVariable() and + not isBuiltin(n1.getId()) and not maybe_defined_in_outer_scope(n2) } -ClassObject value_type(Attribute a) { - a.getObject().refersTo(_, result, _) -} +ClassValue value_type(Attribute a) { a.getObject().pointsTo().getClass() = result } predicate is_property_access(Attribute a) { - value_type(a).lookupAttribute(a.getName()) instanceof PropertyObject + value_type(a).lookup(a.getName()) instanceof PropertyValue } predicate same_attribute(Attribute a1, Attribute a2) { - corresponding(a1, a2) and a1.getName() = a2.getName() and same_value(a1.getObject(), a2.getObject()) and - exists(value_type(a1)) and not is_property_access(a1) + corresponding(a1, a2) and + a1.getName() = a2.getName() and + same_value(a1.getObject(), a2.getObject()) and + exists(value_type(a1)) and + not is_property_access(a1) } int pyflakes_commented_line(File file) { @@ -72,21 +80,25 @@ int pyflakes_commented_line(File file) { predicate pyflakes_commented(AssignStmt assignment) { exists(Location loc | assignment.getLocation() = loc and - loc.getStartLine() = pyflakes_commented_line(loc.getFile())) + loc.getStartLine() = pyflakes_commented_line(loc.getFile()) + ) } predicate side_effecting_lhs(Attribute lhs) { - exists(ClassObject cls, ClassObject decl | - lhs.getObject().refersTo(_, cls, _) and - decl = cls.getAnImproperSuperType() and - not decl.isBuiltin() | + exists(ClassValue cls, ClassValue decl | + lhs.getObject().pointsTo().getClass() = cls and + decl = cls.getASuperType() and + not decl.isBuiltin() + | decl.declaresAttribute("__setattr__") ) } from AssignStmt a, Expr left, Expr right -where assignment(a, left, right) - and same_value(left, right) - and not pyflakes_commented(a) and - not side_effecting_lhs(left) +where + assignment(a, left, right) and + same_value(left, right) and + // some people use self-assignment to shut Pyflakes up, such as `ok = ok # Pyflakes` + not pyflakes_commented(a) and + not side_effecting_lhs(left) select a, "This assignment assigns a variable to itself." diff --git a/python/ql/src/Statements/ReturnOrYieldOutsideFunction.ql b/python/ql/src/Statements/ReturnOrYieldOutsideFunction.ql index a940dc60123..ff6b7f379d3 100644 --- a/python/ql/src/Statements/ReturnOrYieldOutsideFunction.ql +++ b/python/ql/src/Statements/ReturnOrYieldOutsideFunction.ql @@ -14,12 +14,12 @@ import python from AstNode node, string kind where - not node.getScope() instanceof Function and - ( - node instanceof Return and kind = "return" - or - node instanceof Yield and kind = "yield" - or - node instanceof YieldFrom and kind = "yield from" - ) + not node.getScope() instanceof Function and + ( + node instanceof Return and kind = "return" + or + node instanceof Yield and kind = "yield" + or + node instanceof YieldFrom and kind = "yield from" + ) select node, "'" + kind + "' is used outside a function." diff --git a/python/ql/src/Statements/ShouldUseWithStatement.ql b/python/ql/src/Statements/ShouldUseWithStatement.ql index 06b3b762db0..9be28d5ceec 100644 --- a/python/ql/src/Statements/ShouldUseWithStatement.ql +++ b/python/ql/src/Statements/ShouldUseWithStatement.ql @@ -14,24 +14,26 @@ import python +predicate calls_close(Call c) { exists(Attribute a | c.getFunc() = a and a.getName() = "close") } -predicate calls_close(Call c) { - exists (Attribute a | c.getFunc() = a and a.getName() = "close") +predicate only_stmt_in_finally(Try t, Call c) { + exists(ExprStmt s | + t.getAFinalstmt() = s and s.getValue() = c and strictcount(t.getAFinalstmt()) = 1 + ) } -predicate -only_stmt_in_finally(Try t, Call c) { - exists(ExprStmt s | t.getAFinalstmt() = s and s.getValue() = c and strictcount(t.getAFinalstmt()) = 1) +predicate points_to_context_manager(ControlFlowNode f, ClassValue cls) { + forex(Value v | f.pointsTo(v) | v.getClass() = cls) and + cls.isContextManager() } -predicate points_to_context_manager(ControlFlowNode f, ClassObject cls) { - cls.isContextManager() and - forex(Object obj | f.refersTo(obj) | f.refersTo(obj, cls, _)) -} - -from Call close, Try t, ClassObject cls -where only_stmt_in_finally(t, close) and calls_close(close) and -exists(ControlFlowNode f | f = close.getFunc().getAFlowNode().(AttrNode).getObject() | - points_to_context_manager(f, cls)) -select close, "Instance of context-manager class $@ is closed in a finally block. Consider using 'with' statement.", cls, cls.getName() - +from Call close, Try t, ClassValue cls +where + only_stmt_in_finally(t, close) and + calls_close(close) and + exists(ControlFlowNode f | f = close.getFunc().getAFlowNode().(AttrNode).getObject() | + points_to_context_manager(f, cls) + ) +select close, + "Instance of context-manager class $@ is closed in a finally block. Consider using 'with' statement.", + cls, cls.getName() diff --git a/python/ql/src/Statements/SideEffectInAssert.py b/python/ql/src/Statements/SideEffectInAssert.py index 4aba0adc8de..f5a5cbdf807 100644 --- a/python/ql/src/Statements/SideEffectInAssert.py +++ b/python/ql/src/Statements/SideEffectInAssert.py @@ -1 +1 @@ -assert(subprocess.call(['run-backup']) == 0) +assert subprocess.call(['run-backup']) == 0 diff --git a/python/ql/src/Statements/SideEffectInAssert.ql b/python/ql/src/Statements/SideEffectInAssert.ql index e62685b7c33..a8ed146b16e 100644 --- a/python/ql/src/Statements/SideEffectInAssert.ql +++ b/python/ql/src/Statements/SideEffectInAssert.ql @@ -14,22 +14,37 @@ import python predicate func_with_side_effects(Expr e) { - exists(string name | - name = ((Attribute)e).getName() or name = ((Name)e).getId() | - name = "print" or name = "write" or name = "append" or - name = "pop" or name = "remove" or name = "discard" or - name = "delete" or name = "close" or name = "open" or + exists(string name | name = e.(Attribute).getName() or name = e.(Name).getId() | + name = "print" or + name = "write" or + name = "append" or + name = "pop" or + name = "remove" or + name = "discard" or + name = "delete" or + name = "close" or + name = "open" or name = "exit" ) } +predicate call_with_side_effect(Call e) { + e.getAFlowNode() = Value::named("subprocess.call").getACall() + or + e.getAFlowNode() = Value::named("subprocess.check_call").getACall() + or + e.getAFlowNode() = Value::named("subprocess.check_output").getACall() +} + predicate probable_side_effect(Expr e) { // Only consider explicit yields, not artificial ones in comprehensions e instanceof Yield and not exists(Comp c | c.contains(e)) or e instanceof YieldFrom or - e instanceof Call and func_with_side_effects(((Call)e).getFunc()) + e instanceof Call and func_with_side_effects(e.(Call).getFunc()) + or + e instanceof Call and call_with_side_effect(e) } from Assert a, Expr e diff --git a/python/ql/src/Statements/StatementNoEffect.ql b/python/ql/src/Statements/StatementNoEffect.ql index 2e5361bd946..dde0b5d7cde 100644 --- a/python/ql/src/Statements/StatementNoEffect.ql +++ b/python/ql/src/Statements/StatementNoEffect.ql @@ -13,73 +13,75 @@ import python -predicate understood_attribute(Attribute attr, ClassObject cls, ClassObject attr_cls) { - exists(string name | - attr.getName() = name | - attr.getObject().refersTo(_, cls, _) and - cls.attributeRefersTo(name, _, attr_cls, _) +predicate understood_attribute(Attribute attr, ClassValue cls, ClassValue attr_cls) { + exists(string name | attr.getName() = name | + attr.getObject().pointsTo().getClass() = cls and + cls.attr(name).getClass() = attr_cls ) } /* Conservative estimate of whether attribute lookup has a side effect */ predicate side_effecting_attribute(Attribute attr) { - exists(ClassObject cls, ClassObject attr_cls | + exists(ClassValue cls, ClassValue attr_cls | understood_attribute(attr, cls, attr_cls) and side_effecting_descriptor_type(attr_cls) ) } predicate maybe_side_effecting_attribute(Attribute attr) { - not understood_attribute(attr, _, _) and not attr.refersTo(_) + not understood_attribute(attr, _, _) and not attr.pointsTo(_) or side_effecting_attribute(attr) } -predicate side_effecting_descriptor_type(ClassObject descriptor) { +predicate side_effecting_descriptor_type(ClassValue descriptor) { descriptor.isDescriptorType() and - /* Technically all descriptor gets have side effects, - * but some are indicative of a missing call and - * we want to treat them as having no effect. */ - not descriptor = thePyFunctionType() and - not descriptor = theStaticMethodType() and - not descriptor = theClassMethodType() + // Technically all descriptor gets have side effects, + // but some are indicative of a missing call and + // we want to treat them as having no effect. + not descriptor = ClassValue::functionType() and + not descriptor = ClassValue::staticmethod() and + not descriptor = ClassValue::classmethod() } -/** Side effecting binary operators are rare, so we assume they are not +/** + * Side effecting binary operators are rare, so we assume they are not * side-effecting unless we know otherwise. */ predicate side_effecting_binary(Expr b) { - exists(Expr sub, ClassObject cls, string method_name | + exists(Expr sub, ClassValue cls, string method_name | binary_operator_special_method(b, sub, cls, method_name) or comparison_special_method(b, sub, cls, method_name) - | + | method_name = special_method() and - cls.hasAttribute(method_name) - and - not exists(ClassObject declaring | - declaring.declaresAttribute(method_name) - and declaring = cls.getAnImproperSuperType() and - declaring.isBuiltin() and not declaring = theObjectType() + cls.hasAttribute(method_name) and + not exists(ClassValue declaring | + declaring.declaresAttribute(method_name) and + declaring = cls.getASuperType() and + declaring.isBuiltin() and + not declaring = ClassValue::object() ) ) } pragma[nomagic] -private predicate binary_operator_special_method(BinaryExpr b, Expr sub, ClassObject cls, string method_name) { +private predicate binary_operator_special_method( + BinaryExpr b, Expr sub, ClassValue cls, string method_name +) { method_name = special_method() and sub = b.getLeft() and method_name = b.getOp().getSpecialMethodName() and - sub.refersTo(_, cls, _) + sub.pointsTo().getClass() = cls } pragma[nomagic] -private predicate comparison_special_method(Compare b, Expr sub, ClassObject cls, string method_name) { +private predicate comparison_special_method(Compare b, Expr sub, ClassValue cls, string method_name) { exists(Cmpop op | b.compares(sub, op, _) and method_name = op.getSpecialMethodName() ) and - sub.refersTo(_, cls, _) + sub.pointsTo().getClass() = cls } private string special_method() { @@ -89,19 +91,16 @@ private string special_method() { } predicate is_notebook(File f) { - exists(Comment c | - c.getLocation().getFile() = f | + exists(Comment c | c.getLocation().getFile() = f | c.getText().regexpMatch("#\\s*.+\\s*") ) } /** Expression (statement) in a jupyter/ipython notebook */ -predicate in_notebook(Expr e) { - is_notebook(e.getScope().(Module).getFile()) -} +predicate in_notebook(Expr e) { is_notebook(e.getScope().(Module).getFile()) } -FunctionObject assertRaises() { - result = ModuleObject::named("unittest").attr("TestCase").(ClassObject).lookupAttribute("assertRaises") +FunctionValue assertRaises() { + result = Value::named("unittest.TestCase").(ClassValue).lookup("assertRaises") } /** Holds if expression `e` is in a `with` block that tests for exceptions being raised. */ @@ -121,14 +120,11 @@ predicate python2_print(Expr e) { } predicate no_effect(Expr e) { + // strings can be used as comments not e instanceof StrConst and - not ((StrConst)e).isDocString() and not e.hasSideEffects() and - forall(Expr sub | - sub = e.getASubExpression*() - | - not side_effecting_binary(sub) - and + forall(Expr sub | sub = e.getASubExpression*() | + not side_effecting_binary(sub) and not maybe_side_effecting_attribute(sub) ) and not in_notebook(e) and @@ -139,4 +135,3 @@ predicate no_effect(Expr e) { from ExprStmt stmt where no_effect(stmt.getValue()) select stmt, "This statement has no effect." - diff --git a/python/ql/src/Statements/StringConcatenationInLoop.qhelp b/python/ql/src/Statements/StringConcatenationInLoop.qhelp index 8d8f494ddd0..b1350e70478 100644 --- a/python/ql/src/Statements/StringConcatenationInLoop.qhelp +++ b/python/ql/src/Statements/StringConcatenationInLoop.qhelp @@ -3,13 +3,13 @@ "qhelp.dtd"> -

    If you concatenate strings in a loop then the time taken by the loop is quadratic in the number +

    If you concatenate strings in a loop then the time taken by the loop is quadratic in the number of iterations.

    -

    Initialize an empty list before the start of the list. +

    Initialize an empty list before the start of the loop. During the loop append the substrings to the list. At the end of the loop, convert the list to a string by using ''.join(list).

    diff --git a/python/ql/src/Statements/StringConcatenationInLoop.ql b/python/ql/src/Statements/StringConcatenationInLoop.ql index 5ca79a345a5..f225e27cdcd 100644 --- a/python/ql/src/Statements/StringConcatenationInLoop.ql +++ b/python/ql/src/Statements/StringConcatenationInLoop.ql @@ -13,17 +13,16 @@ import python predicate string_concat_in_loop(BinaryExpr b) { - b.getOp() instanceof Add - and - exists(SsaVariable d, SsaVariable u, BinaryExprNode add, ClassObject str_type | - add.getNode() = b and d = u.getAnUltimateDefinition() | - d.getDefinition().(DefinitionNode).getValue() = add and u.getAUse() = add.getAnOperand() and - add.getAnOperand().refersTo(_, str_type, _) and - (str_type = theBytesType() or str_type = theUnicodeType()) + b.getOp() instanceof Add and + exists(SsaVariable d, SsaVariable u, BinaryExprNode add | + add.getNode() = b and d = u.getAnUltimateDefinition() + | + d.getDefinition().(DefinitionNode).getValue() = add and + u.getAUse() = add.getAnOperand() and + add.getAnOperand().pointsTo().getClass() = ClassValue::str() ) } - from BinaryExpr b, Stmt s where string_concat_in_loop(b) and s.getASubExpression() = b select s, "String concatenation in a loop is quadratic in the number of iterations." diff --git a/python/ql/src/Statements/TopLevelPrint.ql b/python/ql/src/Statements/TopLevelPrint.ql index cc56902cd62..d818d80a251 100644 --- a/python/ql/src/Statements/TopLevelPrint.ql +++ b/python/ql/src/Statements/TopLevelPrint.ql @@ -13,10 +13,10 @@ import python - predicate main_eq_name(If i) { exists(Name n, StrConst m, Compare c | - i.getTest() = c and c.getLeft() = n and + i.getTest() = c and + c.getLeft() = n and c.getAComparator() = m and n.getId() = "__name__" and m.getText() = "__main__" @@ -24,12 +24,17 @@ predicate main_eq_name(If i) { } predicate is_print_stmt(Stmt s) { - s instanceof Print or - exists(ExprStmt e, Call c, Name n | e = s and c = e.getValue() and n = c.getFunc() and n.getId() = "print") + s instanceof Print + or + exists(ExprStmt e, Call c, Name n | + e = s and c = e.getValue() and n = c.getFunc() and n.getId() = "print" + ) } from Stmt p -where is_print_stmt(p) and -exists(ModuleObject m | m.getModule() = p.getScope() and m.getKind() = "module") and -not exists(If i | main_eq_name(i) and i.getASubStatement().getASubStatement*() = p) +where + is_print_stmt(p) and + // TODO: Need to discuss how we would like to handle ModuleObject.getKind in the glorious future + exists(ModuleValue m | m.getScope() = p.getScope() and m.isUsedAsModule()) and + not exists(If i | main_eq_name(i) and i.getASubStatement().getASubStatement*() = p) select p, "Print statement may execute during import." diff --git a/python/ql/src/Statements/UnnecessaryDelete.ql b/python/ql/src/Statements/UnnecessaryDelete.ql index fbe196e9fc1..d10bcc2ed20 100644 --- a/python/ql/src/Statements/UnnecessaryDelete.ql +++ b/python/ql/src/Statements/UnnecessaryDelete.ql @@ -12,7 +12,6 @@ * @id py/unnecessary-delete */ - import python from Delete del, Expr e, Function f @@ -23,11 +22,11 @@ where not e instanceof Subscript and not e instanceof Attribute and not exists(Stmt s | s.(While).contains(del) or s.(For).contains(del)) and - /* False positive: calling `sys.exc_info` within a function results in a - reference cycle,and an explicit call to `del` helps break this cycle. */ - not exists(FunctionObject ex | - ex.hasLongName("sys.exc_info") and + // False positive: calling `sys.exc_info` within a function results in a + // reference cycle, and an explicit call to `del` helps break this cycle. + not exists(FunctionValue ex | + ex = Value::named("sys.exc_info") and ex.getACall().getScope() = f ) -select del, "Unnecessary deletion of local variable $@ in function $@.", - e.getLocation(), e.toString(), f.getLocation(), f.getName() \ No newline at end of file +select del, "Unnecessary deletion of local variable $@ in function $@.", e.getLocation(), + e.toString(), f.getLocation(), f.getName() diff --git a/python/ql/src/Statements/UnnecessaryElseClause.ql b/python/ql/src/Statements/UnnecessaryElseClause.ql index cfb93a7c0b7..8884b06e740 100644 --- a/python/ql/src/Statements/UnnecessaryElseClause.ql +++ b/python/ql/src/Statements/UnnecessaryElseClause.ql @@ -14,9 +14,11 @@ import python from Stmt loop, StmtList body, StmtList clause, string kind where -(exists(For f | f = loop | clause = f.getOrelse() and body = f.getBody() and kind = "for") - or - exists(While w | w = loop | clause = w.getOrelse() and body = w.getBody() and kind = "while") -) -and not exists(Break b | body.contains(b)) -select loop, "This '" + kind + "' statement has a redundant 'else' as no 'break' is present in the body." + ( + exists(For f | f = loop | clause = f.getOrelse() and body = f.getBody() and kind = "for") + or + exists(While w | w = loop | clause = w.getOrelse() and body = w.getBody() and kind = "while") + ) and + not exists(Break b | body.contains(b)) +select loop, + "This '" + kind + "' statement has a redundant 'else' as no 'break' is present in the body." diff --git a/python/ql/src/Statements/UnnecessaryPass.ql b/python/ql/src/Statements/UnnecessaryPass.ql index d98aa947236..fe0e0171930 100644 --- a/python/ql/src/Statements/UnnecessaryPass.ql +++ b/python/ql/src/Statements/UnnecessaryPass.ql @@ -13,21 +13,20 @@ import python predicate is_doc_string(ExprStmt s) { - s.getValue() instanceof Unicode or s.getValue() instanceof Bytes + s.getValue() instanceof Unicode or s.getValue() instanceof Bytes } predicate has_doc_string(StmtList stmts) { - stmts.getParent() instanceof Scope - and + stmts.getParent() instanceof Scope and is_doc_string(stmts.getItem(0)) } from Pass p, StmtList list -where list.getAnItem() = p and -( - strictcount(list.getAnItem()) = 2 and not has_doc_string(list) - or - strictcount(list.getAnItem()) > 2 -) +where + list.getAnItem() = p and + ( + strictcount(list.getAnItem()) = 2 and not has_doc_string(list) + or + strictcount(list.getAnItem()) > 2 + ) select p, "Unnecessary 'pass' statement." - diff --git a/python/ql/src/Statements/UnusedExceptionObject.ql b/python/ql/src/Statements/UnusedExceptionObject.ql index be848ad69c3..32b59113c5b 100644 --- a/python/ql/src/Statements/UnusedExceptionObject.ql +++ b/python/ql/src/Statements/UnusedExceptionObject.ql @@ -12,8 +12,9 @@ import python -from Call call, ClassObject ex -where call.getFunc().refersTo(ex) and ex.getAnImproperSuperType() = theExceptionType() -and exists(ExprStmt s | s.getValue() = call) - +from Call call, ClassValue ex +where + call.getFunc().pointsTo(ex) and + ex.getASuperType() = ClassValue::exception() and + exists(ExprStmt s | s.getValue() = call) select call, "Instantiating an exception, but not raising it, has no effect" diff --git a/python/ql/src/Statements/UseOfExit.ql b/python/ql/src/Statements/UseOfExit.ql index c9f22605556..31e0e51ab39 100644 --- a/python/ql/src/Statements/UseOfExit.ql +++ b/python/ql/src/Statements/UseOfExit.ql @@ -12,5 +12,7 @@ import python from CallNode call, string name -where call.getFunction().refersTo(Object::quitter(name)) -select call, "The '" + name + "' site.Quitter object may not exist if the 'site' module is not loaded or is modified." +where call.getFunction().pointsTo(Value::siteQuitter(name)) +select call, + "The '" + name + + "' site.Quitter object may not exist if the 'site' module is not loaded or is modified." diff --git a/python/ql/src/Variables/MonkeyPatched.qll b/python/ql/src/Variables/MonkeyPatched.qll index 41e19a425f2..2a846e3deb1 100644 --- a/python/ql/src/Variables/MonkeyPatched.qll +++ b/python/ql/src/Variables/MonkeyPatched.qll @@ -5,7 +5,7 @@ predicate monkey_patched_builtin(string name) { subscr.isStore() and subscr.getIndex().getNode() = s and s.getText() = name and - subscr.getValue() = attr and + subscr.getObject() = attr and attr.getObject("__dict__").pointsTo(Module::builtinModule()) ) or diff --git a/python/ql/src/experimental/README.md b/python/ql/src/experimental/README.md new file mode 100644 index 00000000000..fc07363b24f --- /dev/null +++ b/python/ql/src/experimental/README.md @@ -0,0 +1 @@ +This directory contains [experimental](../../../../docs/experimental.md) CodeQL queries and libraries. diff --git a/python/ql/src/semmle/python/Flow.qll b/python/ql/src/semmle/python/Flow.qll index c81e5e040e4..370ced23287 100755 --- a/python/ql/src/semmle/python/Flow.qll +++ b/python/ql/src/semmle/python/Flow.qll @@ -910,6 +910,10 @@ private AstNode assigned_value(Expr lhs) { predicate nested_sequence_assign(Expr left_parent, Expr right_parent, Expr left_result, Expr right_result) { + exists(Assign a | + a.getATarget().getASubExpression*() = left_parent and + a.getValue().getASubExpression*() = right_parent + ) and exists(int i, Expr left_elem, Expr right_elem | ( diff --git a/python/ql/src/semmle/python/dataflow/Implementation.qll b/python/ql/src/semmle/python/dataflow/Implementation.qll index b2326f2d100..9f7d44bdfb6 100644 --- a/python/ql/src/semmle/python/dataflow/Implementation.qll +++ b/python/ql/src/semmle/python/dataflow/Implementation.qll @@ -726,6 +726,8 @@ private class EssaTaintTracking extends string { private TaintKind iterable_unpacking_descent( SequenceNode left_parent, ControlFlowNode left_defn, CollectionKind parent_kind ) { + //TODO: Fix the cartesian product in this predicate + none() and left_parent.getAnElement() = left_defn and // Handle `a, *b = some_iterable` if left_defn instanceof StarredNode diff --git a/python/ql/src/semmle/python/dataflow/TaintTracking.qll b/python/ql/src/semmle/python/dataflow/TaintTracking.qll index 6ef9e5ae3e8..88ef3c4e4fc 100755 --- a/python/ql/src/semmle/python/dataflow/TaintTracking.qll +++ b/python/ql/src/semmle/python/dataflow/TaintTracking.qll @@ -286,14 +286,14 @@ module DictKind { pragma[noinline] private predicate subscript_index(ControlFlowNode obj, SubscriptNode sub) { sub.isLoad() and - sub.getValue() = obj and + sub.getObject() = obj and not sub.getNode().getIndex() instanceof Slice } pragma[noinline] private predicate subscript_slice(ControlFlowNode obj, SubscriptNode sub) { sub.isLoad() and - sub.getValue() = obj and + sub.getObject() = obj and sub.getNode().getIndex() instanceof Slice } diff --git a/python/ql/src/semmle/python/objects/Descriptors.qll b/python/ql/src/semmle/python/objects/Descriptors.qll index eb6859f0c0f..dbc3e59afa5 100644 --- a/python/ql/src/semmle/python/objects/Descriptors.qll +++ b/python/ql/src/semmle/python/objects/Descriptors.qll @@ -20,13 +20,39 @@ class PropertyInternal extends ObjectInternal, TProperty { this = TProperty(_, _, result) } + private CallNode getCallNode() { this = TProperty(result, _, _) } + /** Gets the setter function of this property */ CallableObjectInternal getSetter() { + // @x.setter exists(CallNode call, AttrNode setter | - call.getFunction() = setter and + call.getFunction() = setter and PointsToInternal::pointsTo(setter.getObject("setter"), this.getContext(), this, _) and PointsToInternal::pointsTo(call.getArg(0), this.getContext(), result, _) ) + or + // x = property(getter, setter, deleter) + exists(ControlFlowNode setter_arg | + setter_arg = getCallNode().getArg(1) or setter_arg = getCallNode().getArgByName("fset") + | + PointsToInternal::pointsTo(setter_arg, this.getContext(), result, _) + ) + } + + /** Gets the setter function of this property */ + CallableObjectInternal getDeleter() { + exists(CallNode call, AttrNode setter | + call.getFunction() = setter and + PointsToInternal::pointsTo(setter.getObject("deleter"), this.getContext(), this, _) and + PointsToInternal::pointsTo(call.getArg(0), this.getContext(), result, _) + ) + or + // x = property(getter, setter, deleter) + exists(ControlFlowNode deleter_arg | + deleter_arg = getCallNode().getArg(2) or deleter_arg = getCallNode().getArgByName("fdel") + | + PointsToInternal::pointsTo(deleter_arg, this.getContext(), result, _) + ) } private Context getContext() { this = TProperty(_,result, _) } diff --git a/python/ql/src/semmle/python/objects/ObjectAPI.qll b/python/ql/src/semmle/python/objects/ObjectAPI.qll index 1f1f7001da1..c3410457b6f 100644 --- a/python/ql/src/semmle/python/objects/ObjectAPI.qll +++ b/python/ql/src/semmle/python/objects/ObjectAPI.qll @@ -187,6 +187,37 @@ class ModuleValue extends Value { result.importedAs(this.getScope().getAnImportedModuleName()) } + /** When used as a normal module (for example, imported and used by other modules) */ + predicate isUsedAsModule() { + this.isBuiltin() + or + this.isPackage() + or + exists(ImportingStmt i | this.importedAs(i.getAnImportedModuleName())) + or + this.getPath().getBaseName() = "__init__.py" + } + + /** When used (exclusively) as a script (will not include normal modules that can also be run as a script) */ + predicate isUsedAsScript() { + not isUsedAsModule() and + ( + not this.getPath().getExtension() = "py" + or + exists(If i, Name name, StrConst main, Cmpop op | + i.getScope() = this.getScope() and + op instanceof Eq and + i.getTest().(Compare).compares(name, op, main) and + name.getId() = "__name__" and main.getText() = "__main__" + ) + or + exists(Comment c | + c.getLocation().getFile() = this.getPath() and + c.getLocation().getStartLine() = 1 and + c.getText().regexpMatch("^#!/.*python(2|3)?[ \\\\t]*$") + ) + ) + } } module Module { @@ -301,6 +332,19 @@ module Value { result = ObjectInternal::none_() } + /** + * Shorcuts added by the `site` module to exit your interactive session. + * + * see https://docs.python.org/3/library/constants.html#constants-added-by-the-site-module + */ + Value siteQuitter(string name) { + ( + name = "exit" + or + name = "quit" + ) and + result = Value::named(name) + } } /** Class representing callables in the Python program @@ -418,6 +462,12 @@ class ClassValue extends Value { this.hasAttribute("__get__") } + /** Holds if this class is a context manager. */ + predicate isContextManager() { + this.hasAttribute("__enter__") and + this.hasAttribute("__exit__") + } + /** Gets the qualified name for this class. * Should return the same name as the `__qualname__` attribute on classes in Python 3. */ @@ -482,6 +532,15 @@ class ClassValue extends Value { this.(ClassObjectInternal).getClassDeclaration().declaresAttribute(name) } + /** Whether this class is a legal exception class. + * What constitutes a legal exception class differs between major versions */ + predicate isLegalExceptionType() { + not this.isNewStyle() or + this.getASuperType() = ClassValue::baseException() + or + major_version() = 2 and this = ClassValue::tuple() + } + } @@ -505,6 +564,28 @@ abstract class FunctionValue extends CallableValue { predicate isOverriddenMethod() { exists(Value f | f.overrides(this)) } + + /** Whether `name` is a legal argument name for this function */ + bindingset[name] + predicate isLegalArgumentName(string name) { + this.getScope().getAnArg().asName().getId() = name + or + this.getScope().getAKeywordOnlyArg().getId() = name + or + this.getScope().hasKwArg() + } + + /** Whether this is a "normal" method, that is, it is exists as a class attribute + * which is not a lambda and not the __new__ method. */ + predicate isNormalMethod() { + exists(ClassValue cls, string name | + cls.declaredAttribute(name) = this and + name != "__new__" and + exists(Expr expr, AstNode origin | expr.pointsTo(this, origin) | + not origin instanceof Lambda + ) + ) + } } /** Class representing Python functions */ @@ -650,6 +731,34 @@ class NumericValue extends Value { } } +/** A Python property: + * @property + * def f(): + * .... + * + * https://docs.python.org/3/howto/descriptor.html#properties + * https://docs.python.org/3/library/functions.html#property + */ +class PropertyValue extends Value { + + PropertyValue() { + this instanceof PropertyInternal + } + + CallableValue getGetter(){ + result = this.(PropertyInternal).getGetter() + } + + CallableValue getSetter(){ + result = this.(PropertyInternal).getSetter() + } + + CallableValue getDeleter(){ + result = this.(PropertyInternal).getDeleter() + } + +} + /** A method-resolution-order sequence of classes */ class MRO extends TClassList { @@ -698,34 +807,34 @@ 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 `set` class. */ ClassValue set() { result = TBuiltinClassObject(Builtin::special("set")) } - + /** Get the `ClassValue` for the `object` class. */ ClassValue object() { result = TBuiltinClassObject(Builtin::special("object")) @@ -735,7 +844,7 @@ module ClassValue { ClassValue int_() { result = TBuiltinClassObject(Builtin::special("int")) } - + /** Get the `ClassValue` for the `long` class. */ ClassValue long() { result = TBuiltinClassObject(Builtin::special("long")) @@ -745,7 +854,7 @@ module ClassValue { ClassValue float_() { result = TBuiltinClassObject(Builtin::special("float")) } - + /** Get the `ClassValue` for the `complex` class. */ ClassValue complex() { result = TBuiltinClassObject(Builtin::special("complex")) @@ -770,12 +879,12 @@ 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")) @@ -785,32 +894,32 @@ module ClassValue { 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")) @@ -820,26 +929,26 @@ 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")) + result = TBuiltinClassObject(Builtin::builtin("StopIteration")) } /** Get the `ClassValue` for the class of modules. */ @@ -851,17 +960,17 @@ module ClassValue { 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")) @@ -871,22 +980,27 @@ module ClassValue { 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 `LookupError` class. */ + ClassValue lookupError() { + result = TBuiltinClassObject(Builtin::builtin("LookupError")) + } + /** 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")) @@ -897,4 +1011,14 @@ module ClassValue { result = TBuiltinClassObject(Builtin::builtin("ImportError")) } + /** Get the `ClassValue` for the `UnicodeEncodeError` class. */ + ClassValue unicodeEncodeError() { + result = TBuiltinClassObject(Builtin::builtin("UnicodeEncodeError")) + } + + /** Get the `ClassValue` for the `UnicodeDecodeError` class. */ + ClassValue unicodeDecodeError() { + result = TBuiltinClassObject(Builtin::builtin("UnicodeDecodeError")) + } + } diff --git a/python/ql/src/semmle/python/regex.qll b/python/ql/src/semmle/python/regex.qll index 9e3da135f32..2e83d8fca35 100644 --- a/python/ql/src/semmle/python/regex.qll +++ b/python/ql/src/semmle/python/regex.qll @@ -12,25 +12,27 @@ private predicate re_module_function(string name, int flags) { name = "subn" and flags = 4 } +/** + * Holds if `s` is used as a regex with the `re` module, with the regex-mode `mode` (if known). + * If regex mode is not known, `mode` will be `"None"`. + */ predicate used_as_regex(Expr s, string mode) { (s instanceof Bytes or s instanceof Unicode) and - exists(ModuleValue re | re.getName() = "re" | - /* Call to re.xxx(regex, ... [mode]) */ - exists(CallNode call, string name | - call.getArg(0).refersTo(_, _, s.getAFlowNode()) and - call.getFunction().pointsTo(re.attr(name)) | - mode = "None" - or - exists(Value obj | - mode = mode_from_mode_object(obj) | - exists(int flags_arg | - re_module_function(name, flags_arg) and - call.getArg(flags_arg).pointsTo(obj) - ) - or - call.getArgByName("flags").pointsTo(obj) + /* Call to re.xxx(regex, ... [mode]) */ + exists(CallNode call, string name | + call.getArg(0).refersTo(_, _, s.getAFlowNode()) and + call.getFunction().pointsTo(Module::named("re").attr(name)) | + mode = "None" + or + exists(Value obj | + mode = mode_from_mode_object(obj) | + exists(int flags_arg | + re_module_function(name, flags_arg) and + call.getArg(flags_arg).pointsTo(obj) ) + or + call.getArgByName("flags").pointsTo(obj) ) ) } diff --git a/python/ql/src/semmle/python/security/strings/Basic.qll b/python/ql/src/semmle/python/security/strings/Basic.qll index 72361657676..9dda9c59729 100755 --- a/python/ql/src/semmle/python/security/strings/Basic.qll +++ b/python/ql/src/semmle/python/security/strings/Basic.qll @@ -104,7 +104,7 @@ private predicate slice(ControlFlowNode fromnode, SubscriptNode tonode) { exists(Slice all | all = tonode.getIndex().getNode() and not exists(all.getStart()) and not exists(all.getStop()) and - tonode.getValue() = fromnode + tonode.getObject() = fromnode ) } diff --git a/python/ql/src/semmle/python/security/strings/External.qll b/python/ql/src/semmle/python/security/strings/External.qll index bd567f54f24..fca73a2f0b2 100644 --- a/python/ql/src/semmle/python/security/strings/External.qll +++ b/python/ql/src/semmle/python/security/strings/External.qll @@ -2,45 +2,40 @@ import python import Basic private import Common -/** An extensible kind of taint representing an externally controlled string. +/** + * An extensible kind of taint representing an externally controlled string. */ abstract class ExternalStringKind extends StringKind { - bindingset[this] - ExternalStringKind() { - this = this - } + ExternalStringKind() { this = this } override TaintKind getTaintForFlowStep(ControlFlowNode fromnode, ControlFlowNode tonode) { result = StringKind.super.getTaintForFlowStep(fromnode, tonode) or - tonode.(SequenceNode).getElement(_) = fromnode and result.(ExternalStringSequenceKind).getItem() = this + tonode.(SequenceNode).getElement(_) = fromnode and + result.(ExternalStringSequenceKind).getItem() = this or json_load(fromnode, tonode) and result.(ExternalJsonKind).getValue() = this or tonode.(DictNode).getAValue() = fromnode and result.(ExternalStringDictKind).getValue() = this + or + urlsplit(fromnode, tonode) and result.(ExternalUrlSplitResult).getItem() = this + or + urlparse(fromnode, tonode) and result.(ExternalUrlParseResult).getItem() = this } - } /** A kind of "taint", representing a sequence, with a "taint" member */ class ExternalStringSequenceKind extends SequenceKind { - - ExternalStringSequenceKind() { - this.getItem() instanceof ExternalStringKind - } - + ExternalStringSequenceKind() { this.getItem() instanceof ExternalStringKind } } -/** An hierachical dictionary or list where the entire structure is externally controlled +/** + * An hierachical dictionary or list where the entire structure is externally controlled * This is typically a parsed JSON object. */ class ExternalJsonKind extends TaintKind { - - ExternalJsonKind() { - this = "json[" + any(ExternalStringKind key) + "]" - } - + ExternalJsonKind() { this = "json[" + any(ExternalStringKind key) + "]" } /** Gets the taint kind for item in this sequence */ TaintKind getValue() { @@ -54,65 +49,225 @@ class ExternalJsonKind extends TaintKind { json_subscript_taint(tonode, fromnode, this, result) or result = this and copy_call(fromnode, tonode) - } + } override TaintKind getTaintOfMethodResult(string name) { name = "get" and result = this.getValue() - } - + } } /** A kind of "taint", representing a dictionary mapping str->"taint" */ class ExternalStringDictKind extends DictKind { - - ExternalStringDictKind() { - this.getValue() instanceof ExternalStringKind - } - + ExternalStringDictKind() { this.getValue() instanceof ExternalStringKind } } -/** A kind of "taint", representing a dictionary mapping strings to sequences of - * tainted strings */ - +/** + * A kind of "taint", representing a dictionary mapping strings to sequences of + * tainted strings + */ class ExternalStringSequenceDictKind extends DictKind { - ExternalStringSequenceDictKind() { - this.getValue() instanceof ExternalStringSequenceKind + ExternalStringSequenceDictKind() { this.getValue() instanceof ExternalStringSequenceKind } +} + +/** TaintKind for the result of `urlsplit(tainted_string)` */ +class ExternalUrlSplitResult extends ExternalStringSequenceKind { + // https://docs.python.org/3/library/urllib.parse.html#urllib.parse.urlsplit + override TaintKind getTaintOfAttribute(string name) { + result = super.getTaintOfAttribute(name) + or + ( + // namedtuple field names + name = "scheme" or + name = "netloc" or + name = "path" or + name = "query" or + name = "fragment" or + // class methods + name = "username" or + name = "password" or + name = "hostname" + ) and + result instanceof ExternalStringKind + } + + override TaintKind getTaintOfMethodResult(string name) { + result = super.getTaintOfMethodResult(name) + or + name = "geturl" and + result instanceof ExternalStringKind + } +} + +/** TaintKind for the result of `urlparse(tainted_string)` */ +class ExternalUrlParseResult extends ExternalStringSequenceKind { + // https://docs.python.org/3/library/urllib.parse.html#urllib.parse.urlparse + override TaintKind getTaintOfAttribute(string name) { + result = super.getTaintOfAttribute(name) + or + ( + // namedtuple field names + name = "scheme" or + name = "netloc" or + name = "path" or + name = "params" or + name = "query" or + name = "fragment" or + // class methods + name = "username" or + name = "password" or + name = "hostname" + ) and + result instanceof ExternalStringKind + } + + override TaintKind getTaintOfMethodResult(string name) { + result = super.getTaintOfMethodResult(name) + or + name = "geturl" and + result instanceof ExternalStringKind } } /* Helper for getTaintForStep() */ -pragma [noinline] -private predicate json_subscript_taint(SubscriptNode sub, ControlFlowNode obj, ExternalJsonKind seq, TaintKind key) { +pragma[noinline] +private predicate json_subscript_taint( + SubscriptNode sub, ControlFlowNode obj, ExternalJsonKind seq, TaintKind key +) { sub.isLoad() and - sub.getValue() = obj and + sub.getObject() = obj and key = seq.getValue() } - private predicate json_load(ControlFlowNode fromnode, CallNode tonode) { exists(FunctionObject json_loads | ModuleObject::named("json").attr("loads") = json_loads and - json_loads.getACall() = tonode and tonode.getArg(0) = fromnode + json_loads.getACall() = tonode and + tonode.getArg(0) = fromnode + ) +} + +private predicate urlsplit(ControlFlowNode fromnode, CallNode tonode) { + // This could be implemented as `exists(FunctionValue` without the explicit six part, + // but then our tests will need to import +100 modules, so for now this slightly + // altered version gets to live on. + exists(Value urlsplit | + ( + urlsplit = Value::named("six.moves.urllib.parse.urlsplit") + or + // Python 2 + urlsplit = Value::named("urlparse.urlsplit") + or + // Python 3 + urlsplit = Value::named("urllib.parse.urlsplit") + ) and + tonode = urlsplit.getACall() and + tonode.getArg(0) = fromnode + ) +} + +private predicate urlparse(ControlFlowNode fromnode, CallNode tonode) { + // This could be implemented as `exists(FunctionValue` without the explicit six part, + // but then our tests will need to import +100 modules, so for now this slightly + // altered version gets to live on. + exists(Value urlparse | + ( + urlparse = Value::named("six.moves.urllib.parse.urlparse") + or + // Python 2 + urlparse = Value::named("urlparse.urlparse") + or + // Python 3 + urlparse = Value::named("urllib.parse.urlparse") + ) and + tonode = urlparse.getACall() and + tonode.getArg(0) = fromnode ) } /** A kind of "taint", representing an open file-like object from an external source. */ class ExternalFileObject extends TaintKind { - - ExternalFileObject() { - this = "file[" + any(ExternalStringKind key) + "]" - } - + ExternalFileObject() { this = "file[" + any(ExternalStringKind key) + "]" } /** Gets the taint kind for the contents of this file */ - TaintKind getValue() { - this = "file[" + result + "]" - } + TaintKind getValue() { this = "file[" + result + "]" } override TaintKind getTaintOfMethodResult(string name) { name = "read" and result = this.getValue() } - } +/** + * Temporary sanitizer for the tainted result from `urlsplit` and `urlparse`. Can be used to reduce FPs until + * we have better support for namedtuples. + * + * Will clear **all** taint on a test of the kind. That is, on the true edge of any matching test, + * all fields/indexes will be cleared of taint. + * + * Handles: + * - `if splitres.netloc == "KNOWN_VALUE"` + * - `if splitres[0] == "KNOWN_VALUE"` + */ +class UrlsplitUrlparseTempSanitizer extends Sanitizer { + // TODO: remove this once we have better support for named tuples + UrlsplitUrlparseTempSanitizer() { this = "UrlsplitUrlparseTempSanitizer" } + + override predicate sanitizingEdge(TaintKind taint, PyEdgeRefinement test) { + ( + taint instanceof ExternalUrlSplitResult + or + taint instanceof ExternalUrlParseResult + ) and + exists(ControlFlowNode full_use | + full_use.(SubscriptNode).getObject() = test.getInput().getAUse() + or + full_use.(AttrNode).getObject() = test.getInput().getAUse() + | + clears_taint(full_use, test.getTest(), test.getSense()) + ) + } + + private predicate clears_taint(ControlFlowNode tainted, ControlFlowNode test, boolean sense) { + test_equality_with_const(test, tainted, sense) + or + test_in_const_seq(test, tainted, sense) + or + test.(UnaryExprNode).getNode().getOp() instanceof Not and + exists(ControlFlowNode nested_test | + nested_test = test.(UnaryExprNode).getOperand() and + clears_taint(tainted, nested_test, sense.booleanNot()) + ) + } + + /** holds for `== "KNOWN_VALUE"` on `true` edge, and `!= "KNOWN_VALUE"` on `false` edge */ + private predicate test_equality_with_const(CompareNode cmp, ControlFlowNode tainted, boolean sense) { + exists(ControlFlowNode const, Cmpop op | + const.getNode() instanceof StrConst + | + ( + cmp.operands(const, op, tainted) + or + cmp.operands(tainted, op, const) + ) and + ( + op instanceof Eq and sense = true + or + op instanceof NotEq and sense = false + ) + ) + } + + /** holds for `in ["KNOWN_VALUE", ...]` on `true` edge, and `not in ["KNOWN_VALUE", ...]` on `false` edge */ + private predicate test_in_const_seq(CompareNode cmp, ControlFlowNode tainted, boolean sense) { + exists(SequenceNode const_seq, Cmpop op | + forall(ControlFlowNode elem | elem = const_seq.getAnElement() | elem.getNode() instanceof StrConst) + | + cmp.operands(tainted, op, const_seq) and + ( + op instanceof In and sense = true + or + op instanceof NotIn and sense = false + ) + ) + } +} diff --git a/python/ql/src/semmle/python/values/StringAttributes.qll b/python/ql/src/semmle/python/values/StringAttributes.qll index 30c1de0560c..248a11f6ae2 100644 --- a/python/ql/src/semmle/python/values/StringAttributes.qll +++ b/python/ql/src/semmle/python/values/StringAttributes.qll @@ -82,7 +82,7 @@ private predicate tracking_step(ControlFlowNode src, ControlFlowNode dest) { or src = dest.(AttrNode).getObject() or - src = dest.(SubscriptNode).getValue() + src = dest.(SubscriptNode).getObject() or tracked_call_step(src, dest) or diff --git a/python/ql/src/semmle/python/web/Http.qll b/python/ql/src/semmle/python/web/Http.qll index 80b8885d62e..e12cb6abe96 100644 --- a/python/ql/src/semmle/python/web/Http.qll +++ b/python/ql/src/semmle/python/web/Http.qll @@ -26,7 +26,7 @@ class WsgiEnvironment extends TaintKind { tonode.(CallNode).getFunction().(AttrNode).getObject("get") = fromnode and tonode.(CallNode).getArg(0).pointsTo(key) or - tonode.(SubscriptNode).getValue() = fromnode and tonode.isLoad() and + tonode.(SubscriptNode).getObject() = fromnode and tonode.isLoad() and tonode.(SubscriptNode).getIndex().pointsTo(key) | key = Value::forString(text) and result instanceof ExternalStringKind and @@ -66,7 +66,7 @@ class UntrustedCookie extends TaintKind { } override TaintKind getTaintForFlowStep(ControlFlowNode fromnode, ControlFlowNode tonode) { - tonode.(SubscriptNode).getValue() = fromnode and + tonode.(SubscriptNode).getObject() = fromnode and result instanceof UntrustedMorsel } diff --git a/python/ql/src/semmle/python/web/django/Request.qll b/python/ql/src/semmle/python/web/django/Request.qll index 24a29ea4542..cd1bccd5f7f 100644 --- a/python/ql/src/semmle/python/web/django/Request.qll +++ b/python/ql/src/semmle/python/web/django/Request.qll @@ -21,7 +21,7 @@ class DjangoRequest extends TaintKind { /* Helper for getTaintForStep() */ pragma[noinline] private predicate subscript_taint(SubscriptNode sub, ControlFlowNode obj, TaintKind kind) { - sub.getValue() = obj and + sub.getObject() = obj and kind instanceof ExternalStringKind } diff --git a/python/ql/test/2/library-tests/six/options b/python/ql/test/2/library-tests/six/options index 55e896b67f2..573a285f101 100644 --- a/python/ql/test/2/library-tests/six/options +++ b/python/ql/test/2/library-tests/six/options @@ -1,2 +1 @@ semmle-extractor-options: --lang=2 --max-import-depth=4 -optimize: true diff --git a/python/ql/test/2/query-tests/Expressions/TruncatedDivision.expected b/python/ql/test/2/query-tests/Expressions/TruncatedDivision.expected index 67b76d0a86e..b22b9b5a2f2 100644 --- a/python/ql/test/2/query-tests/Expressions/TruncatedDivision.expected +++ b/python/ql/test/2/query-tests/Expressions/TruncatedDivision.expected @@ -1,2 +1,2 @@ -| TruncatedDivision_test.py:8:12:8:16 | BinaryExpr | Result of division may be truncated as its $@ and $@ arguments may both be integers. | TruncatedDivision_test.py:8:12:8:12 | TruncatedDivision_test.py:8 | left | TruncatedDivision_test.py:8:16:8:16 | TruncatedDivision_test.py:8 | right | -| TruncatedDivision_test.py:11:12:11:40 | BinaryExpr | Result of division may be truncated as its $@ and $@ arguments may both be integers. | TruncatedDivision_test.py:2:12:2:12 | TruncatedDivision_test.py:2 | left | TruncatedDivision_test.py:5:12:5:12 | TruncatedDivision_test.py:5 | right | +| TruncatedDivision_test.py:65:7:65:11 | BinaryExpr | Result of division may be truncated as its $@ and $@ arguments may both be integers. | TruncatedDivision_test.py:65:7:65:7 | TruncatedDivision_test.py:65 | left | TruncatedDivision_test.py:65:11:65:11 | TruncatedDivision_test.py:65 | right | +| TruncatedDivision_test.py:72:7:72:35 | BinaryExpr | Result of division may be truncated as its $@ and $@ arguments may both be integers. | TruncatedDivision_test.py:25:12:25:12 | TruncatedDivision_test.py:25 | left | TruncatedDivision_test.py:28:12:28:12 | TruncatedDivision_test.py:28 | right | diff --git a/python/ql/test/2/query-tests/Expressions/TruncatedDivision_test.py b/python/ql/test/2/query-tests/Expressions/TruncatedDivision_test.py index 1c9d85e0fbd..6f265b824fb 100644 --- a/python/ql/test/2/query-tests/Expressions/TruncatedDivision_test.py +++ b/python/ql/test/2/query-tests/Expressions/TruncatedDivision_test.py @@ -1,24 +1,88 @@ +#### TruncatedDivision.ql + +# NOTE: The following test case will only work under Python 2. + +# Truncated division occurs when two integers are divided. This causes the +# fractional part, if there is one, to be discared. So for example, `2 / 3` will +# evaluate to `0` instead of `0.666...`. + + + + + +## Negative Cases + + + +# This case is good, and is a minimal obvious case that should be good. It +# SHOULD NOT be found by the query. +print(3.0 / 2.0) + +# This case is good, because it explicitly converts the possibly-truncated +# value to an integer. It SHOULD NOT be found by the query. + def return_three(): return 3 def return_two(): return 2 -def f1(): - return 3 / 2 +print(int(return_three() / return_two())) -def f2(): - return return_three() / return_two() -def f3(x): + +# These cases are good, because `halve` checks the type, and if the type would +# truncate, it explicitly converts to a float first before doing the division. +# These SHOULD NOT be found by the query. + +def halve(x): if isinstance(x, float): return x / 2 else: return (1.0 * x) / 2 -def f4(): - do_stuff(f3(1)) - do_stuff(f3(1.0)) +print(halve(1)) +print(halve(1.0)) -def f5(): - return int(return_three() / return_two()) + + +# This case is good, because the sum is `3.0`, which is a float, and will not +# truncate. This case SHOULD NOT be found by the query. + +print(average([1.0, 2.0])) + + + + + +## Positive Cases + + + +# This case is bad, and is a minimal obvious case that should be bad. It +# SHOULD be found by the query. + +print(3 / 2) + + + +# This case is bad. It uses indirect returns of integers through function calls +# to produce the problem. I + +print(return_three() / return_two()) + + + +# This case is bad, because the sum is `3`, which is an integer, and will +# truncate when divided by the length `2`. This case SHOULD be found by the +# query. + +# NOTE (2020-02-20): +# The current version of the Value/pointsTo API doesn't permit this detection, +# unfortunately, but we preserve this example in the hopes that future +# versions will catch it. That will necessitate changing the expected results. + +def average(l): + return sum(l) / len(l) + +print(average([1,2])) diff --git a/python/ql/test/2/query-tests/Expressions/UseofApply.expected b/python/ql/test/2/query-tests/Expressions/UseofApply.expected index 082296ef25c..aac8fbcda88 100644 --- a/python/ql/test/2/query-tests/Expressions/UseofApply.expected +++ b/python/ql/test/2/query-tests/Expressions/UseofApply.expected @@ -1 +1,2 @@ +| UseofApply.py:19:3:19:17 | ControlFlowNode for apply() | Call to the obsolete builtin function 'apply'. | | expressions_test.py:3:5:3:21 | ControlFlowNode for apply() | Call to the obsolete builtin function 'apply'. | diff --git a/python/ql/test/2/query-tests/Expressions/UseofApply.py b/python/ql/test/2/query-tests/Expressions/UseofApply.py new file mode 100644 index 00000000000..9109636f99e --- /dev/null +++ b/python/ql/test/2/query-tests/Expressions/UseofApply.py @@ -0,0 +1,30 @@ +#### UseofApply.ql + +# Use of the builtin function `apply` is generally considered bad now that the +# ability to destructure lists of arguments is possible, but we should not flag +# cases where the function is merely named `apply` rather than being the actual +# builtin `apply` function. + +def useofapply(): + + def foo(): + pass + + + + # Positive Cases + + # This use of `apply` is a reference to the builtin function and so SHOULD be + # caught by the query. + apply(foo, [1]) + + + + # Negative Cases + + # This use of `apply` is a reference to the locally defined function inside of + # `local`, and so SHOULD NOT be caught by the query. + def local(): + def apply(f): + pass + apply(foo)([1]) diff --git a/python/ql/test/2/query-tests/Expressions/UseofInput.expected b/python/ql/test/2/query-tests/Expressions/UseofInput.expected index 8f2b1fedd44..470c24df21f 100644 --- a/python/ql/test/2/query-tests/Expressions/UseofInput.expected +++ b/python/ql/test/2/query-tests/Expressions/UseofInput.expected @@ -1 +1 @@ -| expressions_test.py:6:12:6:18 | ControlFlowNode for input() | The unsafe built-in function 'input' is used. | +| expressions_test.py:6:12:6:18 | ControlFlowNode for input() | The unsafe built-in function 'input' is used in Python 2. | diff --git a/python/ql/test/3/library-tests/PointsTo/regressions/subprocess-assert/ClassValue.expected b/python/ql/test/3/library-tests/PointsTo/regressions/subprocess-assert/ClassValue.expected new file mode 100644 index 00000000000..1c02b5ee6c6 --- /dev/null +++ b/python/ql/test/3/library-tests/PointsTo/regressions/subprocess-assert/ClassValue.expected @@ -0,0 +1,3 @@ +| mwe_failure.py:7:1:7:23 | class MyTest | | +| mwe_failure_2.py:7:1:7:23 | class MyTest | | +| mwe_success.py:7:1:7:23 | class MyTest | class TestCase | diff --git a/python/ql/test/3/library-tests/PointsTo/regressions/subprocess-assert/ClassValue.ql b/python/ql/test/3/library-tests/PointsTo/regressions/subprocess-assert/ClassValue.ql new file mode 100644 index 00000000000..b6f1ce2da98 --- /dev/null +++ b/python/ql/test/3/library-tests/PointsTo/regressions/subprocess-assert/ClassValue.ql @@ -0,0 +1,10 @@ +import python + +// as used in semmle.python.filters.Tests + +from ClassValue c, string base +where + c.getScope().getLocation().getFile().getShortName().matches("mwe%.py") and + c.getName() = "MyTest" and + if exists(c.getABaseType()) then base = c.getABaseType().toString() else base = "" +select c, base diff --git a/python/ql/test/3/library-tests/PointsTo/regressions/subprocess-assert/mwe_failure.py b/python/ql/test/3/library-tests/PointsTo/regressions/subprocess-assert/mwe_failure.py new file mode 100644 index 00000000000..6c22b37b04b --- /dev/null +++ b/python/ql/test/3/library-tests/PointsTo/regressions/subprocess-assert/mwe_failure.py @@ -0,0 +1,10 @@ +import subprocess +assert subprocess.call(['run-backup']) == 0 + +class TestCase: + pass + +class MyTest(TestCase): + pass + +# found by /home/rasmus/code/ql/python/ql/test/query-tests/Statements/asserts/AssertLiteralConstant.qlref diff --git a/python/ql/test/3/library-tests/PointsTo/regressions/subprocess-assert/mwe_failure_2.py b/python/ql/test/3/library-tests/PointsTo/regressions/subprocess-assert/mwe_failure_2.py new file mode 100644 index 00000000000..84bed797f8e --- /dev/null +++ b/python/ql/test/3/library-tests/PointsTo/regressions/subprocess-assert/mwe_failure_2.py @@ -0,0 +1,8 @@ +import subprocess +assert subprocess.call(['run-backup']) + +class TestCase: + pass + +class MyTest(TestCase): + pass diff --git a/python/ql/test/3/library-tests/PointsTo/regressions/subprocess-assert/mwe_success.py b/python/ql/test/3/library-tests/PointsTo/regressions/subprocess-assert/mwe_success.py new file mode 100644 index 00000000000..fce8f9bd25c --- /dev/null +++ b/python/ql/test/3/library-tests/PointsTo/regressions/subprocess-assert/mwe_success.py @@ -0,0 +1,8 @@ +import subprocess +subprocess.call(['run-backup']) + +class TestCase: + pass + +class MyTest(TestCase): + pass diff --git a/python/ql/test/3/library-tests/PointsTo/regressions/subprocess-assert/options b/python/ql/test/3/library-tests/PointsTo/regressions/subprocess-assert/options new file mode 100644 index 00000000000..89369a90996 --- /dev/null +++ b/python/ql/test/3/library-tests/PointsTo/regressions/subprocess-assert/options @@ -0,0 +1 @@ +semmle-extractor-options: --lang=3 --max-import-depth=1 diff --git a/python/ql/test/3/library-tests/PointsTo/typehints/options b/python/ql/test/3/library-tests/PointsTo/typehints/options index b3e60ec219e..666fe9447a1 100644 --- a/python/ql/test/3/library-tests/PointsTo/typehints/options +++ b/python/ql/test/3/library-tests/PointsTo/typehints/options @@ -1,2 +1 @@ semmle-extractor-options: --lang=3 -p ../../lib/ --max-import-depth=3 -optimize: true diff --git a/python/ql/test/3/library-tests/six/options b/python/ql/test/3/library-tests/six/options index 02026a7dcc5..61838b00376 100644 --- a/python/ql/test/3/library-tests/six/options +++ b/python/ql/test/3/library-tests/six/options @@ -1,2 +1 @@ semmle-extractor-options: --lang=3 --max-import-depth=4 -optimize: true diff --git a/python/ql/test/3/library-tests/taint/unpacking/TestTaint.expected b/python/ql/test/3/library-tests/taint/unpacking/TestTaint.expected index b6aacc7d670..3b24db6bee3 100644 --- a/python/ql/test/3/library-tests/taint/unpacking/TestTaint.expected +++ b/python/ql/test/3/library-tests/taint/unpacking/TestTaint.expected @@ -1,9 +1,9 @@ -| test.py:11 | extended_unpacking | first | externally controlled string | -| test.py:11 | extended_unpacking | last | externally controlled string | -| test.py:11 | extended_unpacking | rest | [externally controlled string] | -| test.py:16 | also_allowed | a | [externally controlled string] | +| test.py:11 | extended_unpacking | first | NO TAINT | +| test.py:11 | extended_unpacking | last | NO TAINT | +| test.py:11 | extended_unpacking | rest | NO TAINT | +| test.py:16 | also_allowed | a | NO TAINT | | test.py:24 | also_allowed | b | NO TAINT | | test.py:24 | also_allowed | c | NO TAINT | -| test.py:31 | nested | x | externally controlled string | -| test.py:31 | nested | xs | [externally controlled string] | -| test.py:31 | nested | ys | [externally controlled string] | +| test.py:31 | nested | x | NO TAINT | +| test.py:31 | nested | xs | NO TAINT | +| test.py:31 | nested | ys | NO TAINT | diff --git a/python/ql/test/3/query-tests/Expressions/UseofApply/UseofApply.expected b/python/ql/test/3/query-tests/Expressions/UseofApply/UseofApply.expected new file mode 100644 index 00000000000..e69de29bb2d diff --git a/python/ql/test/3/query-tests/Expressions/UseofApply/UseofApply.py b/python/ql/test/3/query-tests/Expressions/UseofApply/UseofApply.py new file mode 100644 index 00000000000..9109636f99e --- /dev/null +++ b/python/ql/test/3/query-tests/Expressions/UseofApply/UseofApply.py @@ -0,0 +1,30 @@ +#### UseofApply.ql + +# Use of the builtin function `apply` is generally considered bad now that the +# ability to destructure lists of arguments is possible, but we should not flag +# cases where the function is merely named `apply` rather than being the actual +# builtin `apply` function. + +def useofapply(): + + def foo(): + pass + + + + # Positive Cases + + # This use of `apply` is a reference to the builtin function and so SHOULD be + # caught by the query. + apply(foo, [1]) + + + + # Negative Cases + + # This use of `apply` is a reference to the locally defined function inside of + # `local`, and so SHOULD NOT be caught by the query. + def local(): + def apply(f): + pass + apply(foo)([1]) diff --git a/python/ql/test/3/query-tests/Expressions/UseofApply/UseofApply.qlref b/python/ql/test/3/query-tests/Expressions/UseofApply/UseofApply.qlref new file mode 100644 index 00000000000..abf684e3918 --- /dev/null +++ b/python/ql/test/3/query-tests/Expressions/UseofApply/UseofApply.qlref @@ -0,0 +1 @@ +Expressions/UseofApply.ql diff --git a/python/ql/test/3/query-tests/Statements/iter/NonIteratorInForLoop.expected b/python/ql/test/3/query-tests/Statements/iter/NonIteratorInForLoop.expected index 4f6ed0fac55..1f807ddf5f0 100644 --- a/python/ql/test/3/query-tests/Statements/iter/NonIteratorInForLoop.expected +++ b/python/ql/test/3/query-tests/Statements/iter/NonIteratorInForLoop.expected @@ -1,2 +1,2 @@ -| async_iterator.py:26:11:26:34 | For | $@ of class '$@' may be used in for-loop. | async_iterator.py:26:20:26:33 | ControlFlowNode for MissingAiter() | Non-iterator | async_iterator.py:13:1:13:19 | class MissingAiter | MissingAiter | -| statements_test.py:34:5:34:19 | For | $@ of class '$@' may be used in for-loop. | statements_test.py:34:18:34:18 | ControlFlowNode for IntegerLiteral | Non-iterator | file://:0:0:0:0 | builtin-class int | int | +| async_iterator.py:26:11:26:34 | For | $@ of class '$@' may be used in for-loop. | async_iterator.py:26:20:26:33 | ControlFlowNode for MissingAiter() | Non-iterable | async_iterator.py:13:1:13:19 | class MissingAiter | MissingAiter | +| statements_test.py:34:5:34:19 | For | $@ of class '$@' may be used in for-loop. | statements_test.py:34:18:34:18 | ControlFlowNode for IntegerLiteral | Non-iterable | file://:0:0:0:0 | builtin-class int | int | diff --git a/python/ql/test/experimental/README.md b/python/ql/test/experimental/README.md new file mode 100644 index 00000000000..2a9db304b9f --- /dev/null +++ b/python/ql/test/experimental/README.md @@ -0,0 +1 @@ +This directory contains tests for [experimental](../../../../docs/experimental.md) CodeQL queries and libraries. diff --git a/python/ql/test/library-tests/PointsTo/guarded/options b/python/ql/test/library-tests/PointsTo/guarded/options index be048160aeb..e7a518df45b 100644 --- a/python/ql/test/library-tests/PointsTo/guarded/options +++ b/python/ql/test/library-tests/PointsTo/guarded/options @@ -1,2 +1 @@ semmle-extractor-options: --max-import-depth=3 -optimize: true diff --git a/python/ql/test/library-tests/PointsTo/new/options b/python/ql/test/library-tests/PointsTo/new/options index 8e16f310b52..2e240a38e85 100644 --- a/python/ql/test/library-tests/PointsTo/new/options +++ b/python/ql/test/library-tests/PointsTo/new/options @@ -1,2 +1 @@ semmle-extractor-options: --max-import-depth=4 -optimize: true diff --git a/python/ql/test/library-tests/PointsTo/regressions/missing/if-urlsplit-access/Test.expected b/python/ql/test/library-tests/PointsTo/regressions/missing/if-urlsplit-access/Test.expected new file mode 100644 index 00000000000..fc25b86ca0a --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/regressions/missing/if-urlsplit-access/Test.expected @@ -0,0 +1,3 @@ +| test.py:5:7:5:9 | ControlFlowNode for foo | int 42 | +| test.py:11:11:11:13 | ControlFlowNode for foo | int 1 | +| test.py:17:11:17:13 | ControlFlowNode for foo | | diff --git a/python/ql/test/library-tests/PointsTo/regressions/missing/if-urlsplit-access/Test.ql b/python/ql/test/library-tests/PointsTo/regressions/missing/if-urlsplit-access/Test.ql new file mode 100644 index 00000000000..dd894ad5cea --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/regressions/missing/if-urlsplit-access/Test.ql @@ -0,0 +1,10 @@ +import python + +from NameNode name, CallNode call, string debug +where + call.getAnArg() = name and + call.getFunction().(NameNode).getId() = "check" and + if exists(name.pointsTo()) + then debug = name.pointsTo().toString() + else debug = "" +select name, debug diff --git a/python/ql/test/library-tests/PointsTo/regressions/missing/if-urlsplit-access/options b/python/ql/test/library-tests/PointsTo/regressions/missing/if-urlsplit-access/options new file mode 100644 index 00000000000..cfef58cf2b2 --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/regressions/missing/if-urlsplit-access/options @@ -0,0 +1 @@ +semmle-extractor-options: --max-import-depth=1 --lang=3 diff --git a/python/ql/test/library-tests/PointsTo/regressions/missing/if-urlsplit-access/test.py b/python/ql/test/library-tests/PointsTo/regressions/missing/if-urlsplit-access/test.py new file mode 100644 index 00000000000..6301475e4a0 --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/regressions/missing/if-urlsplit-access/test.py @@ -0,0 +1,17 @@ +# Only a problem in Python 3 +from urllib.parse import urlsplit + +foo = 42 +check(foo) + +def func(url): + parts = urlsplit(url) + + foo = 1 + check(foo) + + if parts.path: # using `urlsplit(url).path` here is equivalent + return # using `pass` here instead makes points-to work + + foo = 2 + check(foo) # no points-to information diff --git a/python/ql/test/library-tests/PointsTo/regressions/missing/re-compile/Test.expected b/python/ql/test/library-tests/PointsTo/regressions/missing/re-compile/Test.expected new file mode 100644 index 00000000000..c625cceb62f --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/regressions/missing/re-compile/Test.expected @@ -0,0 +1 @@ +| test.py:5:7:5:13 | ControlFlowNode for PATTERN | | diff --git a/python/ql/test/library-tests/PointsTo/regressions/missing/re-compile/Test.ql b/python/ql/test/library-tests/PointsTo/regressions/missing/re-compile/Test.ql new file mode 100644 index 00000000000..dd894ad5cea --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/regressions/missing/re-compile/Test.ql @@ -0,0 +1,10 @@ +import python + +from NameNode name, CallNode call, string debug +where + call.getAnArg() = name and + call.getFunction().(NameNode).getId() = "check" and + if exists(name.pointsTo()) + then debug = name.pointsTo().toString() + else debug = "" +select name, debug diff --git a/python/ql/test/library-tests/PointsTo/regressions/missing/re-compile/test.py b/python/ql/test/library-tests/PointsTo/regressions/missing/re-compile/test.py new file mode 100644 index 00000000000..b1e46a33287 --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/regressions/missing/re-compile/test.py @@ -0,0 +1,5 @@ +import re + +PATTERN = re.compile("a|b") + +check(PATTERN) diff --git a/python/ql/test/library-tests/PointsTo/returns/options b/python/ql/test/library-tests/PointsTo/returns/options deleted file mode 100644 index 58ad829f5a8..00000000000 --- a/python/ql/test/library-tests/PointsTo/returns/options +++ /dev/null @@ -1 +0,0 @@ -optimize: true diff --git a/python/ql/test/library-tests/descriptors/Methods.expected b/python/ql/test/library-tests/descriptors/Methods.expected index efd066e8b4c..eff2e730b4c 100644 --- a/python/ql/test/library-tests/descriptors/Methods.expected +++ b/python/ql/test/library-tests/descriptors/Methods.expected @@ -1,6 +1,6 @@ -| 16 | classmethod() | 17 | Function c1 | -| 23 | classmethod() | 20 | Function c2 | -| 24 | classmethod() | 20 | Function c2 | -| 26 | staticmethod() | 27 | Function s1 | -| 33 | staticmethod() | 30 | Function s2 | -| 34 | staticmethod() | 30 | Function s2 | \ No newline at end of file +| 104 | classmethod() | 105 | Function c1 | +| 111 | classmethod() | 108 | Function c2 | +| 112 | classmethod() | 108 | Function c2 | +| 114 | staticmethod() | 115 | Function s1 | +| 121 | staticmethod() | 118 | Function s2 | +| 122 | staticmethod() | 118 | Function s2 | diff --git a/python/ql/test/library-tests/descriptors/Properties.expected b/python/ql/test/library-tests/descriptors/Properties.expected index 3eb736d618b..4dc1cf76321 100644 --- a/python/ql/test/library-tests/descriptors/Properties.expected +++ b/python/ql/test/library-tests/descriptors/Properties.expected @@ -1 +1,8 @@ -| 6 | Property f | 7 | Function f | 11 | Function f | +| test.py:6:5:6:16 | Function WithDecorator.x | getter | test.py:5:6:5:13 | property x | +| test.py:11:5:11:23 | Function WithDecorator.x | setter | test.py:5:6:5:13 | property x | +| test.py:15:5:15:16 | Function WithDecorator.x | deleter | test.py:5:6:5:13 | property x | +| test.py:21:5:21:16 | Function WithDecoratorOnlyGetter.x | getter | test.py:20:6:20:13 | property x | +| test.py:28:5:28:19 | Function WithoutDecorator.getx | getter | test.py:37:9:37:59 | property getx | +| test.py:31:5:31:26 | Function WithoutDecorator.setx | setter | test.py:37:9:37:59 | property getx | +| test.py:34:5:34:19 | Function WithoutDecorator.delx | deleter | test.py:37:9:37:59 | property getx | +| test.py:41:5:41:19 | Function WithoutDecoratorOnlyGetter.getx | getter | test.py:44:9:44:22 | property getx | diff --git a/python/ql/test/library-tests/descriptors/Properties.ql b/python/ql/test/library-tests/descriptors/Properties.ql index e27ca6beb3c..ed36fb4e5bc 100644 --- a/python/ql/test/library-tests/descriptors/Properties.ql +++ b/python/ql/test/library-tests/descriptors/Properties.ql @@ -1,13 +1,11 @@ - import python import semmle.python.types.Descriptors -int lineof(Object o) { - result = o.getOrigin().getLocation().getStartLine() -} - -from PropertyObject p, FunctionObject getter, FunctionObject setter +from PropertyValue p, string method_name, FunctionValue method where -getter = p.getGetter() and setter = p.getSetter() -select lineof(p), p.toString(), lineof(getter), getter.toString(), lineof(setter), setter.toString() - + method_name = "getter" and method = p.getGetter() + or + method_name = "setter" and method = p.getSetter() + or + method_name = "deleter" and method = p.getDeleter() +select method, method_name, p diff --git a/python/ql/test/library-tests/descriptors/test.py b/python/ql/test/library-tests/descriptors/test.py index 8d7f14198c7..881350e665d 100644 --- a/python/ql/test/library-tests/descriptors/test.py +++ b/python/ql/test/library-tests/descriptors/test.py @@ -1,15 +1,103 @@ - - - -class C(object): +class WithDecorator(object): + def __init__(self): + self._x = None @property - def f(self): - return self._f + def x(self): + """I'm the 'x' property.""" + return self._x - @f.setter - def f(self): - return self._f + @x.setter + def x(self, value): + self._x = value + + @x.deleter + def x(self): + del self._x + +class WithDecoratorOnlyGetter(object): + + @property + def x(self): + return 42 + +class WithoutDecorator(object): + def __init__(self): + self._x = None + + def getx(self): + return self._x + + def setx(self, value): + self._x = value + + def delx(self): + del self._x + + x = property(getx, setx, delx, "I'm the 'x' property.") + +class WithoutDecoratorOnlyGetter(object): + + def getx(self): + return 42 + + x = property(getx) + +class WithoutDecoratorOnlyGetterKWArg(object): + + def getx(self): + return 42 + + x = property(fget=getx) + +class WithoutDecoratorOnlySetter(object): + + def setx(self, value): + self._x = value + + x = property(fset=setx) # TODO: Not handled + +class WithDecoratorOnlySetter(object): + + x = property() + + @x.setter + def x(self, value): + print('{} setting value to {}'.format(self.__class__, value)) + +class FunkyButValid(object): + + def delx(self): + print("deleting x") + + x = property(fdel=delx) + + @x.setter + def y(self, value): + print('setting value to {}'.format(value)) + + @y.getter + def z(self): + return 42 + + +wat = FunkyButValid() +try: + wat.x +except AttributeError as e: + print("x can't be read") +del wat.x + +try: + wat.y +except AttributeError as e: + print("y can't be read") +wat.y = 1234 +del wat.y + +print(wat.z) +wat.z = 10 +del wat.z class D(object): diff --git a/python/ql/test/library-tests/modules/duplicate_name/options b/python/ql/test/library-tests/modules/duplicate_name/options index ebe93df725a..3819071b01c 100644 --- a/python/ql/test/library-tests/modules/duplicate_name/options +++ b/python/ql/test/library-tests/modules/duplicate_name/options @@ -1,2 +1 @@ semmle-extractor-options: -R . -optimize: true diff --git a/python/ql/test/library-tests/modules/usage/ModuleUsage.expected b/python/ql/test/library-tests/modules/usage/ModuleUsage.expected new file mode 100644 index 00000000000..ae241915998 --- /dev/null +++ b/python/ql/test/library-tests/modules/usage/ModuleUsage.expected @@ -0,0 +1,5 @@ +| file://:0:0:0:0 | Module sys | isUsedAsModule | +| imported.py:0:0:0:0 | Module imported | isUsedAsModule | +| main.py:0:0:0:0 | Module main | isUsedAsScript | +| myscript.py:0:0:0:0 | Script myscript | isUsedAsScript | +| script:0:0:0:0 | Script script | isUsedAsScript | diff --git a/python/ql/test/library-tests/modules/usage/ModuleUsage.ql b/python/ql/test/library-tests/modules/usage/ModuleUsage.ql new file mode 100644 index 00000000000..3ff70adb69d --- /dev/null +++ b/python/ql/test/library-tests/modules/usage/ModuleUsage.ql @@ -0,0 +1,16 @@ +import python + +from ModuleValue mv, string usage +where + // builtin module has different name in Python 2 and 3 + not mv = Module::builtinModule() and + ( + mv.isUsedAsModule() and usage = "isUsedAsModule" + or + mv.isUsedAsScript() and usage = "isUsedAsScript" + or + not mv.isUsedAsModule() and + not mv.isUsedAsScript() and + usage = "" + ) +select mv, usage diff --git a/python/ql/test/library-tests/modules/usage/imported.py b/python/ql/test/library-tests/modules/usage/imported.py new file mode 100644 index 00000000000..98e3dfd364a --- /dev/null +++ b/python/ql/test/library-tests/modules/usage/imported.py @@ -0,0 +1,6 @@ +def func(): + pass + +if __name__ == "__main__": + print("I could have done something interesting...") + print("but I didn't") diff --git a/python/ql/test/library-tests/modules/usage/main.py b/python/ql/test/library-tests/modules/usage/main.py new file mode 100644 index 00000000000..171fd01402f --- /dev/null +++ b/python/ql/test/library-tests/modules/usage/main.py @@ -0,0 +1,5 @@ +import imported + +if __name__ == "__main__": + imported.func() + print('Done') diff --git a/python/ql/test/library-tests/modules/usage/myscript.py b/python/ql/test/library-tests/modules/usage/myscript.py new file mode 100755 index 00000000000..0eb83a9dadb --- /dev/null +++ b/python/ql/test/library-tests/modules/usage/myscript.py @@ -0,0 +1,3 @@ +#!/usr/bin/env python + +print("I'm actually a script you see ;)") diff --git a/python/ql/test/library-tests/modules/usage/options b/python/ql/test/library-tests/modules/usage/options new file mode 100644 index 00000000000..98459309caa --- /dev/null +++ b/python/ql/test/library-tests/modules/usage/options @@ -0,0 +1 @@ +semmle-extractor-options: -F script diff --git a/python/ql/test/library-tests/modules/usage/script b/python/ql/test/library-tests/modules/usage/script new file mode 100755 index 00000000000..b5ec8d6f2aa --- /dev/null +++ b/python/ql/test/library-tests/modules/usage/script @@ -0,0 +1,3 @@ +#!/usr/bin/env python + +print('Under construction :)') diff --git a/python/ql/test/library-tests/regex/options b/python/ql/test/library-tests/regex/options index be048160aeb..e7a518df45b 100644 --- a/python/ql/test/library-tests/regex/options +++ b/python/ql/test/library-tests/regex/options @@ -1,2 +1 @@ semmle-extractor-options: --max-import-depth=3 -optimize: true diff --git a/python/ql/test/library-tests/taint/collections/TestStep.expected b/python/ql/test/library-tests/taint/collections/TestStep.expected index a9591db0b9f..609c7c3bcd1 100644 --- a/python/ql/test/library-tests/taint/collections/TestStep.expected +++ b/python/ql/test/library-tests/taint/collections/TestStep.expected @@ -26,9 +26,6 @@ | Taint [externally controlled string] | test.py:29 | test.py:29:9:29:25 | Subscript | | --> | Taint [externally controlled string] | test.py:32 | test.py:32:16:32:16 | c | | | Taint [externally controlled string] | test.py:30 | test.py:30:9:30:20 | tainted_list | | --> | Taint [externally controlled string] | test.py:30 | test.py:30:9:30:27 | Attribute() | | | Taint [externally controlled string] | test.py:30 | test.py:30:9:30:27 | Attribute() | | --> | Taint [externally controlled string] | test.py:32 | test.py:32:19:32:19 | d | | -| Taint [externally controlled string] | test.py:31 | test.py:31:15:31:26 | tainted_list | | --> | Taint externally controlled string | test.py:32 | test.py:32:22:32:22 | e | | -| Taint [externally controlled string] | test.py:31 | test.py:31:15:31:26 | tainted_list | | --> | Taint externally controlled string | test.py:32 | test.py:32:25:32:25 | f | | -| Taint [externally controlled string] | test.py:31 | test.py:31:15:31:26 | tainted_list | | --> | Taint externally controlled string | test.py:32 | test.py:32:28:32:28 | g | | | Taint [externally controlled string] | test.py:33 | test.py:33:14:33:25 | tainted_list | | --> | Taint externally controlled string | test.py:33 | test.py:33:5:33:26 | For | | | Taint [externally controlled string] | test.py:35 | test.py:35:14:35:35 | reversed() | | --> | Taint externally controlled string | test.py:35 | test.py:35:5:35:36 | For | | | Taint [externally controlled string] | test.py:35 | test.py:35:23:35:34 | tainted_list | | --> | Taint [externally controlled string] | test.py:35 | test.py:35:14:35:35 | reversed() | | diff --git a/python/ql/test/library-tests/taint/collections/TestTaint.expected b/python/ql/test/library-tests/taint/collections/TestTaint.expected index aae51236e6d..6f3ca9515d1 100644 --- a/python/ql/test/library-tests/taint/collections/TestTaint.expected +++ b/python/ql/test/library-tests/taint/collections/TestTaint.expected @@ -10,9 +10,9 @@ | test.py:32 | test_access | b | externally controlled string | | test.py:32 | test_access | c | [externally controlled string] | | test.py:32 | test_access | d | [externally controlled string] | -| test.py:32 | test_access | e | externally controlled string | -| test.py:32 | test_access | f | externally controlled string | -| test.py:32 | test_access | g | externally controlled string | +| test.py:32 | test_access | e | NO TAINT | +| test.py:32 | test_access | f | NO TAINT | +| test.py:32 | test_access | g | NO TAINT | | test.py:34 | test_access | h | externally controlled string | | test.py:36 | test_access | i | externally controlled string | | test.py:43 | test_dict_access | a | externally controlled string | diff --git a/python/ql/test/library-tests/taint/namedtuple/SanitizedEdges.expected b/python/ql/test/library-tests/taint/namedtuple/SanitizedEdges.expected new file mode 100644 index 00000000000..0adf64dfd5d --- /dev/null +++ b/python/ql/test/library-tests/taint/namedtuple/SanitizedEdges.expected @@ -0,0 +1,7 @@ +| UrlsplitUrlparseTempSanitizer | [externally controlled string] | test.py:21 | Pi(urlsplit_res_0) [true] | +| UrlsplitUrlparseTempSanitizer | [externally controlled string] | test.py:24 | Pi(urlsplit_res_3) [true] | +| UrlsplitUrlparseTempSanitizer | [externally controlled string] | test.py:27 | Pi(urlsplit_res_6) [true] | +| UrlsplitUrlparseTempSanitizer | [externally controlled string] | test.py:30 | Pi(urlsplit_res_9) [true] | +| string equality sanitizer | externally controlled string | test.py:21 | Pi(urlsplit_res_0) [true] | +| string equality sanitizer | externally controlled string | test.py:24 | Pi(urlsplit_res_3) [true] | +| string equality sanitizer | externally controlled string | test.py:27 | Pi(urlsplit_res_6) [true] | diff --git a/python/ql/test/library-tests/taint/namedtuple/SanitizedEdges.ql b/python/ql/test/library-tests/taint/namedtuple/SanitizedEdges.ql new file mode 100644 index 00000000000..d523f79a963 --- /dev/null +++ b/python/ql/test/library-tests/taint/namedtuple/SanitizedEdges.ql @@ -0,0 +1,6 @@ +import python +import Taint + +from Sanitizer s, TaintKind taint, PyEdgeRefinement test +where s.sanitizingEdge(taint, test) +select s, taint, test.getTest().getLocation().toString(), test.getRepresentation() diff --git a/python/ql/test/library-tests/taint/namedtuple/Taint.qll b/python/ql/test/library-tests/taint/namedtuple/Taint.qll new file mode 100644 index 00000000000..9d19a54488c --- /dev/null +++ b/python/ql/test/library-tests/taint/namedtuple/Taint.qll @@ -0,0 +1,47 @@ +import python +import semmle.python.security.TaintTracking +import semmle.python.security.strings.Untrusted + +class SimpleSource extends TaintSource { + SimpleSource() { this.(NameNode).getId() = "TAINTED_STRING" } + + override predicate isSourceOf(TaintKind kind) { kind instanceof ExternalStringKind } + + override string toString() { result = "taint source" } +} + +class ListSource extends TaintSource { + ListSource() { this.(NameNode).getId() = "TAINTED_LIST" } + + override predicate isSourceOf(TaintKind kind) { kind instanceof ExternalStringSequenceKind } + + override string toString() { result = "list taint source" } +} + +class DictSource extends TaintSource { + DictSource() { this.(NameNode).getId() = "TAINTED_DICT" } + + override predicate isSourceOf(TaintKind kind) { kind instanceof ExternalStringDictKind } + + override string toString() { result = "dict taint source" } +} + +class TestConfig extends TaintTracking::Configuration { + TestConfig() { this = "TestConfig" } + + override predicate isSanitizer(Sanitizer sanitizer) { + sanitizer instanceof UrlsplitUrlparseTempSanitizer + } + + override predicate isSource(TaintTracking::Source source) { + source instanceof SimpleSource + or + source instanceof ListSource + or + source instanceof DictSource + } + + override predicate isSink(TaintTracking::Sink sink) { + none() + } +} diff --git a/python/ql/test/library-tests/taint/namedtuple/TestTaint.expected b/python/ql/test/library-tests/taint/namedtuple/TestTaint.expected new file mode 100644 index 00000000000..79a262c61d5 --- /dev/null +++ b/python/ql/test/library-tests/taint/namedtuple/TestTaint.expected @@ -0,0 +1,15 @@ +| test.py:13 | test_basic | a | externally controlled string | +| test.py:13 | test_basic | b | externally controlled string | +| test.py:13 | test_basic | c | externally controlled string | +| test.py:13 | test_basic | d | NO TAINT | +| test.py:13 | test_basic | urlsplit_res | [externally controlled string] | +| test.py:19 | test_sanitizer | Attribute | externally controlled string | +| test.py:22 | test_sanitizer | Attribute | NO TAINT | +| test.py:25 | test_sanitizer | Subscript | NO TAINT | +| test.py:28 | test_sanitizer | Attribute | NO TAINT | +| test.py:31 | test_sanitizer | Attribute | NO TAINT | +| test.py:34 | test_sanitizer | Attribute | externally controlled string | +| test.py:44 | test_namedtuple | a | NO TAINT | +| test.py:44 | test_namedtuple | b | NO TAINT | +| test.py:44 | test_namedtuple | c | NO TAINT | +| test.py:44 | test_namedtuple | d | NO TAINT | diff --git a/python/ql/test/library-tests/taint/namedtuple/TestTaint.ql b/python/ql/test/library-tests/taint/namedtuple/TestTaint.ql new file mode 100644 index 00000000000..92657b1fef9 --- /dev/null +++ b/python/ql/test/library-tests/taint/namedtuple/TestTaint.ql @@ -0,0 +1,18 @@ +import python +import semmle.python.security.TaintTracking +import Taint + +from Call call, Expr arg, string taint_string +where + call.getLocation().getFile().getShortName() = "test.py" and + call.getFunc().(Name).getId() = "test" and + arg = call.getAnArg() and + ( + not exists(TaintedNode tainted | tainted.getAstNode() = arg) and + taint_string = "NO TAINT" + or + exists(TaintedNode tainted | tainted.getAstNode() = arg | + taint_string = tainted.getTaintKind().toString() + ) + ) +select arg.getLocation().toString(), call.getScope().(Function).getName(), arg.toString(), taint_string diff --git a/python/ql/test/library-tests/taint/namedtuple/test.py b/python/ql/test/library-tests/taint/namedtuple/test.py new file mode 100644 index 00000000000..ec6304f5073 --- /dev/null +++ b/python/ql/test/library-tests/taint/namedtuple/test.py @@ -0,0 +1,44 @@ +from six.moves.urllib.parse import urlsplit + +# Currently we don't have support for namedtuples in general, but do have special support +# for `urlsplit` (and `urlparse`) + +def test_basic(): + tainted_string = TAINTED_STRING + urlsplit_res = urlsplit(tainted_string) + a = urlsplit_res.netloc # field access + b = urlsplit_res.hostname # property + c = urlsplit_res[3] # indexing + _, _, d, _, _ = urlsplit(tainted_string) # unpacking + test(a, b, c, d, urlsplit_res) + +def test_sanitizer(): + tainted_string = TAINTED_STRING + urlsplit_res = urlsplit(tainted_string) + + test(urlsplit_res.netloc) # should be tainted + + if urlsplit_res.netloc == "OK": + test(urlsplit_res.netloc) + + if urlsplit_res[2] == "OK": + test(urlsplit_res[0]) + + if urlsplit_res.netloc == "OK": + test(urlsplit_res.path) # FN + + if urlsplit_res.netloc in ["OK"]: + test(urlsplit_res.netloc) + + if urlsplit_res.netloc in ["OK", non_constant()]: + test(urlsplit_res.netloc) # should be tainted + +def test_namedtuple(): + tainted_string = TAINTED_STRING + Point = namedtuple('Point', ['x', 'y']) + p = Point('safe', tainted_string) + a = p.x + b = p.y + c = p[0] + d = p[1] + test(a, b, c, d) # TODO: FN, at least p.y and p[1] should be tainted diff --git a/python/ql/test/library-tests/taint/strings/DistinctStringKinds.expected b/python/ql/test/library-tests/taint/strings/DistinctStringKinds.expected deleted file mode 100644 index e7c6590cb50..00000000000 --- a/python/ql/test/library-tests/taint/strings/DistinctStringKinds.expected +++ /dev/null @@ -1,16 +0,0 @@ -| Taint exception.info | test.py:41 | test.py:41:22:41:26 | taint | p1 = exception.info | -| Taint exception.info | test.py:42 | test.py:42:12:42:22 | func() | p1 = exception.info | -| Taint exception.info | test.py:42 | test.py:42:17:42:21 | taint | p1 = exception.info | -| Taint exception.info | test.py:45 | test.py:45:12:45:33 | TAINTED_EXCEPTION_INFO | | -| Taint exception.info | test.py:46 | test.py:46:11:46:41 | cross_over() | | -| Taint exception.info | test.py:46 | test.py:46:37:46:40 | info | | -| Taint exception.info | test.py:48 | test.py:48:19:48:21 | arg | p0 = exception.info | -| Taint exception.info | test.py:49 | test.py:49:12:49:14 | arg | p0 = exception.info | -| Taint externally controlled string | test.py:41 | test.py:41:22:41:26 | taint | p1 = externally controlled string | -| Taint externally controlled string | test.py:42 | test.py:42:12:42:22 | func() | p1 = externally controlled string | -| Taint externally controlled string | test.py:42 | test.py:42:17:42:21 | taint | p1 = externally controlled string | -| Taint externally controlled string | test.py:48 | test.py:48:19:48:21 | arg | p0 = externally controlled string | -| Taint externally controlled string | test.py:49 | test.py:49:12:49:14 | arg | p0 = externally controlled string | -| Taint externally controlled string | test.py:52 | test.py:52:11:52:33 | TAINTED_EXTERNAL_STRING | | -| Taint externally controlled string | test.py:53 | test.py:53:11:53:41 | cross_over() | | -| Taint externally controlled string | test.py:53 | test.py:53:38:53:40 | ext | | diff --git a/python/ql/test/library-tests/taint/strings/DistinctStringKinds.ql b/python/ql/test/library-tests/taint/strings/DistinctStringKinds.ql deleted file mode 100644 index a1bf5ea5e1f..00000000000 --- a/python/ql/test/library-tests/taint/strings/DistinctStringKinds.ql +++ /dev/null @@ -1,37 +0,0 @@ -import python -import semmle.python.security.TaintTracking - -import semmle.python.security.Exceptions -import semmle.python.security.strings.Untrusted - - -class ExceptionInfoSource extends TaintSource { - - ExceptionInfoSource() { this.(NameNode).getId() = "TAINTED_EXCEPTION_INFO" } - - override predicate isSourceOf(TaintKind kind) { - kind instanceof ExceptionInfo - } - - override string toString() { - result = "Exception info source" - } - -} - -class ExternalStringSource extends TaintSource { - - ExternalStringSource() { this.(NameNode).getId() = "TAINTED_EXTERNAL_STRING" } - - override predicate isSourceOf(TaintKind kind) { - kind instanceof ExternalStringKind - } - - override string toString() { - result = "Untrusted string source" - } - -} -from TaintedNode n -where n.getLocation().getFile().getShortName() = "test.py" -select "Taint " + n.getTaintKind(), n.getLocation().toString(), n.getAstNode(), n.getContext() diff --git a/python/ql/test/library-tests/taint/strings/Taint.qll b/python/ql/test/library-tests/taint/strings/Taint.qll index b5b2fb61a5e..d045534e0ef 100644 --- a/python/ql/test/library-tests/taint/strings/Taint.qll +++ b/python/ql/test/library-tests/taint/strings/Taint.qll @@ -1,11 +1,12 @@ import python import semmle.python.security.TaintTracking import semmle.python.security.strings.Untrusted +import semmle.python.security.Exceptions class SimpleSource extends TaintSource { - SimpleSource() { this.(NameNode).getId() = "TAINTED" } + SimpleSource() { this.(NameNode).getId() = "TAINTED_STRING" } override predicate isSourceOf(TaintKind kind) { kind instanceof ExternalStringKind @@ -46,3 +47,16 @@ class DictSource extends TaintSource { } +class ExceptionInfoSource extends TaintSource { + + ExceptionInfoSource() { this.(NameNode).getId() = "TAINTED_EXCEPTION_INFO" } + + override predicate isSourceOf(TaintKind kind) { + kind instanceof ExceptionInfo + } + + override string toString() { + result = "Exception info source" + } + +} diff --git a/python/ql/test/library-tests/taint/strings/TestNode.expected b/python/ql/test/library-tests/taint/strings/TestNode.expected deleted file mode 100644 index 3e248be5f89..00000000000 --- a/python/ql/test/library-tests/taint/strings/TestNode.expected +++ /dev/null @@ -1,43 +0,0 @@ -| Taint externally controlled string | test.py:5 | test.py:5:22:5:28 | TAINTED | | -| Taint externally controlled string | test.py:6 | test.py:6:31:6:44 | tainted_string | | -| Taint externally controlled string | test.py:7 | test.py:7:9:7:25 | Subscript | | -| Taint externally controlled string | test.py:8 | test.py:8:9:8:9 | a | | -| Taint externally controlled string | test.py:8 | test.py:8:9:8:18 | Attribute() | | -| Taint externally controlled string | test.py:9 | test.py:9:9:9:9 | b | | -| Taint externally controlled string | test.py:9 | test.py:9:9:9:14 | Subscript | | -| Taint externally controlled string | test.py:12 | test.py:12:22:12:28 | TAINTED | | -| Taint externally controlled string | test.py:13 | test.py:13:9:13:22 | tainted_string | | -| Taint externally controlled string | test.py:13 | test.py:13:9:13:31 | Attribute() | | -| Taint externally controlled string | test.py:14 | test.py:14:9:14:22 | tainted_string | | -| Taint externally controlled string | test.py:14 | test.py:14:9:14:29 | Attribute() | | -| Taint externally controlled string | test.py:15 | test.py:15:9:15:22 | tainted_string | | -| Taint externally controlled string | test.py:15 | test.py:15:9:15:25 | Subscript | | -| Taint externally controlled string | test.py:16 | test.py:16:9:16:22 | tainted_string | | -| Taint externally controlled string | test.py:16 | test.py:16:9:16:27 | Subscript | | -| Taint externally controlled string | test.py:17 | test.py:17:9:17:32 | reversed() | | -| Taint externally controlled string | test.py:17 | test.py:17:18:17:31 | tainted_string | | -| Taint externally controlled string | test.py:18 | test.py:18:9:18:28 | copy() | | -| Taint externally controlled string | test.py:18 | test.py:18:14:18:27 | tainted_string | | -| Taint externally controlled string | test.py:19 | test.py:19:9:19:22 | tainted_string | | -| Taint externally controlled string | test.py:19 | test.py:19:9:19:30 | Attribute() | | -| Taint externally controlled string | test.py:22 | test.py:22:22:22:28 | TAINTED | | -| Taint externally controlled string | test.py:23 | test.py:23:8:23:21 | tainted_string | | -| Taint externally controlled string | test.py:26 | test.py:26:23:26:36 | tainted_string | | -| Taint externally controlled string | test.py:29 | test.py:29:22:29:28 | TAINTED | | -| Taint externally controlled string | test.py:30 | test.py:30:8:30:21 | tainted_string | | -| Taint externally controlled string | test.py:30 | test.py:30:34:30:47 | tainted_string | | -| Taint externally controlled string | test.py:33 | test.py:33:23:33:36 | tainted_string | | -| Taint externally controlled string | test.py:36 | test.py:36:22:36:28 | TAINTED | | -| Taint externally controlled string | test.py:37 | test.py:37:9:37:27 | str() | | -| Taint externally controlled string | test.py:37 | test.py:37:13:37:26 | tainted_string | | -| Taint externally controlled string | test.py:38 | test.py:38:9:38:29 | bytes() | | -| Taint externally controlled string | test.py:38 | test.py:38:15:38:28 | tainted_string | | -| Taint externally controlled string | test.py:39 | test.py:39:9:39:46 | bytes() | | -| Taint externally controlled string | test.py:39 | test.py:39:15:39:28 | tainted_string | | -| Taint json[externally controlled string] | test.py:6 | test.py:6:20:6:45 | Attribute() | | -| Taint json[externally controlled string] | test.py:7 | test.py:7:9:7:20 | tainted_json | | -| Taint json[externally controlled string] | test.py:7 | test.py:7:9:7:25 | Subscript | | -| Taint json[externally controlled string] | test.py:8 | test.py:8:9:8:9 | a | | -| Taint json[externally controlled string] | test.py:8 | test.py:8:9:8:18 | Attribute() | | -| Taint json[externally controlled string] | test.py:9 | test.py:9:9:9:9 | b | | -| Taint json[externally controlled string] | test.py:9 | test.py:9:9:9:14 | Subscript | | diff --git a/python/ql/test/library-tests/taint/strings/TestNode.ql b/python/ql/test/library-tests/taint/strings/TestNode.ql deleted file mode 100644 index 23b6643f86a..00000000000 --- a/python/ql/test/library-tests/taint/strings/TestNode.ql +++ /dev/null @@ -1,8 +0,0 @@ -import python -import semmle.python.security.TaintTracking -import Taint - - -from TaintedNode n -where n.getLocation().getFile().getShortName() = "test.py" -select "Taint " + n.getTaintKind(), n.getLocation().toString(), n.getCfgNode().getNode(), n.getContext() diff --git a/python/ql/test/library-tests/taint/strings/TestStep.expected b/python/ql/test/library-tests/taint/strings/TestStep.expected index 17373b5db79..6efa8fb6edf 100644 --- a/python/ql/test/library-tests/taint/strings/TestStep.expected +++ b/python/ql/test/library-tests/taint/strings/TestStep.expected @@ -1,38 +1,76 @@ -| Taint externally controlled string | test.py:5 | test.py:5:22:5:28 | TAINTED | | --> | Taint externally controlled string | test.py:6 | test.py:6:31:6:44 | tainted_string | | +| Taint [externally controlled string] | test.py:67 | test.py:67:20:67:43 | urlsplit() | | --> | Taint [externally controlled string] | test.py:69 | test.py:69:10:69:21 | urlsplit_res | | +| Taint [externally controlled string] | test.py:68 | test.py:68:20:68:43 | urlparse() | | --> | Taint [externally controlled string] | test.py:69 | test.py:69:24:69:35 | urlparse_res | | +| Taint exception.info | test.py:44 | test.py:44:22:44:26 | taint | p1 = exception.info | --> | Taint exception.info | test.py:45 | test.py:45:17:45:21 | taint | p1 = exception.info | +| Taint exception.info | test.py:45 | test.py:45:17:45:21 | taint | p1 = exception.info | --> | Taint exception.info | test.py:45 | test.py:45:12:45:22 | func() | p1 = exception.info | +| Taint exception.info | test.py:45 | test.py:45:17:45:21 | taint | p1 = exception.info | --> | Taint exception.info | test.py:52 | test.py:52:19:52:21 | arg | p0 = exception.info | +| Taint exception.info | test.py:48 | test.py:48:12:48:33 | TAINTED_EXCEPTION_INFO | | --> | Taint exception.info | test.py:49 | test.py:49:37:49:40 | info | | +| Taint exception.info | test.py:49 | test.py:49:11:49:41 | cross_over() | | --> | Taint exception.info | test.py:50 | test.py:50:10:50:12 | res | | +| Taint exception.info | test.py:49 | test.py:49:37:49:40 | info | | --> | Taint exception.info | test.py:44 | test.py:44:22:44:26 | taint | p1 = exception.info | +| Taint exception.info | test.py:49 | test.py:49:37:49:40 | info | | --> | Taint exception.info | test.py:49 | test.py:49:11:49:41 | cross_over() | | +| Taint exception.info | test.py:52 | test.py:52:19:52:21 | arg | p0 = exception.info | --> | Taint exception.info | test.py:53 | test.py:53:12:53:14 | arg | p0 = exception.info | +| Taint externally controlled string | test.py:5 | test.py:5:22:5:35 | TAINTED_STRING | | --> | Taint externally controlled string | test.py:6 | test.py:6:31:6:44 | tainted_string | | | Taint externally controlled string | test.py:6 | test.py:6:31:6:44 | tainted_string | | --> | Taint json[externally controlled string] | test.py:6 | test.py:6:20:6:45 | Attribute() | | | Taint externally controlled string | test.py:7 | test.py:7:9:7:25 | Subscript | | --> | Taint externally controlled string | test.py:8 | test.py:8:9:8:9 | a | | +| Taint externally controlled string | test.py:7 | test.py:7:9:7:25 | Subscript | | --> | Taint externally controlled string | test.py:10 | test.py:10:10:10:10 | a | | | Taint externally controlled string | test.py:8 | test.py:8:9:8:18 | Attribute() | | --> | Taint externally controlled string | test.py:9 | test.py:9:9:9:9 | b | | -| Taint externally controlled string | test.py:12 | test.py:12:22:12:28 | TAINTED | | --> | Taint externally controlled string | test.py:13 | test.py:13:9:13:22 | tainted_string | | -| Taint externally controlled string | test.py:12 | test.py:12:22:12:28 | TAINTED | | --> | Taint externally controlled string | test.py:14 | test.py:14:9:14:22 | tainted_string | | -| Taint externally controlled string | test.py:12 | test.py:12:22:12:28 | TAINTED | | --> | Taint externally controlled string | test.py:15 | test.py:15:9:15:22 | tainted_string | | -| Taint externally controlled string | test.py:12 | test.py:12:22:12:28 | TAINTED | | --> | Taint externally controlled string | test.py:16 | test.py:16:9:16:22 | tainted_string | | -| Taint externally controlled string | test.py:12 | test.py:12:22:12:28 | TAINTED | | --> | Taint externally controlled string | test.py:17 | test.py:17:18:17:31 | tainted_string | | -| Taint externally controlled string | test.py:12 | test.py:12:22:12:28 | TAINTED | | --> | Taint externally controlled string | test.py:18 | test.py:18:14:18:27 | tainted_string | | -| Taint externally controlled string | test.py:12 | test.py:12:22:12:28 | TAINTED | | --> | Taint externally controlled string | test.py:19 | test.py:19:9:19:22 | tainted_string | | -| Taint externally controlled string | test.py:13 | test.py:13:9:13:22 | tainted_string | | --> | Taint externally controlled string | test.py:13 | test.py:13:9:13:31 | Attribute() | | -| Taint externally controlled string | test.py:14 | test.py:14:9:14:22 | tainted_string | | --> | Taint externally controlled string | test.py:14 | test.py:14:9:14:29 | Attribute() | | -| Taint externally controlled string | test.py:15 | test.py:15:9:15:22 | tainted_string | | --> | Taint externally controlled string | test.py:15 | test.py:15:9:15:25 | Subscript | | -| Taint externally controlled string | test.py:16 | test.py:16:9:16:22 | tainted_string | | --> | Taint externally controlled string | test.py:16 | test.py:16:9:16:27 | Subscript | | -| Taint externally controlled string | test.py:17 | test.py:17:18:17:31 | tainted_string | | --> | Taint externally controlled string | test.py:17 | test.py:17:9:17:32 | reversed() | | -| Taint externally controlled string | test.py:18 | test.py:18:14:18:27 | tainted_string | | --> | Taint externally controlled string | test.py:18 | test.py:18:9:18:28 | copy() | | -| Taint externally controlled string | test.py:19 | test.py:19:9:19:22 | tainted_string | | --> | Taint externally controlled string | test.py:19 | test.py:19:9:19:30 | Attribute() | | -| Taint externally controlled string | test.py:22 | test.py:22:22:22:28 | TAINTED | | --> | Taint externally controlled string | test.py:23 | test.py:23:8:23:21 | tainted_string | | -| Taint externally controlled string | test.py:22 | test.py:22:22:22:28 | TAINTED | | --> | Taint externally controlled string | test.py:26 | test.py:26:23:26:36 | tainted_string | | -| Taint externally controlled string | test.py:29 | test.py:29:22:29:28 | TAINTED | | --> | Taint externally controlled string | test.py:30 | test.py:30:8:30:21 | tainted_string | | -| Taint externally controlled string | test.py:29 | test.py:29:22:29:28 | TAINTED | | --> | Taint externally controlled string | test.py:30 | test.py:30:34:30:47 | tainted_string | | -| Taint externally controlled string | test.py:29 | test.py:29:22:29:28 | TAINTED | | --> | Taint externally controlled string | test.py:33 | test.py:33:23:33:36 | tainted_string | | -| Taint externally controlled string | test.py:36 | test.py:36:22:36:28 | TAINTED | | --> | Taint externally controlled string | test.py:37 | test.py:37:13:37:26 | tainted_string | | -| Taint externally controlled string | test.py:36 | test.py:36:22:36:28 | TAINTED | | --> | Taint externally controlled string | test.py:38 | test.py:38:15:38:28 | tainted_string | | -| Taint externally controlled string | test.py:36 | test.py:36:22:36:28 | TAINTED | | --> | Taint externally controlled string | test.py:39 | test.py:39:15:39:28 | tainted_string | | -| Taint externally controlled string | test.py:37 | test.py:37:13:37:26 | tainted_string | | --> | Taint externally controlled string | test.py:37 | test.py:37:9:37:27 | str() | | -| Taint externally controlled string | test.py:38 | test.py:38:15:38:28 | tainted_string | | --> | Taint externally controlled string | test.py:38 | test.py:38:9:38:29 | bytes() | | -| Taint externally controlled string | test.py:39 | test.py:39:15:39:28 | tainted_string | | --> | Taint externally controlled string | test.py:39 | test.py:39:9:39:46 | bytes() | | +| Taint externally controlled string | test.py:8 | test.py:8:9:8:18 | Attribute() | | --> | Taint externally controlled string | test.py:10 | test.py:10:13:10:13 | b | | +| Taint externally controlled string | test.py:9 | test.py:9:9:9:14 | Subscript | | --> | Taint externally controlled string | test.py:10 | test.py:10:16:10:16 | c | | +| Taint externally controlled string | test.py:13 | test.py:13:22:13:35 | TAINTED_STRING | | --> | Taint externally controlled string | test.py:14 | test.py:14:9:14:22 | tainted_string | | +| Taint externally controlled string | test.py:13 | test.py:13:22:13:35 | TAINTED_STRING | | --> | Taint externally controlled string | test.py:15 | test.py:15:9:15:22 | tainted_string | | +| Taint externally controlled string | test.py:13 | test.py:13:22:13:35 | TAINTED_STRING | | --> | Taint externally controlled string | test.py:16 | test.py:16:9:16:22 | tainted_string | | +| Taint externally controlled string | test.py:13 | test.py:13:22:13:35 | TAINTED_STRING | | --> | Taint externally controlled string | test.py:17 | test.py:17:9:17:22 | tainted_string | | +| Taint externally controlled string | test.py:13 | test.py:13:22:13:35 | TAINTED_STRING | | --> | Taint externally controlled string | test.py:18 | test.py:18:18:18:31 | tainted_string | | +| Taint externally controlled string | test.py:13 | test.py:13:22:13:35 | TAINTED_STRING | | --> | Taint externally controlled string | test.py:19 | test.py:19:14:19:27 | tainted_string | | +| Taint externally controlled string | test.py:13 | test.py:13:22:13:35 | TAINTED_STRING | | --> | Taint externally controlled string | test.py:20 | test.py:20:9:20:22 | tainted_string | | +| Taint externally controlled string | test.py:14 | test.py:14:9:14:22 | tainted_string | | --> | Taint externally controlled string | test.py:14 | test.py:14:9:14:31 | Attribute() | | +| Taint externally controlled string | test.py:14 | test.py:14:9:14:31 | Attribute() | | --> | Taint externally controlled string | test.py:21 | test.py:21:10:21:10 | a | | +| Taint externally controlled string | test.py:15 | test.py:15:9:15:22 | tainted_string | | --> | Taint externally controlled string | test.py:15 | test.py:15:9:15:29 | Attribute() | | +| Taint externally controlled string | test.py:15 | test.py:15:9:15:29 | Attribute() | | --> | Taint externally controlled string | test.py:21 | test.py:21:13:21:13 | b | | +| Taint externally controlled string | test.py:16 | test.py:16:9:16:22 | tainted_string | | --> | Taint externally controlled string | test.py:16 | test.py:16:9:16:25 | Subscript | | +| Taint externally controlled string | test.py:16 | test.py:16:9:16:25 | Subscript | | --> | Taint externally controlled string | test.py:21 | test.py:21:16:21:16 | c | | +| Taint externally controlled string | test.py:17 | test.py:17:9:17:22 | tainted_string | | --> | Taint externally controlled string | test.py:17 | test.py:17:9:17:27 | Subscript | | +| Taint externally controlled string | test.py:17 | test.py:17:9:17:27 | Subscript | | --> | Taint externally controlled string | test.py:21 | test.py:21:19:21:19 | d | | +| Taint externally controlled string | test.py:18 | test.py:18:9:18:32 | reversed() | | --> | Taint externally controlled string | test.py:21 | test.py:21:22:21:22 | e | | +| Taint externally controlled string | test.py:18 | test.py:18:18:18:31 | tainted_string | | --> | Taint externally controlled string | test.py:18 | test.py:18:9:18:32 | reversed() | | +| Taint externally controlled string | test.py:19 | test.py:19:9:19:28 | copy() | | --> | Taint externally controlled string | test.py:21 | test.py:21:25:21:25 | f | | +| Taint externally controlled string | test.py:19 | test.py:19:14:19:27 | tainted_string | | --> | Taint externally controlled string | test.py:19 | test.py:19:9:19:28 | copy() | | +| Taint externally controlled string | test.py:20 | test.py:20:9:20:22 | tainted_string | | --> | Taint externally controlled string | test.py:20 | test.py:20:9:20:30 | Attribute() | | +| Taint externally controlled string | test.py:20 | test.py:20:9:20:30 | Attribute() | | --> | Taint externally controlled string | test.py:21 | test.py:21:28:21:28 | g | | +| Taint externally controlled string | test.py:24 | test.py:24:22:24:35 | TAINTED_STRING | | --> | Taint externally controlled string | test.py:25 | test.py:25:8:25:21 | tainted_string | | +| Taint externally controlled string | test.py:24 | test.py:24:22:24:35 | TAINTED_STRING | | --> | Taint externally controlled string | test.py:28 | test.py:28:14:28:27 | tainted_string | | +| Taint externally controlled string | test.py:31 | test.py:31:22:31:35 | TAINTED_STRING | | --> | Taint externally controlled string | test.py:32 | test.py:32:8:32:21 | tainted_string | | +| Taint externally controlled string | test.py:31 | test.py:31:22:31:35 | TAINTED_STRING | | --> | Taint externally controlled string | test.py:32 | test.py:32:34:32:47 | tainted_string | | +| Taint externally controlled string | test.py:31 | test.py:31:22:31:35 | TAINTED_STRING | | --> | Taint externally controlled string | test.py:35 | test.py:35:14:35:27 | tainted_string | | +| Taint externally controlled string | test.py:38 | test.py:38:22:38:35 | TAINTED_STRING | | --> | Taint externally controlled string | test.py:39 | test.py:39:13:39:26 | tainted_string | | +| Taint externally controlled string | test.py:38 | test.py:38:22:38:35 | TAINTED_STRING | | --> | Taint externally controlled string | test.py:40 | test.py:40:15:40:28 | tainted_string | | +| Taint externally controlled string | test.py:38 | test.py:38:22:38:35 | TAINTED_STRING | | --> | Taint externally controlled string | test.py:41 | test.py:41:15:41:28 | tainted_string | | +| Taint externally controlled string | test.py:39 | test.py:39:9:39:27 | str() | | --> | Taint externally controlled string | test.py:42 | test.py:42:10:42:10 | a | | +| Taint externally controlled string | test.py:39 | test.py:39:13:39:26 | tainted_string | | --> | Taint externally controlled string | test.py:39 | test.py:39:9:39:27 | str() | | +| Taint externally controlled string | test.py:40 | test.py:40:9:40:29 | bytes() | | --> | Taint externally controlled string | test.py:42 | test.py:42:13:42:13 | b | | +| Taint externally controlled string | test.py:40 | test.py:40:15:40:28 | tainted_string | | --> | Taint externally controlled string | test.py:40 | test.py:40:9:40:29 | bytes() | | +| Taint externally controlled string | test.py:41 | test.py:41:9:41:46 | bytes() | | --> | Taint externally controlled string | test.py:42 | test.py:42:16:42:16 | c | | +| Taint externally controlled string | test.py:41 | test.py:41:15:41:28 | tainted_string | | --> | Taint externally controlled string | test.py:41 | test.py:41:9:41:46 | bytes() | | +| Taint externally controlled string | test.py:44 | test.py:44:22:44:26 | taint | p1 = externally controlled string | --> | Taint externally controlled string | test.py:45 | test.py:45:17:45:21 | taint | p1 = externally controlled string | +| Taint externally controlled string | test.py:45 | test.py:45:17:45:21 | taint | p1 = externally controlled string | --> | Taint externally controlled string | test.py:45 | test.py:45:12:45:22 | func() | p1 = externally controlled string | +| Taint externally controlled string | test.py:45 | test.py:45:17:45:21 | taint | p1 = externally controlled string | --> | Taint externally controlled string | test.py:52 | test.py:52:19:52:21 | arg | p0 = externally controlled string | +| Taint externally controlled string | test.py:52 | test.py:52:19:52:21 | arg | p0 = externally controlled string | --> | Taint externally controlled string | test.py:53 | test.py:53:12:53:14 | arg | p0 = externally controlled string | +| Taint externally controlled string | test.py:56 | test.py:56:11:56:24 | TAINTED_STRING | | --> | Taint externally controlled string | test.py:57 | test.py:57:38:57:40 | ext | | +| Taint externally controlled string | test.py:57 | test.py:57:11:57:41 | cross_over() | | --> | Taint externally controlled string | test.py:58 | test.py:58:10:58:12 | res | | +| Taint externally controlled string | test.py:57 | test.py:57:38:57:40 | ext | | --> | Taint externally controlled string | test.py:44 | test.py:44:22:44:26 | taint | p1 = externally controlled string | +| Taint externally controlled string | test.py:57 | test.py:57:38:57:40 | ext | | --> | Taint externally controlled string | test.py:57 | test.py:57:11:57:41 | cross_over() | | +| Taint externally controlled string | test.py:66 | test.py:66:22:66:35 | TAINTED_STRING | | --> | Taint externally controlled string | test.py:67 | test.py:67:29:67:42 | tainted_string | | +| Taint externally controlled string | test.py:66 | test.py:66:22:66:35 | TAINTED_STRING | | --> | Taint externally controlled string | test.py:68 | test.py:68:29:68:42 | tainted_string | | +| Taint externally controlled string | test.py:67 | test.py:67:29:67:42 | tainted_string | | --> | Taint [externally controlled string] | test.py:67 | test.py:67:20:67:43 | urlsplit() | | +| Taint externally controlled string | test.py:68 | test.py:68:29:68:42 | tainted_string | | --> | Taint [externally controlled string] | test.py:68 | test.py:68:20:68:43 | urlparse() | | | Taint json[externally controlled string] | test.py:6 | test.py:6:20:6:45 | Attribute() | | --> | Taint json[externally controlled string] | test.py:7 | test.py:7:9:7:20 | tainted_json | | | Taint json[externally controlled string] | test.py:7 | test.py:7:9:7:20 | tainted_json | | --> | Taint externally controlled string | test.py:7 | test.py:7:9:7:25 | Subscript | | | Taint json[externally controlled string] | test.py:7 | test.py:7:9:7:20 | tainted_json | | --> | Taint json[externally controlled string] | test.py:7 | test.py:7:9:7:25 | Subscript | | | Taint json[externally controlled string] | test.py:7 | test.py:7:9:7:25 | Subscript | | --> | Taint json[externally controlled string] | test.py:8 | test.py:8:9:8:9 | a | | +| Taint json[externally controlled string] | test.py:7 | test.py:7:9:7:25 | Subscript | | --> | Taint json[externally controlled string] | test.py:10 | test.py:10:10:10:10 | a | | | Taint json[externally controlled string] | test.py:8 | test.py:8:9:8:9 | a | | --> | Taint externally controlled string | test.py:8 | test.py:8:9:8:18 | Attribute() | | | Taint json[externally controlled string] | test.py:8 | test.py:8:9:8:9 | a | | --> | Taint json[externally controlled string] | test.py:8 | test.py:8:9:8:18 | Attribute() | | | Taint json[externally controlled string] | test.py:8 | test.py:8:9:8:18 | Attribute() | | --> | Taint json[externally controlled string] | test.py:9 | test.py:9:9:9:9 | b | | +| Taint json[externally controlled string] | test.py:8 | test.py:8:9:8:18 | Attribute() | | --> | Taint json[externally controlled string] | test.py:10 | test.py:10:13:10:13 | b | | | Taint json[externally controlled string] | test.py:9 | test.py:9:9:9:9 | b | | --> | Taint externally controlled string | test.py:9 | test.py:9:9:9:14 | Subscript | | | Taint json[externally controlled string] | test.py:9 | test.py:9:9:9:9 | b | | --> | Taint json[externally controlled string] | test.py:9 | test.py:9:9:9:14 | Subscript | | +| Taint json[externally controlled string] | test.py:9 | test.py:9:9:9:14 | Subscript | | --> | Taint json[externally controlled string] | test.py:10 | test.py:10:16:10:16 | c | | diff --git a/python/ql/test/library-tests/taint/strings/TestTaint.expected b/python/ql/test/library-tests/taint/strings/TestTaint.expected new file mode 100644 index 00000000000..ff7372a8ce6 --- /dev/null +++ b/python/ql/test/library-tests/taint/strings/TestTaint.expected @@ -0,0 +1,24 @@ +| test.py:10 | test_json | a | externally controlled string | +| test.py:10 | test_json | a | json[externally controlled string] | +| test.py:10 | test_json | b | externally controlled string | +| test.py:10 | test_json | b | json[externally controlled string] | +| test.py:10 | test_json | c | externally controlled string | +| test.py:10 | test_json | c | json[externally controlled string] | +| test.py:21 | test_str | a | externally controlled string | +| test.py:21 | test_str | b | externally controlled string | +| test.py:21 | test_str | c | externally controlled string | +| test.py:21 | test_str | d | externally controlled string | +| test.py:21 | test_str | e | externally controlled string | +| test.py:21 | test_str | f | externally controlled string | +| test.py:21 | test_str | g | externally controlled string | +| test.py:26 | test_const_sanitizer1 | tainted_string | NO TAINT | +| test.py:28 | test_const_sanitizer1 | tainted_string | externally controlled string | +| test.py:33 | test_const_sanitizer2 | tainted_string | NO TAINT | +| test.py:35 | test_const_sanitizer2 | tainted_string | externally controlled string | +| test.py:42 | test_str2 | a | externally controlled string | +| test.py:42 | test_str2 | b | externally controlled string | +| test.py:42 | test_str2 | c | externally controlled string | +| test.py:50 | test_exc_info | res | exception.info | +| test.py:58 | test_untrusted | res | externally controlled string | +| test.py:69 | test_urlsplit_urlparse | urlparse_res | [externally controlled string] | +| test.py:69 | test_urlsplit_urlparse | urlsplit_res | [externally controlled string] | diff --git a/python/ql/test/library-tests/taint/strings/TestTaint.ql b/python/ql/test/library-tests/taint/strings/TestTaint.ql new file mode 100644 index 00000000000..92657b1fef9 --- /dev/null +++ b/python/ql/test/library-tests/taint/strings/TestTaint.ql @@ -0,0 +1,18 @@ +import python +import semmle.python.security.TaintTracking +import Taint + +from Call call, Expr arg, string taint_string +where + call.getLocation().getFile().getShortName() = "test.py" and + call.getFunc().(Name).getId() = "test" and + arg = call.getAnArg() and + ( + not exists(TaintedNode tainted | tainted.getAstNode() = arg) and + taint_string = "NO TAINT" + or + exists(TaintedNode tainted | tainted.getAstNode() = arg | + taint_string = tainted.getTaintKind().toString() + ) + ) +select arg.getLocation().toString(), call.getScope().(Function).getName(), arg.toString(), taint_string diff --git a/python/ql/test/library-tests/taint/strings/test.py b/python/ql/test/library-tests/taint/strings/test.py index 1b2b439a648..207f52c807c 100644 --- a/python/ql/test/library-tests/taint/strings/test.py +++ b/python/ql/test/library-tests/taint/strings/test.py @@ -2,41 +2,44 @@ import json from copy import copy def test_json(): - tainted_string = TAINTED + tainted_string = TAINTED_STRING tainted_json = json.loads(tainted_string) a = tainted_json["x"] b = a.get("y") c = b["z"] + test(a, b, c) def test_str(): - tainted_string = TAINTED + tainted_string = TAINTED_STRING a = tainted_string.ljust(8) b = tainted_string.copy() c = tainted_string[:] d = tainted_string[::2] e = reversed(tainted_string) f = copy(tainted_string) - h = tainted_string.strip() + g = tainted_string.strip() + test(a, b, c, d, e, f, g) def test_const_sanitizer1(): - tainted_string = TAINTED + tainted_string = TAINTED_STRING if tainted_string == "OK": - not_tainted(tainted_string) + test(tainted_string) # not tainted else: - still_tainted(tainted_string) + test(tainted_string) # still tainted def test_const_sanitizer2(): - tainted_string = TAINTED + tainted_string = TAINTED_STRING if tainted_string == "OK" or tainted_string == "ALSO_OK": - not_tainted(tainted_string) + test(tainted_string) # not tainted else: - still_tainted(tainted_string) + test(tainted_string) # still tainted def test_str2(): - tainted_string = TAINTED + tainted_string = TAINTED_STRING a = str(tainted_string) b = bytes(tainted_string) # This is an error in Python 3 c = bytes(tainted_string, encoding="utf8") # This is an error in Python 2 + test(a, b, c) def cross_over(func, taint): return func(taint) @@ -44,13 +47,23 @@ def cross_over(func, taint): def test_exc_info(): info = TAINTED_EXCEPTION_INFO res = cross_over(exc_info_call, info) + test(res) def exc_info_call(arg): return arg def test_untrusted(): - ext = TAINTED_EXTERNAL_STRING + ext = TAINTED_STRING res = cross_over(untrusted_call, ext) + test(res) def exc_untrusted_call(arg): return arg + +from six.moves.urllib.parse import urlsplit, urlparse + +def test_urlsplit_urlparse(): + tainted_string = TAINTED_STRING + urlsplit_res = urlsplit(tainted_string) + urlparse_res = urlparse(tainted_string) + test(urlsplit_res, urlparse_res) diff --git a/python/ql/test/library-tests/taint/unpacking/TestStep.expected b/python/ql/test/library-tests/taint/unpacking/TestStep.expected index 5d800e6b5b8..51c4e7fc410 100644 --- a/python/ql/test/library-tests/taint/unpacking/TestStep.expected +++ b/python/ql/test/library-tests/taint/unpacking/TestStep.expected @@ -1,35 +1,8 @@ | Taint [[externally controlled string]] | test.py:19 | test.py:19:10:19:18 | List | | --> | Taint [[externally controlled string]] | test.py:22 | test.py:22:28:22:29 | ll | | | Taint [[externally controlled string]] | test.py:19 | test.py:19:10:19:18 | List | | --> | Taint [[externally controlled string]] | test.py:26 | test.py:26:28:26:29 | ll | | | Taint [[externally controlled string]] | test.py:19 | test.py:19:10:19:18 | List | | --> | Taint [[externally controlled string]] | test.py:30 | test.py:30:28:30:29 | ll | | -| Taint [[externally controlled string]] | test.py:22 | test.py:22:28:22:29 | ll | | --> | Taint [externally controlled string] | test.py:23 | test.py:23:22:23:22 | b | | -| Taint [[externally controlled string]] | test.py:22 | test.py:22:28:22:29 | ll | | --> | Taint [externally controlled string] | test.py:23 | test.py:23:25:23:25 | c | | -| Taint [[externally controlled string]] | test.py:22 | test.py:22:28:22:29 | ll | | --> | Taint externally controlled string | test.py:23 | test.py:23:10:23:11 | a1 | | -| Taint [[externally controlled string]] | test.py:22 | test.py:22:28:22:29 | ll | | --> | Taint externally controlled string | test.py:23 | test.py:23:14:23:15 | a2 | | -| Taint [[externally controlled string]] | test.py:22 | test.py:22:28:22:29 | ll | | --> | Taint externally controlled string | test.py:23 | test.py:23:18:23:19 | a3 | | -| Taint [[externally controlled string]] | test.py:26 | test.py:26:28:26:29 | ll | | --> | Taint [externally controlled string] | test.py:27 | test.py:27:22:27:22 | b | | -| Taint [[externally controlled string]] | test.py:26 | test.py:26:28:26:29 | ll | | --> | Taint [externally controlled string] | test.py:27 | test.py:27:25:27:25 | c | | -| Taint [[externally controlled string]] | test.py:26 | test.py:26:28:26:29 | ll | | --> | Taint externally controlled string | test.py:27 | test.py:27:10:27:11 | a1 | | -| Taint [[externally controlled string]] | test.py:26 | test.py:26:28:26:29 | ll | | --> | Taint externally controlled string | test.py:27 | test.py:27:14:27:15 | a2 | | -| Taint [[externally controlled string]] | test.py:26 | test.py:26:28:26:29 | ll | | --> | Taint externally controlled string | test.py:27 | test.py:27:18:27:19 | a3 | | -| Taint [[externally controlled string]] | test.py:30 | test.py:30:28:30:29 | ll | | --> | Taint [externally controlled string] | test.py:31 | test.py:31:22:31:22 | b | | -| Taint [[externally controlled string]] | test.py:30 | test.py:30:28:30:29 | ll | | --> | Taint [externally controlled string] | test.py:31 | test.py:31:25:31:25 | c | | -| Taint [[externally controlled string]] | test.py:30 | test.py:30:28:30:29 | ll | | --> | Taint externally controlled string | test.py:31 | test.py:31:10:31:11 | a1 | | -| Taint [[externally controlled string]] | test.py:30 | test.py:30:28:30:29 | ll | | --> | Taint externally controlled string | test.py:31 | test.py:31:14:31:15 | a2 | | -| Taint [[externally controlled string]] | test.py:30 | test.py:30:28:30:29 | ll | | --> | Taint externally controlled string | test.py:31 | test.py:31:18:31:19 | a3 | | -| Taint [[externally controlled string]] | test.py:47 | test.py:47:28:47:54 | Tuple | | --> | Taint externally controlled string | test.py:48 | test.py:48:10:48:10 | a | | -| Taint [[externally controlled string]] | test.py:47 | test.py:47:28:47:54 | Tuple | | --> | Taint externally controlled string | test.py:48 | test.py:48:13:48:13 | b | | -| Taint [[externally controlled string]] | test.py:47 | test.py:47:28:47:54 | Tuple | | --> | Taint externally controlled string | test.py:48 | test.py:48:16:48:16 | c | | -| Taint [[externally controlled string]] | test.py:47 | test.py:47:28:47:54 | Tuple | | --> | Taint externally controlled string | test.py:48 | test.py:48:19:48:19 | d | | -| Taint [[externally controlled string]] | test.py:47 | test.py:47:28:47:54 | Tuple | | --> | Taint externally controlled string | test.py:48 | test.py:48:22:48:22 | e | | -| Taint [[externally controlled string]] | test.py:47 | test.py:47:28:47:54 | Tuple | | --> | Taint externally controlled string | test.py:48 | test.py:48:25:48:25 | f | | | Taint [externally controlled string] | test.py:6 | test.py:6:9:6:20 | TAINTED_LIST | | --> | Taint [externally controlled string] | test.py:7 | test.py:7:15:7:15 | l | | -| Taint [externally controlled string] | test.py:7 | test.py:7:15:7:15 | l | | --> | Taint externally controlled string | test.py:8 | test.py:8:10:8:10 | a | | -| Taint [externally controlled string] | test.py:7 | test.py:7:15:7:15 | l | | --> | Taint externally controlled string | test.py:8 | test.py:8:13:8:13 | b | | -| Taint [externally controlled string] | test.py:7 | test.py:7:15:7:15 | l | | --> | Taint externally controlled string | test.py:8 | test.py:8:16:8:16 | c | | | Taint [externally controlled string] | test.py:12 | test.py:12:9:12:20 | TAINTED_LIST | | --> | Taint [externally controlled string] | test.py:13 | test.py:13:17:13:17 | l | | -| Taint [externally controlled string] | test.py:13 | test.py:13:17:13:17 | l | | --> | Taint externally controlled string | test.py:14 | test.py:14:10:14:10 | a | | -| Taint [externally controlled string] | test.py:13 | test.py:13:17:13:17 | l | | --> | Taint externally controlled string | test.py:14 | test.py:14:13:14:13 | b | | -| Taint [externally controlled string] | test.py:13 | test.py:13:17:13:17 | l | | --> | Taint externally controlled string | test.py:14 | test.py:14:16:14:16 | c | | | Taint [externally controlled string] | test.py:18 | test.py:18:9:18:20 | TAINTED_LIST | | --> | Taint [externally controlled string] | test.py:19 | test.py:19:11:19:11 | l | | | Taint [externally controlled string] | test.py:18 | test.py:18:9:18:20 | TAINTED_LIST | | --> | Taint [externally controlled string] | test.py:19 | test.py:19:14:19:14 | l | | | Taint [externally controlled string] | test.py:18 | test.py:18:9:18:20 | TAINTED_LIST | | --> | Taint [externally controlled string] | test.py:19 | test.py:19:17:19:17 | l | | diff --git a/python/ql/test/library-tests/taint/unpacking/TestTaint.expected b/python/ql/test/library-tests/taint/unpacking/TestTaint.expected index d1bad70f811..dc704043c7d 100644 --- a/python/ql/test/library-tests/taint/unpacking/TestTaint.expected +++ b/python/ql/test/library-tests/taint/unpacking/TestTaint.expected @@ -1,33 +1,33 @@ -| test.py:8 | unpacking | a | externally controlled string | -| test.py:8 | unpacking | b | externally controlled string | -| test.py:8 | unpacking | c | externally controlled string | -| test.py:14 | unpacking_to_list | a | externally controlled string | -| test.py:14 | unpacking_to_list | b | externally controlled string | -| test.py:14 | unpacking_to_list | c | externally controlled string | -| test.py:23 | nested | a1 | externally controlled string | -| test.py:23 | nested | a2 | externally controlled string | -| test.py:23 | nested | a3 | externally controlled string | -| test.py:23 | nested | b | [externally controlled string] | -| test.py:23 | nested | c | [externally controlled string] | -| test.py:27 | nested | a1 | externally controlled string | -| test.py:27 | nested | a2 | externally controlled string | -| test.py:27 | nested | a3 | externally controlled string | -| test.py:27 | nested | b | [externally controlled string] | -| test.py:27 | nested | c | [externally controlled string] | -| test.py:31 | nested | a1 | externally controlled string | -| test.py:31 | nested | a2 | externally controlled string | -| test.py:31 | nested | a3 | externally controlled string | -| test.py:31 | nested | b | [externally controlled string] | -| test.py:31 | nested | c | [externally controlled string] | +| test.py:8 | unpacking | a | NO TAINT | +| test.py:8 | unpacking | b | NO TAINT | +| test.py:8 | unpacking | c | NO TAINT | +| test.py:14 | unpacking_to_list | a | NO TAINT | +| test.py:14 | unpacking_to_list | b | NO TAINT | +| test.py:14 | unpacking_to_list | c | NO TAINT | +| test.py:23 | nested | a1 | NO TAINT | +| test.py:23 | nested | a2 | NO TAINT | +| test.py:23 | nested | a3 | NO TAINT | +| test.py:23 | nested | b | NO TAINT | +| test.py:23 | nested | c | NO TAINT | +| test.py:27 | nested | a1 | NO TAINT | +| test.py:27 | nested | a2 | NO TAINT | +| test.py:27 | nested | a3 | NO TAINT | +| test.py:27 | nested | b | NO TAINT | +| test.py:27 | nested | c | NO TAINT | +| test.py:31 | nested | a1 | NO TAINT | +| test.py:31 | nested | a2 | NO TAINT | +| test.py:31 | nested | a3 | NO TAINT | +| test.py:31 | nested | b | NO TAINT | +| test.py:31 | nested | c | NO TAINT | | test.py:38 | unpack_from_set | a | NO TAINT | | test.py:38 | unpack_from_set | b | NO TAINT | | test.py:38 | unpack_from_set | c | NO TAINT | -| test.py:48 | contrived_1 | a | externally controlled string | -| test.py:48 | contrived_1 | b | externally controlled string | -| test.py:48 | contrived_1 | c | externally controlled string | -| test.py:48 | contrived_1 | d | externally controlled string | -| test.py:48 | contrived_1 | e | externally controlled string | -| test.py:48 | contrived_1 | f | externally controlled string | +| test.py:48 | contrived_1 | a | NO TAINT | +| test.py:48 | contrived_1 | b | NO TAINT | +| test.py:48 | contrived_1 | c | NO TAINT | +| test.py:48 | contrived_1 | d | NO TAINT | +| test.py:48 | contrived_1 | e | NO TAINT | +| test.py:48 | contrived_1 | f | NO TAINT | | test.py:56 | contrived_2 | a | NO TAINT | | test.py:56 | contrived_2 | b | NO TAINT | | test.py:56 | contrived_2 | c | NO TAINT | diff --git a/python/ql/test/library-tests/web/bottle/options b/python/ql/test/library-tests/web/bottle/options index e6bd3a732dc..7fb713d5924 100644 --- a/python/ql/test/library-tests/web/bottle/options +++ b/python/ql/test/library-tests/web/bottle/options @@ -1,2 +1 @@ semmle-extractor-options: --max-import-depth=3 -p ../../../query-tests/Security/lib/ -optimize: true diff --git a/python/ql/test/library-tests/web/cherrypy/options b/python/ql/test/library-tests/web/cherrypy/options index e6bd3a732dc..7fb713d5924 100644 --- a/python/ql/test/library-tests/web/cherrypy/options +++ b/python/ql/test/library-tests/web/cherrypy/options @@ -1,2 +1 @@ semmle-extractor-options: --max-import-depth=3 -p ../../../query-tests/Security/lib/ -optimize: true diff --git a/python/ql/test/library-tests/web/django/options b/python/ql/test/library-tests/web/django/options index d7a40c71d46..a87f995c396 100644 --- a/python/ql/test/library-tests/web/django/options +++ b/python/ql/test/library-tests/web/django/options @@ -1,2 +1 @@ semmle-extractor-options: --lang=3 --max-import-depth=3 -p ../../../query-tests/Security/lib/ -optimize: true diff --git a/python/ql/test/library-tests/web/falcon/options b/python/ql/test/library-tests/web/falcon/options index e6bd3a732dc..7fb713d5924 100644 --- a/python/ql/test/library-tests/web/falcon/options +++ b/python/ql/test/library-tests/web/falcon/options @@ -1,2 +1 @@ semmle-extractor-options: --max-import-depth=3 -p ../../../query-tests/Security/lib/ -optimize: true diff --git a/python/ql/test/library-tests/web/flask/options b/python/ql/test/library-tests/web/flask/options index e6bd3a732dc..7fb713d5924 100644 --- a/python/ql/test/library-tests/web/flask/options +++ b/python/ql/test/library-tests/web/flask/options @@ -1,2 +1 @@ semmle-extractor-options: --max-import-depth=3 -p ../../../query-tests/Security/lib/ -optimize: true diff --git a/python/ql/test/library-tests/web/pyramid/options b/python/ql/test/library-tests/web/pyramid/options index 0267f80a6d6..1d132442a3b 100644 --- a/python/ql/test/library-tests/web/pyramid/options +++ b/python/ql/test/library-tests/web/pyramid/options @@ -1,2 +1 @@ semmle-extractor-options: --max-import-depth=2 -p ../../../query-tests/Security/lib/ -optimize: true diff --git a/python/ql/test/library-tests/web/tornado/options b/python/ql/test/library-tests/web/tornado/options index 0267f80a6d6..1d132442a3b 100644 --- a/python/ql/test/library-tests/web/tornado/options +++ b/python/ql/test/library-tests/web/tornado/options @@ -1,2 +1 @@ semmle-extractor-options: --max-import-depth=2 -p ../../../query-tests/Security/lib/ -optimize: true diff --git a/python/ql/test/library-tests/web/turbogears/options b/python/ql/test/library-tests/web/turbogears/options index e6bd3a732dc..7fb713d5924 100644 --- a/python/ql/test/library-tests/web/turbogears/options +++ b/python/ql/test/library-tests/web/turbogears/options @@ -1,2 +1 @@ semmle-extractor-options: --max-import-depth=3 -p ../../../query-tests/Security/lib/ -optimize: true diff --git a/python/ql/test/library-tests/web/twisted/options b/python/ql/test/library-tests/web/twisted/options index 6979a743e58..4084b102b55 100644 --- a/python/ql/test/library-tests/web/twisted/options +++ b/python/ql/test/library-tests/web/twisted/options @@ -1,2 +1 @@ semmle-extractor-options: --max-import-depth=1 -p ../../../query-tests/Security/lib/ -optimize: true diff --git a/python/ql/test/query-tests/Expressions/general/expressions_test.py b/python/ql/test/query-tests/Expressions/general/expressions_test.py index 1d802f1bfd6..5e07b58e204 100644 --- a/python/ql/test/query-tests/Expressions/general/expressions_test.py +++ b/python/ql/test/query-tests/Expressions/general/expressions_test.py @@ -242,3 +242,40 @@ from types import MappingProxyType def mpt_arg(d=MappingProxyType({})): return 1 in d + + + + + + + +#### UseofApply.ql + +# Use of the builtin function `apply` is generally considered bad now that the +# ability to destructure lists of arguments is possible, but we should not flag +# cases where the function is merely named `apply` rather than being the actual +# builtin `apply` function. + +def useofapply(): + + def foo(): + pass + + + + # Positive Cases + + # This use of `apply` is a reference to the builtin function and so SHOULD be + # caught by the query. + apply(foo, [1]) + + + + # Negative Cases + + # This use of `apply` is a reference to the locally defined function inside of + # `local`, and so SHOULD NOT be caught by the query. + def local(): + def apply(f): + pass + apply(foo)([1]) diff --git a/python/ql/test/query-tests/Functions/general/SignatureSpecialMethods.expected b/python/ql/test/query-tests/Functions/general/SignatureSpecialMethods.expected index bda8b8e6472..8f25a085f4f 100644 --- a/python/ql/test/query-tests/Functions/general/SignatureSpecialMethods.expected +++ b/python/ql/test/query-tests/Functions/general/SignatureSpecialMethods.expected @@ -1,9 +1,9 @@ -| om_test.py:59:5:59:28 | Function __div__ | Too many parameters for special method __div__, which has 3 parameters, but should have 2, in class $@. | om_test.py:57:1:57:28 | class WrongSpecials | WrongSpecials | -| om_test.py:62:5:62:22 | Function __mul__ | Too few parameters for special method __mul__, which has 1 parameter, but should have 2, in class $@. | om_test.py:57:1:57:28 | class WrongSpecials | WrongSpecials | -| om_test.py:65:5:65:29 | Function __neg__ | Too many parameters for special method __neg__, which has 2 parameters, but should have 1, in class $@. | om_test.py:57:1:57:28 | class WrongSpecials | WrongSpecials | -| om_test.py:68:5:68:35 | Function __exit__ | Too few parameters for special method __exit__, which has 3 parameters, but should have 4, in class $@. | om_test.py:57:1:57:28 | class WrongSpecials | WrongSpecials | -| om_test.py:71:5:71:19 | Function __repr__ | Too few parameters for special method __repr__, which has no parameters, but should have 1, in class $@. | om_test.py:57:1:57:28 | class WrongSpecials | WrongSpecials | -| om_test.py:74:5:74:46 | Function __add__ | 1 default values(s) will never be used for special method __add__, in class $@. | om_test.py:57:1:57:28 | class WrongSpecials | WrongSpecials | -| om_test.py:97:15:97:34 | Function lambda | Too few parameters for special method __sub__, which has 1 parameter, but should have 2, in class $@. | om_test.py:95:1:95:28 | class NotOKSpecials | NotOKSpecials | +| om_test.py:59:5:59:28 | Function WrongSpecials.__div__ | Too many parameters for special method __div__, which has 3 parameters, but should have 2, in class $@. | om_test.py:57:1:57:28 | class WrongSpecials | WrongSpecials | +| om_test.py:62:5:62:22 | Function WrongSpecials.__mul__ | Too few parameters for special method __mul__, which has 1 parameter, but should have 2, in class $@. | om_test.py:57:1:57:28 | class WrongSpecials | WrongSpecials | +| om_test.py:65:5:65:29 | Function WrongSpecials.__neg__ | Too many parameters for special method __neg__, which has 2 parameters, but should have 1, in class $@. | om_test.py:57:1:57:28 | class WrongSpecials | WrongSpecials | +| om_test.py:68:5:68:35 | Function WrongSpecials.__exit__ | Too few parameters for special method __exit__, which has 3 parameters, but should have 4, in class $@. | om_test.py:57:1:57:28 | class WrongSpecials | WrongSpecials | +| om_test.py:71:5:71:19 | Function WrongSpecials.__repr__ | Too few parameters for special method __repr__, which has no parameters, but should have 1, in class $@. | om_test.py:57:1:57:28 | class WrongSpecials | WrongSpecials | +| om_test.py:74:5:74:46 | Function WrongSpecials.__add__ | 1 default values(s) will never be used for special method __add__, in class $@. | om_test.py:57:1:57:28 | class WrongSpecials | WrongSpecials | +| om_test.py:97:15:97:34 | Function NotOKSpecials.lambda | Too few parameters for special method __sub__, which has 1 parameter, but should have 2, in class $@. | om_test.py:95:1:95:28 | class NotOKSpecials | NotOKSpecials | | protocols.py:104:1:104:12 | Function f | Too few parameters for special method __add__, which has 1 parameter, but should have 2, in class $@. | protocols.py:107:1:107:29 | class MissingMethods | MissingMethods | | protocols.py:104:1:104:12 | Function f | Too few parameters for special method __set__, which has 1 parameter, but should have 3, in class $@. | protocols.py:107:1:107:29 | class MissingMethods | MissingMethods | diff --git a/python/ql/test/query-tests/Security/CWE-022/options b/python/ql/test/query-tests/Security/CWE-022/options index dce78f52325..492768b3481 100644 --- a/python/ql/test/query-tests/Security/CWE-022/options +++ b/python/ql/test/query-tests/Security/CWE-022/options @@ -1,2 +1 @@ semmle-extractor-options: -p ../lib/ --max-import-depth=3 -optimize: true diff --git a/python/ql/test/query-tests/Security/CWE-078/options b/python/ql/test/query-tests/Security/CWE-078/options index dce78f52325..492768b3481 100644 --- a/python/ql/test/query-tests/Security/CWE-078/options +++ b/python/ql/test/query-tests/Security/CWE-078/options @@ -1,2 +1 @@ semmle-extractor-options: -p ../lib/ --max-import-depth=3 -optimize: true diff --git a/python/ql/test/query-tests/Security/CWE-094/options b/python/ql/test/query-tests/Security/CWE-094/options index dce78f52325..492768b3481 100644 --- a/python/ql/test/query-tests/Security/CWE-094/options +++ b/python/ql/test/query-tests/Security/CWE-094/options @@ -1,2 +1 @@ semmle-extractor-options: -p ../lib/ --max-import-depth=3 -optimize: true diff --git a/python/ql/test/query-tests/Security/CWE-326/options b/python/ql/test/query-tests/Security/CWE-326/options index dce78f52325..492768b3481 100644 --- a/python/ql/test/query-tests/Security/CWE-326/options +++ b/python/ql/test/query-tests/Security/CWE-326/options @@ -1,2 +1 @@ semmle-extractor-options: -p ../lib/ --max-import-depth=3 -optimize: true diff --git a/python/ql/test/query-tests/Security/CWE-327/options b/python/ql/test/query-tests/Security/CWE-327/options index dce78f52325..492768b3481 100644 --- a/python/ql/test/query-tests/Security/CWE-327/options +++ b/python/ql/test/query-tests/Security/CWE-327/options @@ -1,2 +1 @@ semmle-extractor-options: -p ../lib/ --max-import-depth=3 -optimize: true diff --git a/python/ql/test/query-tests/Security/CWE-377/options b/python/ql/test/query-tests/Security/CWE-377/options index dce78f52325..492768b3481 100644 --- a/python/ql/test/query-tests/Security/CWE-377/options +++ b/python/ql/test/query-tests/Security/CWE-377/options @@ -1,2 +1 @@ semmle-extractor-options: -p ../lib/ --max-import-depth=3 -optimize: true diff --git a/python/ql/test/query-tests/Security/CWE-502/options b/python/ql/test/query-tests/Security/CWE-502/options index b63c517ee29..84717fe64cf 100644 --- a/python/ql/test/query-tests/Security/CWE-502/options +++ b/python/ql/test/query-tests/Security/CWE-502/options @@ -1,2 +1 @@ semmle-extractor-options: --max-import-depth=2 -p ../lib -optimize: true diff --git a/python/ql/test/query-tests/Security/CWE-732/options b/python/ql/test/query-tests/Security/CWE-732/options index b63c517ee29..84717fe64cf 100644 --- a/python/ql/test/query-tests/Security/CWE-732/options +++ b/python/ql/test/query-tests/Security/CWE-732/options @@ -1,2 +1 @@ semmle-extractor-options: --max-import-depth=2 -p ../lib -optimize: true diff --git a/python/ql/test/query-tests/Security/options b/python/ql/test/query-tests/Security/options deleted file mode 100644 index 58ad829f5a8..00000000000 --- a/python/ql/test/query-tests/Security/options +++ /dev/null @@ -1 +0,0 @@ -optimize: true diff --git a/python/ql/test/query-tests/Statements/asserts/SideEffectInAssert.expected b/python/ql/test/query-tests/Statements/asserts/SideEffectInAssert.expected index 4f46cff1e76..f03252810d8 100644 --- a/python/ql/test/query-tests/Statements/asserts/SideEffectInAssert.expected +++ b/python/ql/test/query-tests/Statements/asserts/SideEffectInAssert.expected @@ -1,2 +1,3 @@ | assert.py:5:5:5:20 | Assert | This 'assert' statement contains $@ which may have side effects. | assert.py:5:13:5:19 | Yield | an expression | | assert.py:8:5:8:22 | Assert | This 'assert' statement contains $@ which may have side effects. | assert.py:8:12:8:22 | Attribute() | an expression | +| side_effect.py:5:1:5:43 | Assert | This 'assert' statement contains $@ which may have side effects. | side_effect.py:5:8:5:38 | Attribute() | an expression | diff --git a/python/ql/test/query-tests/Statements/asserts/assert.py b/python/ql/test/query-tests/Statements/asserts/assert.py index 854fffa3382..036f9b20a4e 100644 --- a/python/ql/test/query-tests/Statements/asserts/assert.py +++ b/python/ql/test/query-tests/Statements/asserts/assert.py @@ -103,4 +103,4 @@ def error_assert_in_intermediate_branch(x): elif yks(x): pass else: - pass \ No newline at end of file + pass diff --git a/python/ql/test/query-tests/Statements/asserts/side_effect.py b/python/ql/test/query-tests/Statements/asserts/side_effect.py new file mode 100644 index 00000000000..42b9f0a16a2 --- /dev/null +++ b/python/ql/test/query-tests/Statements/asserts/side_effect.py @@ -0,0 +1,5 @@ +# For now, this test lives in its own file, since including them in the top of assert.py +# messes up the results of the refers-to/points-to analysis +# see /home/rasmus/code/ql/python/ql/test/library-tests/PointsTo/regressions/subprocess-assert/mwe_failure.py +import subprocess +assert subprocess.call(['run-backup']) == 0 diff --git a/python/ql/test/query-tests/Statements/general/MismatchInMultipleAssignment.expected b/python/ql/test/query-tests/Statements/general/MismatchInMultipleAssignment.expected index 88b3dfbb008..b4755862d85 100644 --- a/python/ql/test/query-tests/Statements/general/MismatchInMultipleAssignment.expected +++ b/python/ql/test/query-tests/Statements/general/MismatchInMultipleAssignment.expected @@ -1,4 +1,4 @@ | statements_test.py:19:5:19:18 | AssignStmt | Left hand side of assignment contains 3 variables, but right hand side is a $@ of length 2. | statements_test.py:19:15:19:18 | statements_test.py:19 | tuple | -| statements_test.py:163:5:163:23 | AssignStmt | Left hand side of assignment contains 3 variables, but right hand side is a $@ of length 5. | statements_test.py:163:13:163:23 | statements_test.py:163 | list | -| statements_test.py:172:5:172:48 | AssignStmt | Left hand side of assignment contains 3 variables, but right hand side is a $@ of length 5. | statements_test.py:167:16:167:24 | statements_test.py:167 | tuple | -| statements_test.py:172:5:172:48 | AssignStmt | Left hand side of assignment contains 3 variables, but right hand side is a $@ of length 6. | statements_test.py:169:16:169:26 | statements_test.py:169 | tuple | +| statements_test.py:169:5:169:23 | AssignStmt | Left hand side of assignment contains 3 variables, but right hand side is a $@ of length 5. | statements_test.py:169:13:169:23 | statements_test.py:169 | list | +| statements_test.py:178:5:178:48 | AssignStmt | Left hand side of assignment contains 3 variables, but right hand side is a $@ of length 5. | statements_test.py:173:16:173:24 | statements_test.py:173 | tuple | +| statements_test.py:178:5:178:48 | AssignStmt | Left hand side of assignment contains 3 variables, but right hand side is a $@ of length 6. | statements_test.py:175:16:175:26 | statements_test.py:175 | tuple | diff --git a/python/ql/test/query-tests/Statements/general/NonIteratorInForLoop.expected b/python/ql/test/query-tests/Statements/general/NonIteratorInForLoop.expected index 308f0aa027e..38d063a8c0e 100644 --- a/python/ql/test/query-tests/Statements/general/NonIteratorInForLoop.expected +++ b/python/ql/test/query-tests/Statements/general/NonIteratorInForLoop.expected @@ -1 +1 @@ -| test.py:50:1:50:23 | For | $@ of class '$@' may be used in for-loop. | test.py:50:10:50:22 | ControlFlowNode for NonIterator() | Non-iterator | test.py:45:1:45:26 | class NonIterator | NonIterator | +| test.py:50:1:50:23 | For | $@ of class '$@' may be used in for-loop. | test.py:50:10:50:22 | ControlFlowNode for NonIterator() | Non-iterable | test.py:45:1:45:26 | class NonIterator | NonIterator | diff --git a/python/ql/test/query-tests/Statements/general/RedundantAssignment.expected b/python/ql/test/query-tests/Statements/general/RedundantAssignment.expected index f997ac54932..72e7116247a 100644 --- a/python/ql/test/query-tests/Statements/general/RedundantAssignment.expected +++ b/python/ql/test/query-tests/Statements/general/RedundantAssignment.expected @@ -1,3 +1,3 @@ | statements_test.py:54:5:54:9 | AssignStmt | This assignment assigns a variable to itself. | | statements_test.py:57:9:57:19 | AssignStmt | This assignment assigns a variable to itself. | -| statements_test.py:117:9:117:23 | AssignStmt | This assignment assigns a variable to itself. | +| statements_test.py:119:9:119:23 | AssignStmt | This assignment assigns a variable to itself. | diff --git a/python/ql/test/query-tests/Statements/general/UnnecessaryDelete.expected b/python/ql/test/query-tests/Statements/general/UnnecessaryDelete.expected index f6f7ca1bad2..d7dda673775 100644 --- a/python/ql/test/query-tests/Statements/general/UnnecessaryDelete.expected +++ b/python/ql/test/query-tests/Statements/general/UnnecessaryDelete.expected @@ -1 +1 @@ -| statements_test.py:181:5:181:9 | Delete | Unnecessary deletion of local variable $@ in function $@. | statements_test.py:181:9:181:9 | statements_test.py:181 | x | statements_test.py:179:1:179:31 | statements_test.py:179 | error_unnecessary_delete | +| statements_test.py:187:5:187:9 | Delete | Unnecessary deletion of local variable $@ in function $@. | statements_test.py:187:9:187:9 | statements_test.py:187 | x | statements_test.py:185:1:185:31 | statements_test.py:185 | error_unnecessary_delete | diff --git a/python/ql/test/query-tests/Statements/general/UnnecessaryElseClause.expected b/python/ql/test/query-tests/Statements/general/UnnecessaryElseClause.expected index 6f333fc7bbf..830c2cd10ca 100644 --- a/python/ql/test/query-tests/Statements/general/UnnecessaryElseClause.expected +++ b/python/ql/test/query-tests/Statements/general/UnnecessaryElseClause.expected @@ -1,2 +1,2 @@ -| statements_test.py:63:1:63:19 | For | This 'for' statement has a redundant 'else' as no 'break' is present in the body. | -| statements_test.py:68:1:68:13 | While | This 'while' statement has a redundant 'else' as no 'break' is present in the body. | +| statements_test.py:65:1:65:19 | For | This 'for' statement has a redundant 'else' as no 'break' is present in the body. | +| statements_test.py:70:1:70:13 | While | This 'while' statement has a redundant 'else' as no 'break' is present in the body. | diff --git a/python/ql/test/query-tests/Statements/general/statements_test.py b/python/ql/test/query-tests/Statements/general/statements_test.py index 37ed94ec57e..0a74bb31c10 100644 --- a/python/ql/test/query-tests/Statements/general/statements_test.py +++ b/python/ql/test/query-tests/Statements/general/statements_test.py @@ -56,27 +56,29 @@ class Redundant(object): def __init__(self, args): args = args # violation -#Non redundant assignment -len = len +if sys.version_info < (3,): + bytes = str +else: + bytes = bytes # Should not be flagged #Pointless else clauses for x in range(10): func(x) else: do_something() - + while x < 10: func(x) else: do_something() - + #OK else clauses: for x in range(10): if func(x): break else: do_something() - + while x < 10: if func(x): break @@ -95,24 +97,24 @@ else: #Not a redundant assignment if a property. class WithProp(object): - + @property def x(self): return self._x - + @prop.setter def set_x(self, x): side_effect(x) self._x = x - + def meth(self): self.x = self.x - + def maybe_property(x): x.y = x.y - + class WithoutProp(object): - + def meth(self): self.x = self.x @@ -143,8 +145,12 @@ for e in EnumDerived: class SideEffectingAttr(object): + def __init__(self): + self.foo = 'foo' + def __setattr__(self, name, val): print("hello!") + super().__setattr__(name, val) s = SideEffectingAttr() s.foo = s.foo