Skip to content

Commit 4cd37d6

Browse files
authored
Merge pull request #124 from microsoft/powershell-more-correct-value-from-property-name
PS: Allow for `ValueFromPipelineByPropertyName` to also read off an `ElementContent`
2 parents bfa9210 + ef75ffe commit 4cd37d6

File tree

8 files changed

+234
-72
lines changed

8 files changed

+234
-72
lines changed

powershell/ql/lib/semmle/code/powershell/NamedAttributeArgument.qll

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ class NamedAttributeArgument extends @named_attribute_argument, Ast {
77

88
string getName() { named_attribute_argument(this, result, _) }
99

10+
predicate hasName(string s) { this.getName() = s }
11+
1012
Expr getValue() { named_attribute_argument(this, _, result) }
1113
}
1214

powershell/ql/lib/semmle/code/powershell/ScriptBlock.qll

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,4 +60,12 @@ class ProcessBlock extends NamedBlock {
6060
ProcessBlock() { scriptBlock.getProcessBlock() = this }
6161

6262
ScriptBlock getScriptBlock() { result = scriptBlock }
63+
64+
PipelineParameter getPipelineParameter() {
65+
result = scriptBlock.getEnclosingFunction().getAParameter()
66+
}
67+
68+
PipelineByPropertyNameParameter getAPipelineByPropertyNameParameter() {
69+
result = scriptBlock.getEnclosingFunction().getAParameter()
70+
}
6371
}

powershell/ql/lib/semmle/code/powershell/Variable.qll

Lines changed: 50 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -178,14 +178,27 @@ private class ThisParameter extends ParameterImpl, TThisParameter {
178178
final override predicate isPipelineByPropertyName() { none() }
179179
}
180180

181+
private predicate isPipelineIteratorVariable(ParameterImpl p, ProcessBlock pb) {
182+
p.isPipeline() and
183+
pb.getEnclosingScope() = p.getEnclosingScope()
184+
}
185+
186+
private predicate isPipelineByPropertyNameIteratorVariable(ParameterImpl p, ProcessBlock pb) {
187+
p.isPipelineByPropertyName() and
188+
pb.getEnclosingScope() = p.getEnclosingScope()
189+
}
190+
181191
private newtype TVariable =
182192
TLocalVariable(string name, Scope scope) {
183193
not isParameterImpl(name, scope) and
184194
not name = "this" and // This is modeled as a parameter
185195
exists(VarAccess va | va.getUserPath() = name and scope = va.getEnclosingScope())
186196
} or
187197
TParameter(ParameterImpl p) or
188-
TPipelineIteratorVariable(ProcessBlock pb)
198+
TPipelineIteratorVariable(ProcessBlock pb) { isPipelineIteratorVariable(_, pb) } or
199+
TPipelineByPropertyNameIteratorVariable(ParameterImpl p) {
200+
isPipelineByPropertyNameIteratorVariable(p, _)
201+
}
189202

190203
private class AbstractVariable extends TVariable {
191204
abstract Location getLocation();
@@ -296,12 +309,22 @@ class Parameter extends AbstractLocalScopeVariable, TParameter {
296309

297310
class PipelineParameter extends Parameter {
298311
PipelineParameter() { this.isPipeline() }
312+
313+
PipelineIteratorVariable getIteratorVariable() {
314+
result.getProcessBlock().getEnclosingScope() = p.getEnclosingScope()
315+
}
316+
}
317+
318+
class PipelineByPropertyNameParameter extends Parameter {
319+
PipelineByPropertyNameParameter() { this.isPipelineByPropertyName() }
320+
321+
PipelineByPropertyNameIteratorVariable getIteratorVariable() { result.getParameter() = this }
299322
}
300323

301324
/**
302325
* The variable that represents the value of a pipeline during a process block.
303326
*
304-
* That is, it is _not_ the pipeline variable, but the value that is obtained by reading
327+
* That is, it is _not_ the `ValueFromPipeline` variable, but the value that is obtained by reading
305328
* from the pipeline.
306329
*/
307330
class PipelineIteratorVariable extends AbstractLocalScopeVariable, TPipelineIteratorVariable {
@@ -317,3 +340,28 @@ class PipelineIteratorVariable extends AbstractLocalScopeVariable, TPipelineIter
317340

318341
ProcessBlock getProcessBlock() { result = pb }
319342
}
343+
344+
/**
345+
* The variable that represents the value of a pipeline that picks out a
346+
* property specific property during a process block.
347+
*
348+
* That is, it is _not_ the `PipelineByPropertyName` variable, but the value that is obtained by reading
349+
* from the pipeline.
350+
*/
351+
class PipelineByPropertyNameIteratorVariable extends AbstractLocalScopeVariable,
352+
TPipelineByPropertyNameIteratorVariable
353+
{
354+
private ParameterImpl p;
355+
356+
PipelineByPropertyNameIteratorVariable() { this = TPipelineByPropertyNameIteratorVariable(p) }
357+
358+
override Location getLocation() { result = p.getLocation() }
359+
360+
override string getName() { result = "pipeline iterator for " + p.toString() }
361+
362+
final override Scope getDeclaringScope() { result = p.getEnclosingScope() }
363+
364+
Parameter getParameter() { result = TParameter(p) }
365+
366+
ProcessBlock getProcessBlock() { isPipelineByPropertyNameIteratorVariable(p, result) }
367+
}

powershell/ql/lib/semmle/code/powershell/controlflow/CfgNodes.qll

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,11 @@ class ProcessBlockCfgNode extends NamedBlockCfgNode {
198198

199199
override ProcessBlock getBlock() { result = block }
200200

201-
PipelineParameter getPipelineParameter() { result = block.getEnclosingFunction().getAParameter() }
201+
PipelineParameter getPipelineParameter() { result = block.getPipelineParameter() }
202+
203+
PipelineByPropertyNameParameter getAPipelineByPropertyNameParameter() {
204+
result = block.getAPipelineByPropertyNameParameter()
205+
}
202206
}
203207

204208
private class StmtBlockChildMapping extends NonExprChildMapping, StmtBlock {

powershell/ql/lib/semmle/code/powershell/dataflow/internal/DataFlowPrivate.qll

Lines changed: 59 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -76,17 +76,16 @@ module SsaFlow {
7676
or
7777
result.(Impl::ExprNode).getExpr() = n.asStmt()
7878
or
79-
result.(Impl::ExprNode).getExpr() = n.(ProcessNode).getProcessBlock()
79+
result.(Impl::ExprNode).getExpr() =
80+
[n.(ProcessNode).getProcessBlock(), n.(ProcessPropertyByNameNode).getProcessBlock()]
8081
or
8182
result.(Impl::ExprPostUpdateNode).getExpr() = n.(PostUpdateNode).getPreUpdateNode().asExpr()
8283
or
8384
n = toParameterNode(result.(Impl::ParameterNode).getParameter())
8485
}
8586

8687
predicate localFlowStep(SsaImpl::DefinitionExt def, Node nodeFrom, Node nodeTo, boolean isUseStep) {
87-
Impl::localFlowStep(def, asNode(nodeFrom), asNode(nodeTo), isUseStep) and
88-
// Flow out of property name parameter nodes are covered by `readStep`.
89-
not nodeFrom instanceof PipelineByPropertyNameParameter
88+
Impl::localFlowStep(def, asNode(nodeFrom), asNode(nodeTo), isUseStep)
9089
}
9190

9291
predicate localMustFlowStep(SsaImpl::DefinitionExt def, Node nodeFrom, Node nodeTo) {
@@ -146,6 +145,12 @@ module VariableCapture {
146145
// TODO
147146
}
148147

148+
private predicate isProcessPropertyByNameNode(
149+
PipelineByPropertyNameIteratorVariable iter, ProcessBlock pb
150+
) {
151+
pb.getEnclosingScope() = iter.getDeclaringScope()
152+
}
153+
149154
/** A collection of cached types and predicates to be evaluated in the same stage. */
150155
cached
151156
private module Cached {
@@ -172,7 +177,10 @@ private module Cached {
172177
TPreReturnNodeImpl(CfgNodes::AstCfgNode n, Boolean isArray) { isMultiReturned(n) } or
173178
TImplicitWrapNode(CfgNodes::AstCfgNode n, Boolean shouldWrap) { isMultiReturned(n) } or
174179
TReturnNodeImpl(CfgScope scope) or
175-
TProcessNode(ProcessBlock process)
180+
TProcessNode(ProcessBlock process) or
181+
TProcessPropertyByNameNode(PipelineByPropertyNameIteratorVariable iter) {
182+
isProcessPropertyByNameNode(iter, _)
183+
}
176184

177185
cached
178186
Location getLocation(NodeImpl n) { result = n.getLocationImpl() }
@@ -501,8 +509,8 @@ private module ParameterNodes {
501509
override string toStringImpl() { result = parameter.toString() }
502510
}
503511

504-
class PipelineByPropertyNameParameter extends NormalParameterNode {
505-
PipelineByPropertyNameParameter() { this.getParameter().isPipelineByPropertyName() }
512+
class PipelineByPropertyNameParameterNode extends NormalParameterNode {
513+
PipelineByPropertyNameParameterNode() { this.getParameter().isPipelineByPropertyName() }
506514

507515
string getPropretyName() { result = this.getParameter().getName() }
508516
}
@@ -753,15 +761,30 @@ predicate readStep(Node node1, ContentSet c, Node node2) {
753761
)
754762
or
755763
c.isAnyElement() and
756-
exists(SsaImpl::DefinitionExt def |
757-
node1.(ProcessNode).getIteratorVariable() = def.getSourceVariable() and
764+
exists(SsaImpl::DefinitionExt def, ProcessNode processNode, LocalScopeVariable iterator |
765+
processNode = node1 and iterator = def.getSourceVariable()
766+
|
767+
processNode.getIteratorVariable() = iterator and
768+
SsaImpl::firstRead(def, node2.asExpr())
769+
or
770+
processNode.getPropertyNameIteratorVariable() = iterator and
771+
node2 = TProcessPropertyByNameNode(def.getSourceVariable())
772+
)
773+
or
774+
exists(
775+
Content::KnownElementContent ec, PipelineByPropertyNameParameter p, SsaImpl::DefinitionExt def
776+
|
777+
c.isSingleton(ec) and
778+
p.getIteratorVariable() = node1.(ProcessPropertyByNameNode).getVariable() and
779+
p.getName() = ec.getIndex().asString() and
780+
def.getSourceVariable() = p.getIteratorVariable() and
758781
SsaImpl::firstRead(def, node2.asExpr())
759782
)
760783
or
761784
exists(Content::KnownElementContent ec, SsaImpl::DefinitionExt def |
762785
c.isSingleton(ec) and
763-
node1.(PipelineByPropertyNameParameter).getPropretyName() = ec.getIndex().asString() and
764-
def.getSourceVariable() = node1.(PipelineByPropertyNameParameter).getParameter() and
786+
node1.(PipelineByPropertyNameParameterNode).getPropretyName() = ec.getIndex().asString() and
787+
def.getSourceVariable() = node1.(PipelineByPropertyNameParameterNode).getParameter() and
765788
SsaImpl::firstRead(def, node2.asExpr())
766789
)
767790
}
@@ -792,11 +815,6 @@ predicate expectsContent(Node n, ContentSet c) {
792815
or
793816
n instanceof ProcessNode and
794817
c.isAnyElement()
795-
or
796-
exists(Content::KnownElementContent ec |
797-
ec.getIndex().asString() = n.(PipelineByPropertyNameParameter).getPropretyName() and
798-
c.isSingleton(ec)
799-
)
800818
}
801819

802820
class DataFlowType extends TDataFlowType {
@@ -925,15 +943,39 @@ private class ProcessNode extends TProcessNode, NodeImpl {
925943

926944
override Location getLocationImpl() { result = process.getLocation() }
927945

928-
override string toStringImpl() { result = process.toString() }
946+
override string toStringImpl() { result = "process node for " + process.toString() }
929947

930948
override predicate nodeIsHidden() { any() }
931949

932950
PipelineIteratorVariable getIteratorVariable() { result.getProcessBlock() = process }
933951

952+
PipelineByPropertyNameIteratorVariable getPropertyNameIteratorVariable() {
953+
result.getProcessBlock() = process
954+
}
955+
934956
CfgNodes::ProcessBlockCfgNode getProcessBlock() { result.getAstNode() = process }
935957
}
936958

959+
private class ProcessPropertyByNameNode extends TProcessPropertyByNameNode, NodeImpl {
960+
private PipelineByPropertyNameIteratorVariable iter;
961+
962+
ProcessPropertyByNameNode() { this = TProcessPropertyByNameNode(iter) }
963+
964+
PipelineByPropertyNameIteratorVariable getVariable() { result = iter }
965+
966+
override CfgScope getCfgScope() { result = iter.getDeclaringScope() }
967+
968+
override Location getLocationImpl() { result = iter.getLocation() }
969+
970+
override string toStringImpl() { result = "process node for " + iter.toString() }
971+
972+
override predicate nodeIsHidden() { any() }
973+
974+
CfgNodes::ProcessBlockCfgNode getProcessBlock() {
975+
isProcessPropertyByNameNode(iter, result.getAstNode())
976+
}
977+
}
978+
937979
/** A node that performs a type cast. */
938980
class CastNode extends Node {
939981
CastNode() { none() }

powershell/ql/lib/semmle/code/powershell/dataflow/internal/SsaImpl.qll

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -68,10 +68,11 @@ predicate uninitializedWrite(Cfg::EntryBasicBlock bb, int i, LocalVariable v) {
6868
i = -1
6969
}
7070

71-
predicate pipelineIteratorWrite(Cfg::BasicBlock bb, int i, PipelineIteratorVariable v) {
72-
exists(ProcessBlockCfgNode process |
73-
process = bb.getNode(i) and
74-
v.getProcessBlock() = process.getAstNode()
71+
predicate pipelineIteratorWrite(Cfg::BasicBlock bb, int i, LocalScopeVariable v) {
72+
exists(ProcessBlockCfgNode process | process = bb.getNode(i) |
73+
v.(PipelineIteratorVariable).getProcessBlock() = process.getAstNode()
74+
or
75+
v.(PipelineByPropertyNameIteratorVariable).getProcessBlock() = process.getAstNode()
7576
)
7677
}
7778

@@ -114,12 +115,19 @@ predicate parameterWrite(Cfg::EntryBasicBlock bb, int i, Parameter p) {
114115
)
115116
}
116117

117-
private PipelineIteratorVariable getPipelineIterator(PipelineParameter pipelineParam) {
118-
result.getProcessBlock().getScriptBlock() = pipelineParam.getDeclaringScope()
118+
private LocalScopeVariable getPipelineIterator(LocalScopeVariable pipelineParam) {
119+
result.(PipelineIteratorVariable).getProcessBlock().getScriptBlock() =
120+
pipelineParam.(PipelineParameter).getDeclaringScope()
121+
or
122+
result.(PipelineByPropertyNameIteratorVariable).getParameter() =
123+
pipelineParam.(PipelineByPropertyNameParameter)
119124
}
120125

121126
private predicate isPipelineIteratorVarAccess(VarAccessCfgNode va) {
122-
va.getVariable() instanceof PipelineParameter and
127+
(
128+
va.getVariable() instanceof PipelineParameter or
129+
va.getVariable() instanceof PipelineByPropertyNameParameter
130+
) and
123131
va.getAstNode().getParent*() instanceof ProcessBlock
124132
}
125133

@@ -133,9 +141,9 @@ private predicate variableReadActual(Cfg::BasicBlock bb, int i, SsaInput::Source
133141
}
134142

135143
private predicate pipelineRead(Cfg::BasicBlock bb, int i, SsaInput::SourceVariable v) {
136-
exists(ProcessBlockCfgNode process |
137-
process = bb.getNode(i) and
138-
v = process.getPipelineParameter()
144+
exists(ProcessBlockCfgNode process | process = bb.getNode(i) |
145+
v = process.getPipelineParameter() or
146+
v = process.getAPipelineByPropertyNameParameter()
139147
)
140148
}
141149

0 commit comments

Comments
 (0)