Skip to content

Commit be72021

Browse files
committed
Allow functions with multiple return values in Guards logic
1 parent 9ca7e14 commit be72021

4 files changed

Lines changed: 262 additions & 46 deletions

File tree

‎go/ql/lib/semmle/go/controlflow/Guards.qll‎

Lines changed: 80 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -304,33 +304,80 @@ private module GuardsInput implements
304304
pragma[inline]
305305
predicate parameterMatch(ParameterPosition ppos, ArgumentPosition apos) { ppos = apos }
306306

307-
final private class FinalFunction = G::Function;
307+
private newtype TNonOverridableMethod =
308+
TMethod(G::Function function, int resultIndex) {
309+
exists(function.getFuncDecl()) and
310+
resultIndex in [-1 .. function.getNumResult() - 1]
311+
}
308312

309313
/**
310-
* A declared function or concrete method.
314+
* A result of a declared function or concrete method, or its normal completion.
311315
*
312316
* Calls are restricted separately to calls whose syntactic target is this
313317
* function or method, excluding interface dispatch.
314318
*/
315-
class NonOverridableMethod extends FinalFunction {
316-
NonOverridableMethod() {
317-
exists(super.getFuncDecl()) and
318-
super.getNumResult() <= 1
319+
class NonOverridableMethod extends TNonOverridableMethod {
320+
G::Function getFunction() { this = TMethod(result, _) }
321+
322+
int getResultIndex() { this = TMethod(_, result) }
323+
324+
string toString() {
325+
result = this.getFunction().toString() + " result " + this.getResultIndex().toString()
319326
}
320327

321-
Parameter getParameter(ParameterPosition ppos) { result = super.getParameter(ppos) }
328+
int getNumParameter() { result = this.getFunction().getNumParameter() }
329+
330+
Parameter getParameter(ParameterPosition ppos) {
331+
result = this.getFunction().getParameter(ppos)
332+
}
333+
334+
/**
335+
* Holds if every return maps one expression to each result position.
336+
*
337+
* Otherwise, the shared wrapper analysis would treat a partial set of
338+
* return expressions as exhaustive.
339+
*/
340+
private predicate hasOnlyPositionMappedReturns() {
341+
forall(G::ReturnStmt ret | ret.getEnclosingFunction() = this.getFunction().getFuncDecl() |
342+
ret.getNumExpr() = this.getFunction().getNumResult()
343+
)
344+
}
322345

323346
/** Gets an expression being returned by this function. */
324347
Expr getAReturnExpr() {
325348
exists(G::ReturnStmt ret |
326-
ret.getEnclosingFunction() = super.getFuncDecl() and
327-
result = ret.getExpr()
349+
this.getResultIndex() >= 0 and
350+
this.hasOnlyPositionMappedReturns() and
351+
ret.getEnclosingFunction() = this.getFunction().getFuncDecl() and
352+
result = ret.getExpr(this.getResultIndex())
328353
)
329354
}
330355
}
331356

332-
private predicate nonOverridableCall(G::CallExpr call, NonOverridableMethod m) {
333-
call.getTarget() = m
357+
private predicate extractedCallResult(Expr use, G::CallExpr call, int resultIndex) {
358+
exists(GoSsa::SsaDefinition def, IR::ExtractTupleElementInstruction extract |
359+
use = def.getVariable().getAUse().(IR::EvalInstruction).getExpr() and
360+
def.(GoSsa::SsaExplicitDefinition).getInstruction() = extract and
361+
extract.extractsElement(IR::evalExprInstruction(call), resultIndex)
362+
)
363+
}
364+
365+
private predicate nonOverridableCall(
366+
Expr resultExpr, G::CallExpr call, NonOverridableMethod method
367+
) {
368+
call.getTarget() = method.getFunction() and
369+
(
370+
method.getResultIndex() = -1 and
371+
resultExpr = call
372+
or
373+
method.getResultIndex() = 0 and
374+
method.getFunction().getNumResult() = 1 and
375+
resultExpr = call
376+
or
377+
method.getResultIndex() >= 0 and
378+
method.getFunction().getNumResult() > 1 and
379+
extractedCallResult(resultExpr, call, method.getResultIndex())
380+
)
334381
}
335382

336383
private predicate hasExplicitReceiverArgument(G::CallExpr call) {
@@ -352,28 +399,32 @@ private module GuardsInput implements
352399
)
353400
}
354401

355-
class NonOverridableMethodCall extends Expr instanceof G::CallExpr {
356-
NonOverridableMethodCall() { nonOverridableCall(this, _) }
402+
class NonOverridableMethodCall extends Expr {
403+
NonOverridableMethodCall() { nonOverridableCall(this, _, _) }
404+
405+
private G::CallExpr getCall() { nonOverridableCall(this, result, _) }
357406

358-
NonOverridableMethod getMethod() { nonOverridableCall(this, result) }
407+
NonOverridableMethod getMethod() { nonOverridableCall(this, _, result) }
359408

360409
Expr getArgument(ArgumentPosition apos) {
361-
(
362-
not hasExplicitReceiverArgument(this) and
410+
exists(G::CallExpr call | call = this.getCall() |
363411
(
364-
apos = -1 and
365-
result = getDirectReceiverArgument(this)
412+
not hasExplicitReceiverArgument(call) and
413+
(
414+
apos = -1 and
415+
result = getDirectReceiverArgument(call)
416+
or
417+
apos != -1 and
418+
result = call.getArgument(apos)
419+
)
366420
or
367-
apos != -1 and
368-
result = super.getArgument(apos)
421+
hasExplicitReceiverArgument(call) and
422+
result = call.getArgument(apos + 1)
423+
) and
424+
not (
425+
call.hasImplicitVarargs() and
426+
apos = call.getTarget().getNumParameter() - 1
369427
)
370-
or
371-
hasExplicitReceiverArgument(this) and
372-
result = super.getArgument(apos + 1)
373-
) and
374-
not (
375-
super.hasImplicitVarargs() and
376-
apos = this.getTarget().getNumParameter() - 1
377428
)
378429
}
379430
}
@@ -413,8 +464,8 @@ private module LogicInput implements GuardsImpl::LogicInputSig {
413464

414465
predicate implicitReturnDefinition(GuardsInput::NonOverridableMethod method, SsaDefinition def) {
415466
exists(IR::ReadResultInstruction read |
416-
method.getNumResult() = 1 and
417-
read.reads(method.getResult(0)) and
467+
method.getResultIndex() >= 0 and
468+
read.reads(method.getFunction().getResult(method.getResultIndex())) and
418469
def.getVariable().getAUse() = read
419470
)
420471
}

‎go/ql/test/library-tests/semmle/go/controlflow/Guards/Guards.expected‎

Lines changed: 25 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,10 @@ valueControlsResult
198198
| guards.go:309:13:309:20 | pointers | explicit variadic wrapper | not null |
199199
| guards.go:321:2:321:22 | call to ensureNotNil | after assertion | no exception |
200200
| guards.go:321:15:321:21 | pointer | after assertion | not null |
201+
| guards.go:333:2:333:32 | call to ensureNotNilWithResult | after single-result assertion | no exception |
202+
| guards.go:333:25:333:31 | pointer | after single-result assertion | not null |
203+
| guards.go:345:2:345:33 | call to ensureNotNilWithResults | after multi-result assertion | no exception |
204+
| guards.go:345:26:345:32 | pointer | after multi-result assertion | not null |
201205
ensuresEqResult
202206
| guards.go:14:7:14:16 | ...==... | 0 = value | true |
203207
| guards.go:14:7:14:16 | ...==... | value = 0 | true |
@@ -247,16 +251,20 @@ ensuresEqResult
247251
| guards.go:302:9:302:21 | ...!=... | values = nil | false |
248252
| guards.go:315:5:315:18 | ...==... | nil = pointer | true |
249253
| guards.go:315:5:315:18 | ...==... | pointer = nil | true |
250-
| guards.go:328:9:328:22 | ...!=... | nil = pointer | false |
251-
| guards.go:328:9:328:22 | ...!=... | pointer = nil | false |
252-
| guards.go:353:5:353:32 | ...==... | 0 = call to modelZeroGuard | true |
253-
| guards.go:353:5:353:32 | ...==... | call to modelZeroGuard = 0 | true |
254-
| guards.go:356:5:356:35 | ...!=... | 0 = call to modelNotZeroGuard | false |
255-
| guards.go:356:5:356:35 | ...!=... | call to modelNotZeroGuard = 0 | false |
256-
| guards.go:359:5:359:34 | ...==... | call to modelNullGuard = nil | true |
257-
| guards.go:359:5:359:34 | ...==... | nil = call to modelNullGuard | true |
258-
| guards.go:362:5:362:37 | ...!=... | call to modelNotNullGuard = nil | false |
259-
| guards.go:362:5:362:37 | ...!=... | nil = call to modelNotNullGuard | false |
254+
| guards.go:326:5:326:18 | ...==... | nil = pointer | true |
255+
| guards.go:326:5:326:18 | ...==... | pointer = nil | true |
256+
| guards.go:338:5:338:18 | ...==... | nil = pointer | true |
257+
| guards.go:338:5:338:18 | ...==... | pointer = nil | true |
258+
| guards.go:352:9:352:22 | ...!=... | nil = pointer | false |
259+
| guards.go:352:9:352:22 | ...!=... | pointer = nil | false |
260+
| guards.go:377:5:377:32 | ...==... | 0 = call to modelZeroGuard | true |
261+
| guards.go:377:5:377:32 | ...==... | call to modelZeroGuard = 0 | true |
262+
| guards.go:380:5:380:35 | ...!=... | 0 = call to modelNotZeroGuard | false |
263+
| guards.go:380:5:380:35 | ...!=... | call to modelNotZeroGuard = 0 | false |
264+
| guards.go:383:5:383:34 | ...==... | call to modelNullGuard = nil | true |
265+
| guards.go:383:5:383:34 | ...==... | nil = call to modelNullGuard | true |
266+
| guards.go:386:5:386:37 | ...!=... | call to modelNotNullGuard = nil | false |
267+
| guards.go:386:5:386:37 | ...!=... | nil = call to modelNotNullGuard | false |
260268
defaultCaseResult
261269
| guards.go:16:2:17:18 | case clause | positive | true |
262270
| guards.go:27:2:28:22 | case clause | tagged other | true |
@@ -265,11 +273,11 @@ defaultCaseResult
265273
| guards.go:155:2:156:22 | case clause | default only | true |
266274
| guards.go:164:2:165:32 | case clause | expressionless default | true |
267275
modelBarrierResult
268-
| guards.go:351:27:351:33 | pointer | model bool | model-bool |
269-
| guards.go:354:27:354:33 | pointer | model zero | model-zero |
270-
| guards.go:357:31:357:37 | pointer | model not zero | model-not-zero |
271-
| guards.go:360:27:360:33 | pointer | model null | model-null |
272-
| guards.go:363:31:363:37 | pointer | model not null | model-not-null |
273-
| guards.go:366:34:366:40 | pointer | model no exception | model-no-exception |
276+
| guards.go:375:27:375:33 | pointer | model bool | model-bool |
277+
| guards.go:378:27:378:33 | pointer | model zero | model-zero |
278+
| guards.go:381:31:381:37 | pointer | model not zero | model-not-zero |
279+
| guards.go:384:27:384:33 | pointer | model null | model-null |
280+
| guards.go:387:31:387:37 | pointer | model not null | model-not-null |
281+
| guards.go:390:34:390:40 | pointer | model no exception | model-no-exception |
274282
legacyConditionGuardCount
275-
| 88 |
283+
| 92 |

‎go/ql/test/library-tests/semmle/go/controlflow/Guards/guards.go‎

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -322,6 +322,30 @@ func exceptionGuard(pointer *int) {
322322
sink("after assertion") // pointer is not null
323323
}
324324

325+
func ensureNotNilWithResult(pointer *int) *int {
326+
if pointer == nil {
327+
panic("nil pointer")
328+
}
329+
return pointer
330+
}
331+
332+
func exceptionGuardWithResult(pointer *int) {
333+
ensureNotNilWithResult(pointer)
334+
sink("after single-result assertion") // pointer is not null
335+
}
336+
337+
func ensureNotNilWithResults(pointer *int) (*int, bool) {
338+
if pointer == nil {
339+
panic("nil pointer")
340+
}
341+
return pointer, true
342+
}
343+
344+
func exceptionGuardWithResults(pointer *int) {
345+
ensureNotNilWithResults(pointer)
346+
sink("after multi-result assertion") // pointer is not null
347+
}
348+
325349
func valueSink(string, any) {}
326350

327351
func modelBoolGuard(pointer *int) bool {

‎go/ql/test/library-tests/semmle/go/dataflow/GuardingFunctions/test.go‎

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -334,6 +334,62 @@ func deeplyNestedConditionalRight(p string) bool {
334334
return p[1] == 'b' && len(p)%2 == 1 && p[0] == 'a' && !isBad(p)
335335
}
336336

337+
// Valid when the second result is nil
338+
func guardMultiError(p string) (string, error) {
339+
if isBad(p) {
340+
return "", errors.New("invalid")
341+
}
342+
return p, nil
343+
}
344+
345+
// Valid when the first result is true; the second result is unrelated
346+
func guardMultiResultIsolation(p string) (bool, bool) {
347+
return !isBad(p), true
348+
}
349+
350+
// Valid when the named error result is nil
351+
func guardMultiNamed(p string) (value string, err error) {
352+
if isBad(p) {
353+
err = errors.New("invalid")
354+
return
355+
}
356+
value = p
357+
return
358+
}
359+
360+
// Not a guard: the naked return can return false without validating p
361+
func mixedNamedResultGuard(p string, bypass bool) (invalid bool) {
362+
if bypass {
363+
return
364+
}
365+
return isBad(p)
366+
}
367+
368+
func uncheckedMultiResult(p string) (string, error) {
369+
return p, nil
370+
}
371+
372+
// Not a guard: the tuple-forwarding return can return nil without validating p
373+
func mixedTupleReturnGuard(p string, bypass bool) (string, error) {
374+
if bypass {
375+
return uncheckedMultiResult(p)
376+
}
377+
if isBad(p) {
378+
return "", errors.New("invalid")
379+
}
380+
return p, nil
381+
}
382+
383+
type multiGuard struct{}
384+
385+
// Validates p when the second result is nil
386+
func (multiGuard) validate(p string) (string, error) {
387+
if isBad(p) {
388+
return "", errors.New("invalid")
389+
}
390+
return p, nil
391+
}
392+
337393
// Finally, actually test the functions -- try sinking a tainted value in the is-true/false
338394
// or is-nil/non-nil case for each candidate:
339395

@@ -848,4 +904,81 @@ func test() {
848904
}
849905
}
850906

907+
{
908+
s := source()
909+
_, err := guardMultiError(s)
910+
if err == nil {
911+
sink(s)
912+
} else {
913+
sink(s) // $ hasValueFlow="s"
914+
}
915+
}
916+
917+
{
918+
s := source()
919+
valid, _ := guardMultiResultIsolation(s)
920+
if valid {
921+
sink(s)
922+
} else {
923+
sink(s) // $ hasValueFlow="s"
924+
}
925+
}
926+
927+
{
928+
s := source()
929+
_, unrelated := guardMultiResultIsolation(s)
930+
if unrelated {
931+
sink(s) // $ hasValueFlow="s"
932+
} else {
933+
sink(s) // $ hasValueFlow="s"
934+
}
935+
}
936+
937+
{
938+
s := source()
939+
_, err := guardMultiNamed(s)
940+
if err == nil {
941+
sink(s)
942+
} else {
943+
sink(s) // $ hasValueFlow="s"
944+
}
945+
}
946+
947+
{
948+
s := source()
949+
invalid := mixedNamedResultGuard(s, true)
950+
if !invalid {
951+
sink(s) // $ hasValueFlow="s"
952+
}
953+
}
954+
955+
{
956+
s := source()
957+
_, err := mixedTupleReturnGuard(s, true)
958+
if err == nil {
959+
sink(s) // $ hasValueFlow="s"
960+
}
961+
}
962+
963+
{
964+
s := source()
965+
_, err := guardMultiError(s)
966+
copiedErr := err
967+
if copiedErr == nil {
968+
sink(s)
969+
} else {
970+
sink(s) // $ hasValueFlow="s"
971+
}
972+
}
973+
974+
{
975+
s := source()
976+
_, err := (multiGuard{}).validate(s)
977+
if err == nil {
978+
sink(s)
979+
} else {
980+
sink(s) // $ hasValueFlow="s"
981+
}
982+
}
983+
851984
}

0 commit comments

Comments
 (0)