Skip to content

Commit

Permalink
go/analysis/passes/ctrlflow: add typeparams test
Browse files Browse the repository at this point in the history
This CL adds a test for the assign pass that involves use of generics.

Updates golang/go#48704

Change-Id: I19d27932f0d326e2456b6714f76de01d46f1b1e3
Reviewed-on: https://go-review.googlesource.com/c/tools/+/357777
Trust: Zvonimir Pavlinovic <zpavlinovic@google.com>
Run-TryBot: Zvonimir Pavlinovic <zpavlinovic@google.com>
gopls-CI: kokoro <noreply+kokoro@google.com>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Robert Findley <rfindley@google.com>
  • Loading branch information
zpavlinovic committed Oct 28, 2021
1 parent 8de2a7f commit c8ad2e1
Show file tree
Hide file tree
Showing 5 changed files with 131 additions and 3 deletions.
54 changes: 52 additions & 2 deletions go/analysis/passes/ctrlflow/ctrlflow.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,11 @@ import (

"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/ast/astutil"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/go/cfg"
"golang.org/x/tools/go/types/typeutil"
"golang.org/x/tools/internal/typeparams"
)

var Analyzer = &analysis.Analyzer{
Expand Down Expand Up @@ -187,8 +189,12 @@ func (c *CFGs) callMayReturn(call *ast.CallExpr) (r bool) {
return false // panic never returns
}

// Is this a static call?
fn := typeutil.StaticCallee(c.pass.TypesInfo, call)
// Is this a static call? Also includes static functions
// parameterized by a type. Such functions may or may not
// return depending on the parameter type, but in some
// cases the answer is definite. We let ctrlflow figure
// that out.
fn := staticCallee(c.pass.TypesInfo, call)
if fn == nil {
return true // callee not statically known; be conservative
}
Expand All @@ -204,6 +210,50 @@ func (c *CFGs) callMayReturn(call *ast.CallExpr) (r bool) {
return !c.pass.ImportObjectFact(fn, new(noReturn))
}

// staticCallee returns static function, if any, called by call.
// Effectivelly reduces to typeutil.StaticCallee. In addition,
// returns static function parameterized by a type, if any.
//
// TODO(zpavlinovic): can this be replaced by typeutil.StaticCallee
// in the future?
func staticCallee(info *types.Info, call *ast.CallExpr) *types.Func {
if fn := typeutil.StaticCallee(info, call); fn != nil {
return fn
}
return staticTypeParamCallee(info, call)
}

// staticTypeParamCallee returns the static function in call, if any,
// expected to be parameterized by a type.
func staticTypeParamCallee(info *types.Info, call *ast.CallExpr) *types.Func {
ix := typeparams.GetIndexExprData(astutil.Unparen(call.Fun))
if ix == nil {
return nil
}

var obj types.Object
switch fun := ix.X.(type) {
case *ast.Ident:
obj = info.Uses[fun] // type, var, builtin, or declared func
case *ast.SelectorExpr:
if sel, ok := info.Selections[fun]; ok {
obj = sel.Obj() // method or field
} else {
obj = info.Uses[fun.Sel] // qualified identifier?
}
}

if f, ok := obj.(*types.Func); ok && !interfaceMethod(f) {
return f
}
return nil
}

func interfaceMethod(f *types.Func) bool {
recv := f.Type().(*types.Signature).Recv()
return recv != nil && types.IsInterface(recv.Type())
}

var panicBuiltin = types.Universe.Lookup("panic").(*types.Builtin)

func hasReachableReturn(g *cfg.CFG) bool {
Expand Down
8 changes: 7 additions & 1 deletion go/analysis/passes/ctrlflow/ctrlflow_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,19 @@ import (

"golang.org/x/tools/go/analysis/analysistest"
"golang.org/x/tools/go/analysis/passes/ctrlflow"
"golang.org/x/tools/internal/typeparams"
)

func Test(t *testing.T) {
testdata := analysistest.TestData()

// load testdata/src/a/a.go
results := analysistest.Run(t, testdata, ctrlflow.Analyzer, "a")
tests := []string{"a"}
if typeparams.Enabled {
// and testdata/src/typeparams/typeparams.go when possible
tests = append(tests, "typeparams")
}
results := analysistest.Run(t, testdata, ctrlflow.Analyzer, tests...)

// Perform a minimal smoke test on
// the result (CFG) computed by ctrlflow.
Expand Down
4 changes: 4 additions & 0 deletions go/analysis/passes/ctrlflow/testdata/src/a/a.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package a

// This file tests facts produced by ctrlflow.
Expand Down
4 changes: 4 additions & 0 deletions go/analysis/passes/ctrlflow/testdata/src/lib/lib.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package lib

func CanReturn() {}
Expand Down
64 changes: 64 additions & 0 deletions go/analysis/passes/ctrlflow/testdata/src/typeparams/typeparams.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package a

// This file tests facts produced by ctrlflow.

var cond bool

var funcs = []func(){func() {}}

func a[A any]() { // want a:"noReturn"
if cond {
funcs[0]()
b[A]()
} else {
for {
}
}
}

func b[B any]() { // want b:"noReturn"
select {}
}

func c[A, B any]() { // want c:"noReturn"
if cond {
a[A]()
} else {
d[A, B]()
}
}

func d[A, B any]() { // want d:"noReturn"
b[B]()
}

type I[T any] interface {
Id(T) T
}

func e[T any](i I[T], t T) T {
return i.Id(t)
}

func k[T any](i I[T], t T) T { // want k:"noReturn"
b[T]()
return i.Id(t)
}

type T[X any] int

func (T[X]) method1() { // want method1:"noReturn"
a[X]()
}

func (T[X]) method2() { // (may return)
if cond {
a[X]()
} else {
funcs[0]()
}
}

0 comments on commit c8ad2e1

Please sign in to comment.