diff --git a/powershell/ql/lib/semmle/code/powershell/ast/internal/Ast.qll b/powershell/ql/lib/semmle/code/powershell/ast/internal/Ast.qll index 78d1d97876c8..a8619cb06486 100644 --- a/powershell/ql/lib/semmle/code/powershell/ast/internal/Ast.qll +++ b/powershell/ql/lib/semmle/code/powershell/ast/internal/Ast.qll @@ -3,6 +3,7 @@ private import AstImport class Ast extends TAst { string toString() { none() } + pragma[nomagic] final Ast getParent() { result.getChild(_) = this } Location getLocation() { diff --git a/powershell/ql/lib/semmle/code/powershell/ast/internal/CallExpr.qll b/powershell/ql/lib/semmle/code/powershell/ast/internal/CallExpr.qll index d56741fdd6c6..b7e756a2a8a0 100644 --- a/powershell/ql/lib/semmle/code/powershell/ast/internal/CallExpr.qll +++ b/powershell/ql/lib/semmle/code/powershell/ast/internal/CallExpr.qll @@ -26,10 +26,16 @@ class CallExpr extends Expr, TCallExpr { */ Expr getCallee() { none() } - /** Holds if an argument with name `name` is provided to this call. */ + /** + * Holds if an argument with name `name` is provided to this call. + * Note: `name` is normalized to lower case. + */ final predicate hasNamedArgument(string name) { exists(this.getNamedArgument(name)) } - /** Gets the argument to this call with the name `name`. */ + /** + * Gets the named argument with the given name. + * Note: `name` is normalized to lower case. + */ Expr getNamedArgument(string name) { none() } /** Gets any argument to this call. */ diff --git a/powershell/ql/lib/semmle/code/powershell/ast/internal/Command.qll b/powershell/ql/lib/semmle/code/powershell/ast/internal/Command.qll index 8983c373d9bf..c1984d5286e6 100644 --- a/powershell/ql/lib/semmle/code/powershell/ast/internal/Command.qll +++ b/powershell/ql/lib/semmle/code/powershell/ast/internal/Command.qll @@ -1,7 +1,9 @@ private import AstImport class CmdCall extends CallExpr, TCmd { - final override string getLowerCaseName() { result = getRawAst(this).(Raw::Cmd).getLowerCaseName() } + final override string getLowerCaseName() { + result = getRawAst(this).(Raw::Cmd).getLowerCaseName() + } final override Expr getArgument(int i) { synthChild(getRawAst(this), cmdArgument(i), result) } diff --git a/powershell/ql/lib/semmle/code/powershell/ast/internal/Parameter.qll b/powershell/ql/lib/semmle/code/powershell/ast/internal/Parameter.qll index a6f45e335189..1b471bc5a0de 100644 --- a/powershell/ql/lib/semmle/code/powershell/ast/internal/Parameter.qll +++ b/powershell/ql/lib/semmle/code/powershell/ast/internal/Parameter.qll @@ -65,6 +65,8 @@ class PipelineByPropertyNameParameter extends Parameter instanceof PipelineByPro * Gets the iterator variable that is used to iterate over the elements in the pipeline. */ PipelineByPropertyNameIteratorVariable getIteratorVariable() { result.getParameter() = this } + + ProcessBlock getProcessBlock() { result = this.getIteratorVariable().getProcessBlock() } } /** diff --git a/powershell/ql/lib/semmle/code/powershell/ast/internal/Raw/Ast.qll b/powershell/ql/lib/semmle/code/powershell/ast/internal/Raw/Ast.qll index 5bfb2383a69b..8f5b86f1fd9f 100644 --- a/powershell/ql/lib/semmle/code/powershell/ast/internal/Raw/Ast.qll +++ b/powershell/ql/lib/semmle/code/powershell/ast/internal/Raw/Ast.qll @@ -5,10 +5,12 @@ private import Scope class Ast extends @ast { final string toString() { none() } + pragma[nomagic] final Ast getParent() { result.getAChild() = this } Ast getChild(ChildIndex i) { none() } + pragma[nomagic] final Ast getAChild() { result = this.getChild(_) } Location getLocation() { none() } diff --git a/powershell/ql/lib/semmle/code/powershell/ast/internal/Synthesis.qll b/powershell/ql/lib/semmle/code/powershell/ast/internal/Synthesis.qll index 68c2452ba61c..7afca387379e 100644 --- a/powershell/ql/lib/semmle/code/powershell/ast/internal/Synthesis.qll +++ b/powershell/ql/lib/semmle/code/powershell/ast/internal/Synthesis.qll @@ -26,7 +26,8 @@ newtype VarKind = PipelineIteratorKind() or PipelineByPropertyNameIteratorKind(string name) { exists(Raw::ProcessBlock pb | - name = pb.getScriptBlock().getParamBlock().getAPipelineByPropertyNameParameter().getLowerCaseName() + name = + pb.getScriptBlock().getParamBlock().getAPipelineByPropertyNameParameter().getLowerCaseName() ) } @@ -771,28 +772,35 @@ private module IteratorAccessSynth { // TODO: We could join on something other than the string if we wanted (i.e., the raw parameter). v.getPropertyName().toLowerCase() = result and result = - pb.getScriptBlock() - .getParamBlock() - .getAPipelineByPropertyNameParameter() - .getLowerCaseName() + pb.getScriptBlock().getParamBlock().getAPipelineByPropertyNameParameter().getLowerCaseName() } + private Raw::Ast getParent(Raw::Ast a) { a.getParent() = result } + + private predicate isVarAccess(Raw::VarAccess va) { any() } + + private predicate isProcessBlock(Raw::ProcessBlock pb) { any() } + + private Raw::ProcessBlock getProcessBlock(Raw::VarAccess va) = + doublyBoundedFastTC(getParent/1, isVarAccess/1, isProcessBlock/1)(va, result) + private class IteratorAccessSynth extends Synthesis { final override predicate isRelevant(Raw::Ast a) { - exists(Raw::ProcessBlock pb, Raw::VarAccess va | - va = a and - pb = va.getParent+() - | + exists(Raw::VarAccess va | va = a | va.getUserPath() = "_" or - va.getUserPath().toLowerCase() = - pb.getScriptBlock().getParamBlock().getPipelineParameter().getLowerCaseName() - or - va.getUserPath().toLowerCase() = - pb.getScriptBlock() - .getParamBlock() - .getAPipelineByPropertyNameParameter() - .getLowerCaseName() + exists(Raw::ProcessBlock pb | + pragma[only_bind_into](pb) = getProcessBlock(pragma[only_bind_into](va)) + | + va.getUserPath().toLowerCase() = + pb.getScriptBlock().getParamBlock().getPipelineParameter().getLowerCaseName() + or + va.getUserPath().toLowerCase() = + pb.getScriptBlock() + .getParamBlock() + .getAPipelineByPropertyNameParameter() + .getLowerCaseName() + ) ) } @@ -810,7 +818,7 @@ private module IteratorAccessSynth { private PipelineOrPipelineByPropertyNameIteratorVariable varAccess(Raw::VarAccess va) { exists(Raw::ProcessBlock pb | - pb = va.getParent+() and + pb = getProcessBlock(va) and result = TVariableSynth(pb, _) and va.getUserPath().toLowerCase() = getAPipelineIteratorName(pb, result) ) @@ -896,6 +904,7 @@ private module PipelineAccess { exists(PipelineByPropertyNameParameter pipelineVar, Raw::PipelineByPropertyNameParameter p | i = processBlockPipelineByPropertyNameVarReadAccess(p.getLowerCaseName()) and getResultAst(p) = pipelineVar and + pipelineVar = TVariableSynth(pb.getScriptBlock(), _) and child = SynthChild(VarAccessSynthKind(pipelineVar)) ) ) diff --git a/powershell/ql/lib/semmle/code/powershell/ast/internal/Variable.qll b/powershell/ql/lib/semmle/code/powershell/ast/internal/Variable.qll index 89fc6d2bba1c..b9791ab06bd0 100644 --- a/powershell/ql/lib/semmle/code/powershell/ast/internal/Variable.qll +++ b/powershell/ql/lib/semmle/code/powershell/ast/internal/Variable.qll @@ -59,8 +59,9 @@ module Private { PipelineParameterImpl() { exists(int index | - i = FunParam(index) and - any(Synthesis s).pipelineParameterHasIndex(super.getDeclaringScopeImpl(), index) + i = FunParam(pragma[only_bind_into](index)) and + any(Synthesis s) + .pipelineParameterHasIndex(super.getDeclaringScopeImpl(), pragma[only_bind_into](index)) ) } diff --git a/powershell/ql/lib/semmle/code/powershell/controlflow/internal/ControlFlowGraphImpl.qll b/powershell/ql/lib/semmle/code/powershell/controlflow/internal/ControlFlowGraphImpl.qll index e9c5c4862e4d..a1f88274a98c 100644 --- a/powershell/ql/lib/semmle/code/powershell/controlflow/internal/ControlFlowGraphImpl.qll +++ b/powershell/ql/lib/semmle/code/powershell/controlflow/internal/ControlFlowGraphImpl.qll @@ -427,7 +427,9 @@ module Trees { override predicate last(AstNode last, Completion c) { // Exit the loop body when the condition is false last(this.getCondition(), last, c) and - this.entersLoopWhenConditionIs(c.(BooleanCompletion).getValue().booleanNot()) + this.entersLoopWhenConditionIs(pragma[only_bind_into](c.(BooleanCompletion) + .getValue() + .booleanNot())) or super.last(last, c) } @@ -435,7 +437,7 @@ module Trees { override predicate succ(AstNode pred, AstNode succ, Completion c) { // Condition -> body last(this.getCondition(), pred, c) and - this.entersLoopWhenConditionIs(c.(BooleanCompletion).getValue()) and + this.entersLoopWhenConditionIs(pragma[only_bind_into](c.(BooleanCompletion).getValue())) and first(this.getBody(), succ) or // Body -> condition diff --git a/powershell/ql/lib/semmle/code/powershell/dataflow/internal/DataFlowPrivate.qll b/powershell/ql/lib/semmle/code/powershell/dataflow/internal/DataFlowPrivate.qll index 72ad1786ca30..004e4ba4e7df 100644 --- a/powershell/ql/lib/semmle/code/powershell/dataflow/internal/DataFlowPrivate.qll +++ b/powershell/ql/lib/semmle/code/powershell/dataflow/internal/DataFlowPrivate.qll @@ -465,27 +465,47 @@ class NamedSet extends NamedSet0 { /** Gets the non-empty set of names, if any. */ NamedSetModule::Set asNonEmpty() { this = TNonEmptyNamedSet(result) } + /** Gets the `i`'th name in this set according to some ordering. */ + private string getRankedName(int i) { + result = rank[i + 1](string s | s = this.getALowerCaseName() | s) + } + /** Holds if this is the empty set. */ predicate isEmpty() { this = TEmptyNamedSet() } - /** Gets a name in this set. */ - string getAName() { this.asNonEmpty().contains(result) } + int getSize() { + result = strictcount(this.getALowerCaseName()) + or + this.isEmpty() and + result = 0 + } + + /** Gets a lower-case name in this set. */ + string getALowerCaseName() { this.asNonEmpty().contains(result) } /** Gets the textual representation of this set. */ string toString() { - result = "{" + strictconcat(this.getAName(), ", ") + "}" + result = "{" + strictconcat(this.getALowerCaseName(), ", ") + "}" or this.isEmpty() and result = "{}" } + private CfgNodes::ExprNodes::CallExprCfgNode getABindingCallRec(int i) { + exists(string name | name = this.getRankedName(i) and exists(result.getNamedArgument(name)) | + i = 0 + or + result = this.getABindingCallRec(i - 1) + ) + } + /** * Gets a `CfgNodes::CallCfgNode` that provides a named parameter for every name in `this`. * * NOTE: The `CfgNodes::CallCfgNode` may also provide more names. */ CfgNodes::ExprNodes::CallExprCfgNode getABindingCall() { - forex(string name | name = this.getAName() | exists(result.getNamedArgument(name))) + result = this.getABindingCallRec(this.getSize() - 1) or this.isEmpty() and exists(result) @@ -496,16 +516,28 @@ class NamedSet extends NamedSet0 { * this set. */ CfgNodes::ExprNodes::CallExprCfgNode getAnExactBindingCall() { - forex(string name | name = this.getAName() | exists(result.getNamedArgument(name))) and - forex(string name | exists(result.getNamedArgument(name)) | name = this.getAName()) + result = this.getABindingCallRec(this.getSize() - 1) and + strictcount(string name | result.hasNamedArgument(name)) = this.getSize() or this.isEmpty() and not exists(result.getNamedArgument(_)) } + pragma[nomagic] + private Function getAFunctionRec(int i) { + i = 0 and + result.getAParameter().getLowerCaseName() = this.getRankedName(0) + or + exists(string name | + pragma[only_bind_into](name) = this.getRankedName(i) and + result.getAParameter().getLowerCaseName() = pragma[only_bind_into](name) and + result = this.getAFunctionRec(i - 1) + ) + } + /** Gets a function that has a parameter for each name in this set. */ Function getAFunction() { - forex(string name | name = this.getAName() | result.getAParameter().matchesName(name)) + result = this.getAFunctionRec(this.getSize() - 1) or this.isEmpty() and exists(result) @@ -533,6 +565,12 @@ private module ParameterNodes { } } + bindingset[p] + pragma[inline_late] + private predicate namedSetHasParameter(NamedSet ns, Parameter p) { + ns.getALowerCaseName() = p.getLowerCaseName() + } + /** * The value of a normal parameter at function entry, viewed as a node in a data * flow graph. @@ -566,13 +604,13 @@ private module ParameterNodes { f = parameter.getFunction() and f = ns.getAFunction() and name = parameter.getLowerCaseName() and - not name = ns.getAName() and + not name = ns.getALowerCaseName() and j = i - count(int k, Parameter p | k < i and p = getNormalParameter(f, k) and - p.getLowerCaseName() = ns.getAName() + namedSetHasParameter(ns, p) ) ) ) diff --git a/powershell/ql/lib/semmle/code/powershell/dataflow/internal/SsaImpl.qll b/powershell/ql/lib/semmle/code/powershell/dataflow/internal/SsaImpl.qll index 7d2b7a7ec398..23ae5e5397e0 100644 --- a/powershell/ql/lib/semmle/code/powershell/dataflow/internal/SsaImpl.qll +++ b/powershell/ql/lib/semmle/code/powershell/dataflow/internal/SsaImpl.qll @@ -278,7 +278,7 @@ private module DataFlowIntegrationInput implements Impl::DataFlowIntegrationInpu * guard to `branch`. */ predicate controlsBranchEdge(SsaInput::BasicBlock bb1, SsaInput::BasicBlock bb2, boolean branch) { - hasBranchEdge(bb1, bb2, branch) + this.hasBranchEdge(bb1, bb2, branch) } /** * Holds if the evaluation of this guard to `branch` corresponds to the edge