Skip to content

Commit 1bc4e40

Browse files
authored
Merge pull request #22854 from asgerf/unified/shorthand-returns
Unified: Support shorthand returns
2 parents e06cf0b + f447c31 commit 1bc4e40

14 files changed

Lines changed: 171 additions & 7 deletions

‎unified/ql/lib/codeql/unified/internal/ExprPositions.qll‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,3 +79,13 @@ predicate hasResultValue(Expr e) {
7979
not isInAssignmentContext(e, any(AssignExpr n)) and // non-compound assignment target
8080
not e instanceof IdentifierLabel
8181
}
82+
83+
/** Holds if `e` is returned from `callable` either because it is an operand of `return` or is implicitly returned due to language-specific semantics. */
84+
predicate isReturnedExpr(Callable callable, Expr e) {
85+
exists(ReturnExpr ret |
86+
callable = ret.getEnclosingCallable() and
87+
e = ret.getValue()
88+
)
89+
or
90+
any(ExprPositionsPlugin p).isReturnedExpr(callable, e)
91+
}

‎unified/ql/lib/codeql/unified/internal/ExprPositionsPlugin.qll‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,6 @@ private module Plugins {
77

88
class ExprPositionsPlugin extends Unit {
99
predicate isInTypeContext(Expr e) { none() }
10+
11+
predicate isReturnedExpr(Callable callable, Expr e) { none() }
1012
}

‎unified/ql/lib/codeql/unified/internal/ExprPositionsPluginSwift.qll‎

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,24 @@ private import ExprPositionsPlugin
33

44
private class ExprPositionsPluginSwift extends ExprPositionsPlugin {
55
override predicate isInTypeContext(Expr e) { e = any(GenericTypeExpr g).getBase() }
6+
7+
override predicate isReturnedExpr(Callable callable, Expr e) {
8+
// Short-hand return from a function body, e.g. `func foo() -> String { "42" }`
9+
// This happens when the body contains a single expression and this expression resolves to a value.
10+
// We don't detect precisely whether the expression resolves to a value, but we restrict to cases
11+
// where a spurious return would be harmless in practice.
12+
(
13+
callable instanceof FunctionDeclaration and
14+
exists(Expr ret | ret = callable.getReturnType() | not ret.(Identifier).getValue() = "Void")
15+
or
16+
callable instanceof GetterDeclaration
17+
or
18+
callable instanceof FunctionExpr // We can't tell whether closures have a return value
19+
) and
20+
exists(Block body |
21+
body = callable.getBody() and
22+
not exists(body.getStmt(1)) and // Check that only one statement exists
23+
e = body.getStmt(0)
24+
)
25+
}
626
}

‎unified/ql/lib/codeql/unified/internal/FacadeAst.qll‎

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -437,4 +437,40 @@ module Unified {
437437
/** Gets the number of elements in this array literal. */
438438
int getNumberOfElements() { result = count(this.getAnElement()) }
439439
}
440+
441+
/**
442+
* A declaration of a getter associated with a variable or field.
443+
*
444+
* For example, in Swift:
445+
* ```swift
446+
* var x: String {
447+
* get { "42" } // getter declaration
448+
* }
449+
* var x: String { "42" } // getter declaration
450+
* ```
451+
*/
452+
final class GetterDeclaration extends AccessorDeclaration {
453+
GetterDeclaration() { this.getAccessorKind().getValue() = "get" }
454+
}
455+
456+
/**
457+
* A declaration of a setter associated with a variable or field.
458+
*
459+
* For example, in Swift:
460+
* ```swift
461+
* var x: String {
462+
* get { "42" }
463+
* set { print(newValue) } // setter declaration
464+
* }
465+
* ```
466+
*/
467+
final class SetterDeclaration extends AccessorDeclaration {
468+
SetterDeclaration() { this.getAccessorKind().getValue() = "set" }
469+
}
470+
471+
/** An `if` statement or conditional expression. */
472+
class IfExpr extends G::IfExpr {
473+
/** Gets then `then` or `else` branch. */
474+
Expr getABranch() { result = [this.getThen(), this.getElse()] }
475+
}
440476
}

‎unified/ql/lib/codeql/unified/internal/dataflow/DataFlowGraph.qll‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
private import unified
22
private import AllDataFlow
33
private import codeql.unified.internal.LocalNameBinding
4+
private import codeql.unified.internal.ExprPositions
45

56
predicate step(BuilderNode node1, Step step, BuilderNode node2) {
67
any(DataFlowPlugin p).step(node1, step, node2)
@@ -180,6 +181,19 @@ predicate step(BuilderNode node1, Step step, BuilderNode node2) {
180181
node2.isIncomingValue(fun.getNameNode())
181182
)
182183
or
184+
exists(Callable callable, Expr expr |
185+
isReturnedExpr(callable, expr) and
186+
node1.isResultValue(expr) and
187+
step.value() and
188+
node2.isReturnNode(callable)
189+
)
190+
or
191+
exists(IfExpr expr |
192+
node1.isResultValue(expr.getABranch()) and
193+
step.value() and
194+
node2.isResultValue(expr)
195+
)
196+
or
183197
none() // Temporarily disable compilation errors from unsatisfiable types
184198
}
185199

‎unified/ql/lib/codeql/unified/internal/dataflow/DataFlowInstantiation.qll‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,7 @@ module DataFlowInput implements InputSig<Location> {
2323
// Parameter, argument, return, and out nodes and their positions/kinds
2424
//
2525
private predicate isReturnNode(Node n, ReturnKind kind) {
26-
// TODO: Handle short-hand returns such as `func foo() -> Int { 5 }`
27-
n.asExpr() = any(ReturnExpr r).getValue() and kind.isStandardReturnValue()
26+
n.isReturnNode(_) and kind.isStandardReturnValue()
2827
or
2928
FlowSummaryOutput::summaryReturnNode(getSummaryNode(n), kind)
3029
}
@@ -240,7 +239,8 @@ module DataFlowInput implements InputSig<Location> {
240239

241240
predicate neverSkipInPathGraph(Node n) {
242241
n.isIncomingValue(_) or // Never skip assignment target
243-
n.asExpr() instanceof LocalVariableAccess // Never skip a variable reference
242+
n.asExpr() instanceof LocalVariableAccess or // Never skip a variable reference
243+
n.asExpr() = any(ReturnExpr e).getValue()
244244
}
245245

246246
class DataFlowExpr = Expr;

‎unified/ql/lib/codeql/unified/internal/dataflow/DataFlowNode.qll‎

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,9 @@ newtype TDataFlowNode =
9191
TLocalVariableRefNode(AstNode repr, LocalVariable var, VariableRefKind kind) {
9292
performsVariableAccess(repr, var, kind, _)
9393
} or
94+
TReturnNode(DataFlowCallable callable) {
95+
not exists(callable.asSummarizedCallable()) // represented by TSummaryNode
96+
} or
9497
TCallableNode(DataFlowCallable callable) or
9598
TImplicitParameterNode(DataFlowCallable callable, ImplicitParameterPosition pos) {
9699
not exists(callable.asSummarizedCallable()) // represented by TSummaryNode
@@ -104,7 +107,7 @@ newtype TDataFlowNode =
104107

105108
class TDataFlowNodeStage1 =
106109
TValueNode or TStrictlyIncomingValue or TExprPostUpdateNode or TLocalVariableRefNode or
107-
TCallableNode or TImplicitParameterNode or TImplicitArgumentNode;
110+
TReturnNode or TCallableNode or TImplicitParameterNode or TImplicitArgumentNode;
108111

109112
class TDataFlowNodeStage2 = TDataFlowNodeStage1 or TLocalSsaNode;
110113

@@ -152,6 +155,16 @@ class BuilderNode extends TDataFlowNodeStage1 {
152155
/** Holds if this represents the updated state of the value returned by `expr` after it has been mutated by the surrounding assignment or call. */
153156
predicate isPostUpdate(Expr expr) { this = TExprPostUpdateNode(expr) }
154157

158+
/** Holds if this node is the canonical representative for the return value of `callable`. */
159+
predicate isReturnNode(Callable callable) { this.isReturnNodeEx(toDataFlowCallable(callable)) }
160+
161+
/**
162+
* Holds if this node is the canonical representative for the return value of `callable`.
163+
*
164+
* Note that this does not hold for the return node in a summarized callable.
165+
*/
166+
predicate isReturnNodeEx(DataFlowCallable callable) { this = TReturnNode(callable) }
167+
155168
/** Holds if this node represents an implicit parameter of `callable`. */
156169
predicate isImplicitParameter(DataFlowCallable callable, ImplicitParameterPosition pos) {
157170
this = TImplicitParameterNode(callable, pos)
@@ -258,6 +271,11 @@ class BuilderNode extends TDataFlowNodeStage1 {
258271
result = "[callable] " + callable.toString()
259272
)
260273
or
274+
exists(DataFlowCallable callable |
275+
this.isReturnNodeEx(callable) and
276+
result = "[return] " + callable.toString()
277+
)
278+
or
261279
exists(DataFlowCall call, ArgumentPosition pos |
262280
this.isImplicitArgument(call, pos, false) and
263281
result = "[" + pos + " arg] " + call.toString()
@@ -284,6 +302,11 @@ class BuilderNode extends TDataFlowNodeStage1 {
284302
result = callable.getLocation()
285303
)
286304
or
305+
exists(DataFlowCallable callable |
306+
this.isReturnNodeEx(callable) and
307+
result = callable.getLocation()
308+
)
309+
or
287310
exists(DataFlowCall call |
288311
this.isImplicitArgument(call, _, _) and
289312
result = call.getLocation()
@@ -371,6 +394,9 @@ class Node extends TDataFlowNode {
371394
/** Holds if this is the canonical representative for the given `callable`. */
372395
predicate isCallable(Callable callable) { this.(BuilderNode).isCallable(callable) }
373396

397+
/** Holds if this node is the canonical representative for the return value of `callable`. */
398+
predicate isReturnNode(Callable callable) { this.(BuilderNode).isReturnNode(callable) }
399+
374400
/**
375401
* Gets the AST node wrapped by this data flow, if any.
376402
*/
@@ -419,8 +445,6 @@ class Node extends TDataFlowNode {
419445
result.asSourceCallable() = node.getSourceVariable().getDeclaringCallable()
420446
)
421447
or
422-
this.(BuilderNode).isImplicitParameter(result, _)
423-
or
424448
exists(CaptureSsaOutput::SynthesizedCaptureNode node |
425449
this = TCaptureSsaNode(node) and
426450
result.asSourceCallable() = node.getEnclosingCallable()
@@ -433,6 +457,8 @@ class Node extends TDataFlowNode {
433457
result = call.getEnclosingCallable()
434458
)
435459
or
460+
this.(BuilderNode).isReturnNodeEx(result)
461+
or
436462
exists(DataFlowCallable callable |
437463
this.isCallableEx(callable) and
438464
result.asSourceCallable() = callable.asSourceCallable().getEnclosingCallable()

‎unified/ql/lib/codeql/unified/internal/dataflow/DataFlowPluginSwift.qll‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,5 +76,13 @@ private class SwiftDataFlowPlugin extends DataFlowPlugin {
7676
step.taint() and
7777
node2.isResultValue(call)
7878
)
79+
or
80+
// Singleton blocks propagate their value upwards, e.g. in `if expr { expr } else { expr }`
81+
exists(Block block |
82+
not exists(block.getStmt(1)) and
83+
node1.isResultValue(block.getStmt(0)) and
84+
step.value() and
85+
node2.isResultValue(block)
86+
)
7987
}
8088
}

‎unified/ql/lib/codeql/unified/internal/mad/LegacyMaD.qll‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -245,7 +245,7 @@ private DataFlow::Node getSourceFromCall(CallExpr call, AccessPathToken token) {
245245
bindingset[callable, token]
246246
private DataFlow::Node getSinkFromCallable(Callable callable, AccessPathToken token) {
247247
token = "ReturnValue" and
248-
result.asExpr() = any(ReturnExpr r | r.getEnclosingCallable() = callable).getValue() // TODO: expose canonical return node in data flow
248+
result.isReturnNode(callable)
249249
}
250250

251251
/**
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
func source(_ s: String) -> String { return s }
2+
3+
@discardableResult
4+
func sink(_ s: Any) -> String { return "" }
5+
6+
func t1() {
7+
func getValue() -> String { source("t1.1") }
8+
sink(getValue()) // $ hasValueFlow=t1.1
9+
}

0 commit comments

Comments
 (0)