Skip to content

Commit 60cda95

Browse files
authored
Merge pull request #105 from microsoft/powershell-argument-parameter-matching
PS: Implement argument/parameter matching in dataflow
2 parents 5803e06 + b601965 commit 60cda95

File tree

7 files changed

+256
-30
lines changed

7 files changed

+256
-30
lines changed

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

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,14 @@ class Cmd extends @command, CmdBase {
1919

2020
StringConstExpr getCmdName() { result = this.getElement(0) }
2121

22-
Expr getAnArgument() { result = this.getArgument(_) or result = this.getNamedArgument(_) }
23-
22+
/** Gets any argument to this command. */
23+
Expr getAnArgument() { result = this.getArgument(_) }
24+
25+
/**
26+
* Gets the `i`th argument to this command.
27+
*
28+
* The argument may be named or positional.
29+
*/
2430
Expr getArgument(int i) {
2531
result =
2632
rank[i + 1](CmdElement e, int j |
@@ -32,6 +38,18 @@ class Cmd extends @command, CmdBase {
3238
)
3339
}
3440

41+
/** Gets the `i`th positional argument to this command. */
42+
Expr getPositionalArgument(int i) {
43+
result =
44+
rank[i + 1](Argument e, int j |
45+
e = this.getArgument(j) and
46+
e instanceof PositionalArgument
47+
|
48+
e order by j
49+
)
50+
}
51+
52+
/** Gets the named argument with the given name. */
3553
Expr getNamedArgument(string name) {
3654
exists(int i, CmdParameter p |
3755
this.getElement(i) = p and
@@ -53,11 +71,21 @@ class Cmd extends @command, CmdBase {
5371
class Argument extends Expr {
5472
Cmd cmd;
5573

56-
Argument() { cmd.getArgument(_) = this or cmd.getNamedArgument(_) = this }
74+
Argument() { cmd.getAnArgument() = this }
5775

5876
Cmd getCmd() { result = cmd }
5977

60-
int getIndex() { cmd.getArgument(result) = this }
78+
int getPosition() { cmd.getPositionalArgument(result) = this }
6179

6280
string getName() { cmd.getNamedArgument(result) = this }
6381
}
82+
83+
/** A positional argument to a command. */
84+
class PositionalArgument extends Argument {
85+
PositionalArgument() { not this instanceof NamedArgument }
86+
}
87+
88+
/** A named argument to a command. */
89+
class NamedArgument extends Argument {
90+
NamedArgument() { this = cmd.getNamedArgument(_) }
91+
}

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

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,10 @@ private predicate hasParameterBlockImpl(Internal::Parameter p, ParamBlock block,
1414

1515
/**
1616
* Gets the enclosing scope of `p`.
17-
*
17+
*
1818
* For a function parameter, this is the function body. For a parameter from a
1919
* parameter block, this is the enclosing scope of the parameter block.
20-
*
20+
*
2121
* In both of the above cases, the enclosing scope is the function body.
2222
*/
2323
private Scope getEnclosingScopeImpl(Internal::Parameter p) {
@@ -176,6 +176,8 @@ class Parameter extends AbstractLocalScopeVariable, TParameter {
176176

177177
override string getName() { result = p.getName() }
178178

179+
final predicate hasName(string name) { name = p.getName() }
180+
179181
final override Scope getDeclaringScope() { result = p.getEnclosingScope() }
180182

181183
predicate hasParameterBlock(ParamBlock block, int i) { p.hasParameterBlock(block, i) }
@@ -186,7 +188,18 @@ class Parameter extends AbstractLocalScopeVariable, TParameter {
186188

187189
predicate hasDefaultValue() { exists(this.getDefaultValue()) }
188190

189-
int getIndex() { this.isFunctionParameter(_, result) }
191+
/**
192+
* Gets the index of this parameter.
193+
*
194+
* The parameter may be in a parameter block or a function parameter.
195+
*/
196+
int getIndex() { result = this.getFunctionIndex() or result = this.getBlockIndex() }
197+
198+
/** Gets the index of this parameter in the parameter block, if any. */
199+
int getBlockIndex() { this.hasParameterBlock(_, result) }
200+
201+
/** Gets the index of this parameter in the function, if any. */
202+
int getFunctionIndex() { this.isFunctionParameter(_, result) }
190203

191204
Function getFunction() { result.getBody() = this.getDeclaringScope() }
192205
}

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

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,14 @@ module ExprNodes {
174174
override Argument e;
175175

176176
final override Argument getExpr() { result = super.getExpr() }
177+
178+
/** Gets the position of this argument, if any. */
179+
int getPosition() { result = e.getPosition() }
180+
181+
/** Gets the name of this argument, if any. */
182+
string getName() { result = e.getName() }
183+
184+
StmtNodes::CmdCfgNode getCmd() { result.getAnArgument() = this }
177185
}
178186

179187
private class InvokeMemberChildMapping extends ExprChildMapping, InvokeMemberExpr {
@@ -253,7 +261,7 @@ module ExprNodes {
253261

254262
module StmtNodes {
255263
private class CmdChildMapping extends NonExprChildMapping, Cmd {
256-
override predicate relevantChild(Ast n) { n = this.getElement(_) }
264+
override predicate relevantChild(Ast n) { n = this.getAnArgument() or n = this.getCommand() }
257265
}
258266

259267
/** A control-flow node that wraps a `Cmd` AST expression. */
@@ -263,6 +271,20 @@ module StmtNodes {
263271
override CmdChildMapping s;
264272

265273
override Cmd getStmt() { result = super.getStmt() }
274+
275+
ExprCfgNode getArgument(int i) { s.hasCfgChild(s.getArgument(i), this, result) }
276+
277+
ExprCfgNode getPositionalArgument(int i) {
278+
s.hasCfgChild(s.getPositionalArgument(i), this, result)
279+
}
280+
281+
ExprCfgNode getNamedArgument(string name) {
282+
s.hasCfgChild(s.getNamedArgument(name), this, result)
283+
}
284+
285+
ExprCfgNode getAnArgument() { s.hasCfgChild(s.getAnArgument(), this, result) }
286+
287+
ExprCfgNode getCommand() { s.hasCfgChild(s.getCommand(), this, result) }
266288
}
267289

268290
private class AssignStmtChildMapping extends NonExprChildMapping, AssignStmt {

powershell/ql/lib/semmle/code/powershell/controlflow/internal/ControlFlowGraphImpl.qll

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,7 @@ module Trees {
176176
exists(Parameter p |
177177
p =
178178
rank[i + 1](Parameter cand, int j |
179-
cand.hasDefaultValue() and j = cand.getIndex()
179+
cand.hasDefaultValue() and j = cand.getFunctionIndex()
180180
|
181181
cand order by j
182182
) and

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

Lines changed: 49 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -170,34 +170,75 @@ private module Cached {
170170

171171
cached
172172
newtype TArgumentPosition =
173-
TPositionalArgumentPosition(int pos) { exists(Cmd c | exists(c.getArgument(pos))) }
173+
TKeywordArgumentPosition(string name) { name = any(CmdParameter p).getName() } or
174+
TPositionalArgumentPosition(int pos, NamedSet ns) {
175+
exists(Cmd cmd |
176+
cmd = ns.getABindingCall() and
177+
exists(cmd.getArgument(pos))
178+
)
179+
}
174180

175181
cached
176-
newtype TParameterPosition = TPositionalParameterPosition(int pos) { none() /* TODO */ }
182+
newtype TParameterPosition =
183+
TKeywordParameter(string name) { name = any(CmdParameter p).getName() } or
184+
TPositionalParameter(int pos, NamedSet ns) {
185+
exists(Cmd cmd |
186+
cmd = ns.getABindingCall() and
187+
exists(cmd.getArgument(pos))
188+
)
189+
}
177190
}
178191

179192
import Cached
180193

181194
/** A parameter position. */
182195
class ParameterPosition extends TParameterPosition {
183-
/** Holds if this position represents a positional parameter at position `pos`. */
184-
predicate isPositional(int pos) { this = TPositionalParameterPosition(pos) }
196+
/**
197+
* Holds if this position represents a positional parameter at position `pos`
198+
* with function is called with exactly the named parameters from the set `ns`
199+
*/
200+
predicate isPositional(int pos, NamedSet ns) { this = TPositionalParameter(pos, ns) }
201+
202+
/** Holds if this parameter is a keyword parameter with `name`. */
203+
predicate isKeyword(string name) { this = TKeywordParameter(name) }
185204

186205
/** Gets a textual representation of this position. */
187-
string toString() { exists(int pos | this.isPositional(pos) and result = "position " + pos) }
206+
string toString() {
207+
exists(int pos, NamedSet ns |
208+
this.isPositional(pos, ns) and result = "pos(" + pos + ", " + ns.toString() + ")"
209+
)
210+
or
211+
exists(string name | this.isKeyword(name) and result = "kw(" + name + ")")
212+
}
188213
}
189214

190215
/** An argument position. */
191216
class ArgumentPosition extends TArgumentPosition {
192217
/** Holds if this position represents a positional argument at position `pos`. */
193-
predicate isPositional(int pos) { this = TPositionalArgumentPosition(pos) }
218+
predicate isPositional(int pos, NamedSet ns) { this = TPositionalArgumentPosition(pos, ns) }
219+
220+
predicate isKeyword(string name) { this = TKeywordArgumentPosition(name) }
194221

195222
/** Gets a textual representation of this position. */
196-
string toString() { exists(int pos | this.isPositional(pos) and result = "position " + pos) }
223+
string toString() {
224+
exists(int pos, NamedSet ns |
225+
this.isPositional(pos, ns) and result = "pos(" + pos + ", " + ns.toString() + ")"
226+
)
227+
or
228+
exists(string name | this.isKeyword(name) and result = "kw(" + name + ")")
229+
}
197230
}
198231

199232
/** Holds if arguments at position `apos` match parameters at position `ppos`. */
200233
pragma[nomagic]
201234
predicate parameterMatch(ParameterPosition ppos, ArgumentPosition apos) {
202-
exists(int pos | ppos.isPositional(pos) and apos.isPositional(pos))
235+
exists(string name |
236+
ppos.isKeyword(name) and
237+
apos.isKeyword(name)
238+
)
239+
or
240+
exists(int pos, NamedSet ns |
241+
ppos.isPositional(pos, ns) and
242+
apos.isPositional(pos, ns)
243+
)
203244
}

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

Lines changed: 101 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -229,18 +229,71 @@ class SsaInputNode extends SsaNode {
229229
override CfgScope getCfgScope() { result = node.getDefinitionExt().getBasicBlock().getScope() }
230230
}
231231

232+
private string getANamedArgument(Cmd c) { exists(c.getNamedArgument(result)) }
233+
234+
private module NamedSetModule = QlBuiltins::InternSets<Cmd, string, getANamedArgument/1>;
235+
236+
private newtype NamedSet0 =
237+
TEmptyNamedSet() or
238+
TNonEmptyNamedSet(NamedSetModule::Set ns)
239+
240+
/** A (possiby empty) set of argument names. */
241+
class NamedSet extends NamedSet0 {
242+
/** Gets the non-empty set of names, if any. */
243+
NamedSetModule::Set asNonEmpty() { this = TNonEmptyNamedSet(result) }
244+
245+
/** Holds if this is the empty set. */
246+
predicate isEmpty() { this = TEmptyNamedSet() }
247+
248+
/** Gets a name in this set. */
249+
string getAName() { this.asNonEmpty().contains(result) }
250+
251+
/** Gets the textual representation of this set. */
252+
string toString() {
253+
result = "{" + strictconcat(this.getAName(), ", ") + "}"
254+
or
255+
this.isEmpty() and
256+
result = "{}"
257+
}
258+
259+
/**
260+
* Gets a `Cmd` that provides a named parameter for every name in `this`.
261+
*
262+
* NOTE: The `Cmd` may also provide more names.
263+
*/
264+
Cmd getABindingCall() {
265+
forex(string name | name = this.getAName() | exists(result.getNamedArgument(name)))
266+
or
267+
this.isEmpty() and
268+
exists(result)
269+
}
270+
271+
/**
272+
* Gets a `Cmd` that provides exactly the named parameters represented by
273+
* this set.
274+
*/
275+
Cmd getAnExactBindingCall() {
276+
forex(string name | name = this.getAName() | exists(result.getNamedArgument(name))) and
277+
forex(string name | exists(result.getNamedArgument(name)) | name = this.getAName())
278+
or
279+
this.isEmpty() and
280+
not exists(result.getNamedArgument(_))
281+
}
282+
283+
/** Gets a function that has a parameter for each name in this set. */
284+
Function getAFunction() {
285+
forex(string name | name = this.getAName() | result.getAParameter().hasName(name))
286+
or
287+
this.isEmpty() and
288+
exists(result)
289+
}
290+
}
291+
232292
private module ParameterNodes {
233293
abstract class ParameterNodeImpl extends NodeImpl {
234294
abstract Parameter getParameter();
235295

236296
abstract predicate isParameterOf(DataFlowCallable c, ParameterPosition pos);
237-
238-
final predicate isSourceParameterOf(CfgScope c, ParameterPosition pos) {
239-
exists(DataFlowCallable callable |
240-
this.isParameterOf(callable, pos) and
241-
c = callable.asCfgScope()
242-
)
243-
}
244297
}
245298

246299
/**
@@ -255,8 +308,29 @@ private module ParameterNodes {
255308
override Parameter getParameter() { result = parameter }
256309

257310
override predicate isParameterOf(DataFlowCallable c, ParameterPosition pos) {
258-
exists(CfgScope callable, int i |
259-
callable = c.asCfgScope() and pos.isPositional(i) and callable.getParameter(i) = parameter
311+
exists(CfgScope callable | callable = c.asCfgScope() |
312+
pos.isKeyword(parameter.getName())
313+
or
314+
// Given a function f with parameters x, y we map
315+
// x to the positions:
316+
// 1. keyword(x)
317+
// 2. position(0, {y})
318+
// 3. position(0, {})
319+
// Likewise, y is mapped to the positions:
320+
// 1. keyword(y)
321+
// 2. position(0, {x})
322+
// 3. position(1, {})
323+
// The interpretation of `position(i, S)` is the position of the i'th unnamed parameter when the
324+
// keywords in S are specified.
325+
exists(int i, int j, string name, NamedSet ns, Function f |
326+
pos.isPositional(j, ns) and
327+
parameter.getIndex() = i and
328+
f = parameter.getFunction() and
329+
f = ns.getAFunction() and
330+
name = parameter.getName() and
331+
not name = ns.getAName() and
332+
j = i - count(int k | k < i and f.getParameter(k).getName() = ns.getAName())
333+
)
260334
)
261335
}
262336

@@ -275,14 +349,29 @@ abstract class ArgumentNode extends Node {
275349
/** Holds if this argument occurs at the given position in the given call. */
276350
abstract predicate argumentOf(DataFlowCall call, ArgumentPosition pos);
277351

278-
abstract predicate sourceArgumentOf(CfgNodes::StmtNodes::CmdCfgNode call, ArgumentPosition pos);
279-
280352
/** Gets the call in which this node is an argument. */
281353
final DataFlowCall getCall() { this.argumentOf(result, _) }
282354
}
283355

284356
module ArgumentNodes {
285-
// TODO
357+
class ExplicitArgumentNode extends ArgumentNode {
358+
CfgNodes::ExprNodes::ArgumentCfgNode arg;
359+
360+
ExplicitArgumentNode() { this.asExpr() = arg }
361+
362+
override predicate argumentOf(DataFlowCall call, ArgumentPosition pos) {
363+
arg.getCmd() = call.asCall() and
364+
(
365+
pos.isKeyword(arg.getName())
366+
or
367+
exists(NamedSet ns, int i |
368+
i = arg.getPosition() and
369+
ns.getAnExactBindingCall() = call.asCall().getStmt() and
370+
pos.isPositional(i, ns)
371+
)
372+
)
373+
}
374+
}
286375
}
287376

288377
import ArgumentNodes

0 commit comments

Comments
 (0)