Skip to content

Commit 6b8ed7e

Browse files
authored
Merge pull request #15175 from tamasvajk/feature/arg-param-mapping
C#: Improve arg-param mapping logic to better handle arguments passed to `params` parameters
2 parents 25e2271 + e67035f commit 6b8ed7e

File tree

13 files changed

+523
-284
lines changed

13 files changed

+523
-284
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
category: minorAnalysis
3+
---
4+
5+
* The `Call::getArgumentForParameter` predicate has been reworked to add support for arguments passed to `params` parameters.

csharp/ql/lib/semmle/code/csharp/dataflow/Nullness.qll

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,48 @@ private predicate isMaybeNullArgument(Ssa::ExplicitDefinition def, MaybeNullExpr
163163
|
164164
p = pdef.getParameter().getUnboundDeclaration() and
165165
arg = p.getAnAssignedArgument() and
166-
not arg.getEnclosingCallable().getEnclosingCallable*() instanceof TestMethod
166+
not arg.getEnclosingCallable().getEnclosingCallable*() instanceof TestMethod and
167+
(
168+
p.isParams()
169+
implies
170+
(
171+
isValidExplicitParamsType(p, arg.getType()) and
172+
not exists(Call c | c.getAnArgument() = arg and hasMultipleParamsArguments(c))
173+
)
174+
)
175+
)
176+
}
177+
178+
/**
179+
* Holds if the type `t` is a valid argument type for passing an explicit array
180+
* to the `params` parameter `p`. For example, the types `object[]` and `string[]`
181+
* of the arguments on lines 4 and 5, respectively, are valid for the parameter
182+
* `args` on line 1 in
183+
*
184+
* ```csharp
185+
* void M(params object[] args) { ... }
186+
*
187+
* void CallM(object[] os, string[] ss, string s) {
188+
* M(os);
189+
* M(ss);
190+
* M(s);
191+
* }
192+
* ```
193+
*/
194+
pragma[nomagic]
195+
private predicate isValidExplicitParamsType(Parameter p, Type t) {
196+
p.isParams() and
197+
t.isImplicitlyConvertibleTo(p.getType())
198+
}
199+
200+
/**
201+
* Holds if call `c` has multiple arguments for a `params` parameter
202+
* of the targeted callable.
203+
*/
204+
private predicate hasMultipleParamsArguments(Call c) {
205+
exists(Parameter p | p = c.getTarget().getAParameter() |
206+
p.isParams() and
207+
exists(c.getArgument(any(int i | i > p.getPosition())))
167208
)
168209
}
169210

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

Lines changed: 1 addition & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -602,18 +602,6 @@ predicate simpleLocalFlowStep(Node nodeFrom, Node nodeTo) {
602602
nodeTo.(ObjectCreationNode).getPreUpdateNode() = nodeFrom.(ObjectInitializerNode)
603603
}
604604

605-
pragma[noinline]
606-
private Expr getImplicitArgument(Call c, int pos) {
607-
result = c.getArgument(pos) and
608-
not exists(result.getExplicitArgumentName())
609-
}
610-
611-
pragma[nomagic]
612-
private Expr getExplicitArgument(Call c, string name) {
613-
result = c.getAnArgument() and
614-
result.getExplicitArgumentName() = name
615-
}
616-
617605
/**
618606
* Holds if `arg` is a `params` argument of `c`, for parameter `p`, and `arg` will
619607
* be wrapped in an array by the C# compiler.
@@ -624,11 +612,7 @@ private predicate isParamsArg(Call c, Expr arg, Parameter p) {
624612
p = target.getAParameter() and
625613
p.isParams() and
626614
numArgs = c.getNumberOfArguments() and
627-
arg =
628-
[
629-
getImplicitArgument(c, [p.getPosition() .. numArgs - 1]),
630-
getExplicitArgument(c, p.getName())
631-
]
615+
arg = c.getArgumentForParameter(p)
632616
|
633617
numArgs > target.getNumberOfParameters()
634618
or

csharp/ql/lib/semmle/code/csharp/exprs/Call.qll

Lines changed: 11 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -57,60 +57,29 @@ class Call extends DotNet::Call, Expr, @call {
5757
*
5858
* This takes into account both positional and named arguments, but does not
5959
* consider default arguments.
60-
*
61-
* An argument must always have a type that is convertible to the relevant
62-
* parameter type. Therefore, `params` arguments are only taken into account
63-
* when they are passed as explicit arrays. For example, in the call to `M1`
64-
* on line 5, `o` is not an argument for `M1`'s `args` parameter, while
65-
* `new object[] { o }` on line 6 is, in
66-
*
67-
* ```csharp
68-
* class C {
69-
* void M1(params object[] args) { }
70-
*
71-
* void M2(object o) {
72-
* M1(o);
73-
* M1(new object[] { o });
74-
* }
75-
* }
76-
* ```
7760
*/
7861
cached
7962
override Expr getArgumentForParameter(DotNet::Parameter p) {
8063
this.getTarget().getAParameter() = p and
8164
(
8265
// Appears in the positional part of the call
83-
result = this.getImplicitArgument(p.getPosition()) and
84-
(
85-
p.(Parameter).isParams()
86-
implies
87-
(
88-
isValidExplicitParamsType(p, result.getType()) and
89-
not this.hasMultipleParamsArguments()
90-
)
91-
)
66+
result = this.getImplicitArgument(p)
9267
or
9368
// Appears in the named part of the call
94-
result = this.getExplicitArgument(p.getName()) and
95-
(p.(Parameter).isParams() implies isValidExplicitParamsType(p, result.getType()))
96-
)
97-
}
98-
99-
/**
100-
* Holds if this call has multiple arguments for a `params` parameter
101-
* of the targeted callable.
102-
*/
103-
private predicate hasMultipleParamsArguments() {
104-
exists(Parameter p | p = this.getTarget().getAParameter() |
105-
p.isParams() and
106-
exists(this.getArgument(any(int i | i > p.getPosition())))
69+
result = this.getExplicitArgument(p.getName())
10770
)
10871
}
10972

11073
pragma[noinline]
111-
private Expr getImplicitArgument(int pos) {
112-
result = this.getArgument(pos) and
113-
not exists(result.getExplicitArgumentName())
74+
private Expr getImplicitArgument(DotNet::Parameter p) {
75+
not exists(result.getExplicitArgumentName()) and
76+
(
77+
p.(Parameter).isParams() and
78+
result = this.getArgument(any(int i | i >= p.getPosition()))
79+
or
80+
not p.(Parameter).isParams() and
81+
result = this.getArgument(p.getPosition())
82+
)
11483
}
11584

11685
pragma[nomagic]
@@ -254,28 +223,6 @@ class Call extends DotNet::Call, Expr, @call {
254223
override string toString() { result = "call" }
255224
}
256225

257-
/**
258-
* Holds if the type `t` is a valid argument type for passing an explicit array
259-
* to the `params` parameter `p`. For example, the types `object[]` and `string[]`
260-
* of the arguments on lines 4 and 5, respectively, are valid for the parameter
261-
* `args` on line 1 in
262-
*
263-
* ```csharp
264-
* void M(params object[] args) { ... }
265-
*
266-
* void CallM(object[] os, string[] ss, string s) {
267-
* M(os);
268-
* M(ss);
269-
* M(s);
270-
* }
271-
* ```
272-
*/
273-
pragma[nomagic]
274-
private predicate isValidExplicitParamsType(Parameter p, Type t) {
275-
p.isParams() and
276-
t.isImplicitlyConvertibleTo(p.getType())
277-
}
278-
279226
/**
280227
* A method call, for example `a.M()` on line 5 in
281228
*

0 commit comments

Comments
 (0)