Skip to content

Commit 270ba09

Browse files
authored
Merge pull request github#11732 from owen-mc/go/fix/model-data-flow-through-varargs
Go: Allow data flow through varargs parameters
2 parents 619d25e + 1c66564 commit 270ba09

File tree

34 files changed

+201
-101
lines changed

34 files changed

+201
-101
lines changed
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
---
2+
category: minorAnalysis
3+
---
4+
* Fixed data flow through variadic function parameters. The arguments corresponding to a variadic parameter are no longer returned by `CallNode.getArgument(int i)` and `CallNode.getAnArgument()`, and hence aren't `ArgumentNode`s. They now have one result, which is an `ImplicitVarargsSlice` node. For example, a call `f(a, b, c)` to a function `f(T...)` is treated like `f([]T{a, b, c})`. The old behaviour is preserved by `CallNode.getSyntacticArgument(int i)` and `CallNode.getASyntacticArgument()`. `CallExpr.getArgument(int i)` and `CallExpr.getAnArgument()` are unchanged, and will still have three results in the example given.

go/ql/lib/semmle/go/Expr.qll

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -857,6 +857,24 @@ class CallExpr extends CallOrConversionExpr {
857857
/** Gets the number of argument expressions of this call. */
858858
int getNumArgument() { result = count(this.getAnArgument()) }
859859

860+
/** Holds if this call has implicit variadic arguments. */
861+
predicate hasImplicitVarargs() {
862+
this.getCalleeType().isVariadic() and
863+
not this.hasEllipsis()
864+
}
865+
866+
/**
867+
* Gets an argument with an ellipsis after it which is passed to a varargs
868+
* parameter, as in `f(x...)`.
869+
*
870+
* Note that if the varargs parameter is `...T` then the type of the argument
871+
* must be assignable to the slice type `[]T`.
872+
*/
873+
Expr getExplicitVarargsArgument() {
874+
this.hasEllipsis() and
875+
result = this.getArgument(this.getNumArgument() - 1)
876+
}
877+
860878
/**
861879
* Gets the name of the invoked function, method or variable if it can be
862880
* determined syntactically.
@@ -873,6 +891,15 @@ class CallExpr extends CallOrConversionExpr {
873891
)
874892
}
875893

894+
/**
895+
* Gets the signature type of the invoked function.
896+
*
897+
* Note that it avoids calling `getTarget()` so that it works even when that
898+
* predicate isn't defined, for example when calling a variable with function
899+
* type.
900+
*/
901+
SignatureType getCalleeType() { result = this.getCalleeExpr().getType() }
902+
876903
/** Gets the declared target of this call. */
877904
Function getTarget() { this.getCalleeExpr() = result.getAReference() }
878905

go/ql/lib/semmle/go/StringOps.qll

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -219,7 +219,7 @@ module StringOps {
219219
* replaced.
220220
*/
221221
DataFlow::Node getAReplacedArgument() {
222-
exists(int n | n % 2 = 0 and result = this.getArgument(n))
222+
exists(int n | n % 2 = 0 and result = this.getSyntacticArgument(n))
223223
}
224224
}
225225

@@ -304,11 +304,6 @@ module StringOps {
304304
* Gets the parameter index of the format string.
305305
*/
306306
abstract int getFormatStringIndex();
307-
308-
/**
309-
* Gets the parameter index of the first parameter to be formatted.
310-
*/
311-
abstract int getFirstFormattedParameterIndex();
312307
}
313308

314309
/**
@@ -336,7 +331,7 @@ module StringOps {
336331
formatDirective = this.getComponent(n) and
337332
formatDirective.charAt(0) = "%" and
338333
formatDirective.charAt(1) != "%" and
339-
result = this.getArgument((n / 2) + f.getFirstFormattedParameterIndex())
334+
result = this.getImplicitVarargsArgument(n / 2)
340335
}
341336
}
342337
}

go/ql/lib/semmle/go/dataflow/FunctionInputsAndOutputs.qll

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,9 @@ private class ParameterInput extends FunctionInput, TInParameter {
7474

7575
override predicate isParameter(int i) { i = index }
7676

77-
override DataFlow::Node getEntryNode(DataFlow::CallNode c) { result = c.getArgument(index) }
77+
override DataFlow::Node getEntryNode(DataFlow::CallNode c) {
78+
result = c.getSyntacticArgument(index)
79+
}
7880

7981
override DataFlow::Node getExitNode(FuncDef f) {
8082
result = DataFlow::parameterNode(f.getParameter(index))
@@ -280,7 +282,7 @@ private class OutReceiver extends FunctionOutput, TOutReceiver {
280282
/**
281283
* A parameter of a function, viewed as an output.
282284
*
283-
* Note that slices passed to varargs parameters using `...` are not included, since in this
285+
* Note that slices passed to variadic parameters using `...` are not included, since in this
284286
* case it is ambiguous whether the output should be the slice itself or one of its elements.
285287
*/
286288
private class OutParameter extends FunctionOutput, TOutParameter {
@@ -298,9 +300,12 @@ private class OutParameter extends FunctionOutput, TOutParameter {
298300

299301
override DataFlow::Node getExitNode(DataFlow::CallNode c) {
300302
exists(DataFlow::Node arg |
301-
arg = getArgument(c, index) and
302-
// exclude slices passed to varargs parameters using `...` calls
303+
arg = c.getSyntacticArgument(index) and
304+
// exclude slices followed by `...` passed to variadic parameters
303305
not (c.hasEllipsis() and index = c.getNumArgument() - 1)
306+
or
307+
arg = c.(DataFlow::MethodCallNode).getReceiver() and
308+
index = -1
304309
|
305310
result.(DataFlow::PostUpdateNode).getPreUpdateNode() = arg
306311
)

go/ql/lib/semmle/go/dataflow/internal/ContainerFlow.qll

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ private import semmle.go.dataflow.ExternalFlow
1010
* Holds if the step from `node1` to `node2` stores a value in an array, a
1111
* slice, a collection or a map. Thus, `node2` references an object with a
1212
* content `c` that contains the value of `node1`. This covers array
13-
* assignments and initializers as well as implicit array creations for
13+
* assignments and initializers as well as implicit slice creations for
1414
* varargs.
1515
*/
1616
predicate containerStoreStep(Node node1, Node node2, Content c) {
@@ -20,7 +20,11 @@ predicate containerStoreStep(Node node1, Node node2, Content c) {
2020
node2.getType() instanceof ArrayType or
2121
node2.getType() instanceof SliceType
2222
) and
23-
exists(Write w | w.writesElement(node2, _, node1))
23+
(
24+
exists(Write w | w.writesElement(node2, _, node1))
25+
or
26+
node1 = node2.(ImplicitVarargsSlice).getCallNode().getAnImplicitVarargsArgument()
27+
)
2428
)
2529
or
2630
c instanceof CollectionContent and

go/ql/lib/semmle/go/dataflow/internal/DataFlowNodes.qll

Lines changed: 93 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ private newtype TNode =
1010
MkInstructionNode(IR::Instruction insn) or
1111
MkSsaNode(SsaDefinition ssa) or
1212
MkGlobalFunctionNode(Function f) or
13+
MkImplicitVarargsSlice(CallExpr c) { c.hasImplicitVarargs() } or
1314
MkSummarizedParameterNode(SummarizedCallable c, int i) {
1415
FlowSummaryImpl::Private::summaryParameterNodeRange(c, i)
1516
} or
@@ -426,6 +427,41 @@ module Public {
426427
override ResultNode getAResult() { result.getRoot() = this.getExpr() }
427428
}
428429

430+
/**
431+
* An implicit varargs slice creation expression.
432+
*
433+
* A variadic function like `f(t1 T1, ..., Tm tm, A... x)` actually sees the
434+
* varargs parameter as a slice `[]A`. A call `f(t1, ..., tm, x1, ..., xn)`
435+
* desugars to `f(t1, ..., tm, []A{x1, ..., xn})`, and this node corresponds
436+
* to this implicit slice creation.
437+
*/
438+
class ImplicitVarargsSlice extends Node, MkImplicitVarargsSlice {
439+
CallNode call;
440+
441+
ImplicitVarargsSlice() { this = MkImplicitVarargsSlice(call.getCall()) }
442+
443+
override ControlFlow::Root getRoot() { result = call.getRoot() }
444+
445+
/** Gets the call containing this varargs slice creation argument. */
446+
CallNode getCallNode() { result = call }
447+
448+
override Type getType() {
449+
exists(Function f | f = call.getTarget() |
450+
result = f.getParameterType(f.getNumParameter() - 1)
451+
)
452+
}
453+
454+
override string getNodeKind() { result = "implicit varargs slice" }
455+
456+
override string toString() { result = "[]type{args}" }
457+
458+
override predicate hasLocationInfo(
459+
string filepath, int startline, int startcolumn, int endline, int endcolumn
460+
) {
461+
call.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
462+
}
463+
}
464+
429465
/**
430466
* Gets a possible target of call `cn`.class
431467
*
@@ -498,16 +534,11 @@ module Public {
498534
CallExpr getCall() { result = this.getExpr() }
499535

500536
/**
501-
* Gets the data flow node corresponding to the `i`th argument of this call.
502-
*
503-
* Note that the first argument in calls to the built-in function `make` is a type, which is
504-
* not a data-flow node. It is skipped for the purposes of this predicate, so the (syntactically)
505-
* second argument becomes the first argument in terms of data flow.
506-
*
507-
* For calls of the form `f(g())` where `g` has multiple results, the arguments of the call to
508-
* `i` are the (implicit) element extraction nodes for the call to `g`.
537+
* Gets the `i`th argument of this call, where tuple extraction has been
538+
* done but arguments corresponding to a variadic parameter are still
539+
* considered separate.
509540
*/
510-
Node getArgument(int i) {
541+
Node getSyntacticArgument(int i) {
511542
if expr.getArgument(0).getType() instanceof TupleType
512543
then result = DataFlow::extractTupleElement(DataFlow::exprNode(expr.getArgument(0)), i)
513544
else
@@ -519,12 +550,60 @@ module Public {
519550
)
520551
}
521552

553+
/**
554+
* Gets a data flow node corresponding to an argument of this call, where
555+
* tuple extraction has been done but arguments corresponding to a variadic
556+
* parameter are still considered separate.
557+
*/
558+
Node getASyntacticArgument() { result = this.getSyntacticArgument(_) }
559+
560+
/**
561+
* Gets the data flow node corresponding to the `i`th argument of this call.
562+
*
563+
* Note that the first argument in calls to the built-in function `make` is a type, which is
564+
* not a data-flow node. It is skipped for the purposes of this predicate, so the (syntactically)
565+
* second argument becomes the first argument in terms of data flow.
566+
*
567+
* For calls of the form `f(g())` where `g` has multiple results, the arguments of the call to
568+
* `i` are the (implicit) element extraction nodes for the call to `g`.
569+
*
570+
* Returns a single `Node` corresponding to a variadic parameter. If there is no corresponding
571+
* argument with an ellipsis (`...`), then it is a `ImplicitVarargsSlice`. This is in contrast
572+
* to `getArgument` on `CallExpr`, which gets the syntactic arguments. Use
573+
* `getSyntacticArgument` to get that behavior.
574+
*/
575+
Node getArgument(int i) {
576+
result = this.getSyntacticArgument(i) and
577+
not (expr.hasImplicitVarargs() and i >= expr.getCalleeType().getNumParameter() - 1)
578+
or
579+
i = expr.getCalleeType().getNumParameter() - 1 and
580+
result.(ImplicitVarargsSlice).getCallNode() = this
581+
}
582+
522583
/** Gets the data flow node corresponding to an argument of this call. */
523584
Node getAnArgument() { result = this.getArgument(_) }
524585

525586
/** Gets the number of arguments of this call, if it can be determined. */
526587
int getNumArgument() { result = count(this.getAnArgument()) }
527588

589+
/**
590+
* Gets the 'i'th argument without an ellipsis after it which is passed to
591+
* the varargs parameter of the target of this call (if there is one).
592+
*/
593+
Node getImplicitVarargsArgument(int i) {
594+
i >= 0 and
595+
expr.hasImplicitVarargs() and
596+
exists(int lastParamIndex | lastParamIndex = expr.getCalleeType().getNumParameter() - 1 |
597+
result = this.getSyntacticArgument(lastParamIndex + i)
598+
)
599+
}
600+
601+
/**
602+
* Gets an argument without an ellipsis after it which is passed to
603+
* the varargs parameter of the target of this call (if there is one).
604+
*/
605+
Node getAnImplicitVarargsArgument() { result = this.getImplicitVarargsArgument(_) }
606+
528607
/** Gets a function passed as the `i`th argument of this call. */
529608
FunctionNode getCallback(int i) { result.getASuccessor*() = this.getArgument(i) }
530609

@@ -696,7 +775,11 @@ module Public {
696775
or
697776
preupd = getAWrittenNode()
698777
or
699-
preupd instanceof ArgumentNode and
778+
(
779+
preupd instanceof ArgumentNode and not preupd instanceof ImplicitVarargsSlice
780+
or
781+
preupd = any(CallNode c).getAnImplicitVarargsArgument()
782+
) and
700783
mutableType(preupd.getType())
701784
) and
702785
(

go/ql/lib/semmle/go/frameworks/Beego.qll

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -253,21 +253,21 @@ module Beego {
253253
this.getTarget().hasQualifiedName([packagePath(), logsPackagePath()], getALogFunctionName())
254254
}
255255

256-
override DataFlow::Node getAMessageComponent() { result = this.getAnArgument() }
256+
override DataFlow::Node getAMessageComponent() { result = this.getASyntacticArgument() }
257257
}
258258

259259
private class BeegoLoggerMethods extends LoggerCall::Range, DataFlow::MethodCallNode {
260260
BeegoLoggerMethods() {
261261
this.getTarget().hasQualifiedName(logsPackagePath(), "BeeLogger", getALogFunctionName())
262262
}
263263

264-
override DataFlow::Node getAMessageComponent() { result = this.getAnArgument() }
264+
override DataFlow::Node getAMessageComponent() { result = this.getASyntacticArgument() }
265265
}
266266

267267
private class UtilLoggers extends LoggerCall::Range, DataFlow::CallNode {
268268
UtilLoggers() { this.getTarget().hasQualifiedName(utilsPackagePath(), "Display") }
269269

270-
override DataFlow::Node getAMessageComponent() { result = this.getAnArgument() }
270+
override DataFlow::Node getAMessageComponent() { result = this.getASyntacticArgument() }
271271
}
272272

273273
private class HtmlQuoteSanitizer extends SharedXss::Sanitizer {

go/ql/lib/semmle/go/frameworks/BeegoOrm.qll

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ module BeegoOrm {
3333
// Note this class doesn't do any escaping, unlike the true ORM part of the package
3434
QueryBuilderSink() {
3535
exists(Method impl | impl.implements(packagePath(), "QueryBuilder", _) |
36-
this = impl.getACall().getAnArgument()
36+
this = impl.getACall().getASyntacticArgument()
3737
) and
3838
this.getType().getUnderlyingType() instanceof StringType
3939
}

go/ql/lib/semmle/go/frameworks/ElazarlGoproxy.qll

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ module ElazarlGoproxy {
9090
onreqcall.getTarget().hasQualifiedName(packagePath(), "ProxyHttpServer", "OnRequest")
9191
|
9292
handlerReg.getReceiver() = onreqcall.getASuccessor*() and
93-
check = onreqcall.getArgument(0)
93+
check = onreqcall.getSyntacticArgument(0)
9494
)
9595
}
9696
}
@@ -112,13 +112,11 @@ module ElazarlGoproxy {
112112
ProxyLogFunction() { this.hasQualifiedName(packagePath(), "ProxyCtx", ["Logf", "Warnf"]) }
113113

114114
override int getFormatStringIndex() { result = 0 }
115-
116-
override int getFirstFormattedParameterIndex() { result = 1 }
117115
}
118116

119117
private class ProxyLog extends LoggerCall::Range, DataFlow::MethodCallNode {
120118
ProxyLog() { this.getTarget() instanceof ProxyLogFunction }
121119

122-
override DataFlow::Node getAMessageComponent() { result = this.getAnArgument() }
120+
override DataFlow::Node getAMessageComponent() { result = this.getASyntacticArgument() }
123121
}
124122
}

go/ql/lib/semmle/go/frameworks/Email.qll

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,13 +56,13 @@ module EmailData {
5656
// func NewV3MailInit(from *Email, subject string, to *Email, content ...*Content) *SGMailV3
5757
exists(Function newv3MailInit |
5858
newv3MailInit.hasQualifiedName(sendgridMail(), "NewV3MailInit") and
59-
this = newv3MailInit.getACall().getArgument(any(int i | i = 1 or i >= 3))
59+
this = newv3MailInit.getACall().getSyntacticArgument(any(int i | i = 1 or i >= 3))
6060
)
6161
or
6262
// func (s *SGMailV3) AddContent(c ...*Content) *SGMailV3
6363
exists(Method addContent |
6464
addContent.hasQualifiedName(sendgridMail(), "SGMailV3", "AddContent") and
65-
this = addContent.getACall().getAnArgument()
65+
this = addContent.getACall().getASyntacticArgument()
6666
)
6767
}
6868
}

0 commit comments

Comments
 (0)