| package analysis |
| |
| import ( |
| "go/ast" |
| "go/types" |
| "sort" |
| "testing" |
| |
| "github.com/gopherjs/gopherjs/compiler/internal/typeparams" |
| "github.com/gopherjs/gopherjs/internal/srctesting" |
| ) |
| |
| func TestBlocking_Simple(t *testing.T) { |
| bt := newBlockingTest(t, |
| `package test |
| |
| func notBlocking() { |
| println("hi") |
| }`) |
| bt.assertNotBlocking(`notBlocking`) |
| } |
| |
| func TestBlocking_Recursive(t *testing.T) { |
| bt := newBlockingTest(t, |
| `package test |
| |
| func notBlocking(i int) { |
| if i > 0 { |
| println(i) |
| notBlocking(i - 1) |
| } |
| }`) |
| bt.assertNotBlocking(`notBlocking`) |
| } |
| |
| func TestBlocking_AlternatingRecursive(t *testing.T) { |
| bt := newBlockingTest(t, |
| `package test |
| |
| func near(i int) { |
| if i > 0 { |
| println(i) |
| far(i) |
| } |
| } |
| |
| func far(i int) { |
| near(i - 1) |
| }`) |
| bt.assertNotBlocking(`near`) |
| bt.assertNotBlocking(`far`) |
| } |
| |
| func TestBlocking_Channels(t *testing.T) { |
| bt := newBlockingTest(t, |
| `package test |
| |
| func readFromChannel(c chan bool) { |
| <-c |
| } |
| |
| func readFromChannelAssign(c chan bool) { |
| v := <-c |
| println(v) |
| } |
| |
| func readFromChannelAsArg(c chan bool) { |
| println(<-c) |
| } |
| |
| func sendToChannel(c chan bool) { |
| c <- true |
| } |
| |
| func rangeOnChannel(c chan bool) { |
| for v := range c { |
| println(v) |
| } |
| } |
| |
| func rangeOnSlice(c []bool) { |
| for v := range c { |
| println(v) |
| } |
| }`) |
| bt.assertBlocking(`readFromChannel`) |
| bt.assertBlocking(`sendToChannel`) |
| bt.assertBlocking(`rangeOnChannel`) |
| bt.assertBlocking(`readFromChannelAssign`) |
| bt.assertBlocking(`readFromChannelAsArg`) |
| bt.assertNotBlocking(`rangeOnSlice`) |
| } |
| |
| func TestBlocking_Selects(t *testing.T) { |
| bt := newBlockingTest(t, |
| `package test |
| |
| func selectReadWithoutDefault(a, b chan bool) { |
| select { |
| case <-a: |
| println("a") |
| case v := <-b: |
| println("b", v) |
| } |
| } |
| |
| func selectReadWithDefault(a, b chan bool) { |
| select { |
| case <-a: |
| println("a") |
| case v := <-b: |
| println("b", v) |
| default: |
| println("nothing") |
| } |
| } |
| |
| func selectSendWithoutDefault(a, b chan bool) { |
| select { |
| case a <- true: |
| println("a") |
| case b <- false: |
| println("b") |
| } |
| } |
| |
| func selectSendWithDefault(a, b chan bool) { |
| select { |
| case a <- true: |
| println("a") |
| case b <- false: |
| println("b") |
| default: |
| println("nothing") |
| } |
| }`) |
| bt.assertBlocking(`selectReadWithoutDefault`) |
| bt.assertBlocking(`selectSendWithoutDefault`) |
| bt.assertNotBlocking(`selectReadWithDefault`) |
| bt.assertNotBlocking(`selectSendWithDefault`) |
| } |
| |
| func TestBlocking_GoRoutines_WithFuncLiterals(t *testing.T) { |
| bt := newBlockingTest(t, |
| `package test |
| |
| func notBlocking(c chan bool) { |
| go func(c chan bool) { // line 4 |
| println(<-c) |
| }(c) |
| } |
| |
| func blocking(c chan bool) { |
| go func(v bool) { // line 10 |
| println(v) |
| }(<-c) |
| }`) |
| bt.assertNotBlocking(`notBlocking`) |
| bt.assertBlockingLit(4, ``) |
| |
| bt.assertBlocking(`blocking`) |
| bt.assertNotBlockingLit(10, ``) |
| } |
| |
| func TestBlocking_GoRoutines_WithNamedFuncs(t *testing.T) { |
| bt := newBlockingTest(t, |
| `package test |
| |
| func blockingRoutine(c chan bool) { |
| println(<-c) |
| } |
| |
| func nonBlockingRoutine(v bool) { |
| println(v) |
| } |
| |
| func notBlocking(c chan bool) { |
| go blockingRoutine(c) |
| } |
| |
| func blocking(c chan bool) { |
| go nonBlockingRoutine(<-c) |
| }`) |
| bt.assertBlocking(`blockingRoutine`) |
| bt.assertNotBlocking(`nonBlockingRoutine`) |
| |
| bt.assertNotBlocking(`notBlocking`) |
| bt.assertBlocking(`blocking`) |
| } |
| |
| func TestBlocking_Defers_WithoutReturns_WithFuncLiterals(t *testing.T) { |
| bt := newBlockingTest(t, |
| `package test |
| |
| func blockingBody(c chan bool) { |
| defer func(c chan bool) { // line 4 |
| println(<-c) |
| }(c) |
| } |
| |
| func blockingArg(c chan bool) { |
| defer func(v bool) { // line 10 |
| println(v) |
| }(<-c) |
| } |
| |
| func notBlocking(c chan bool) { |
| defer func(v bool) { // line 16 |
| println(v) |
| }(true) |
| }`) |
| bt.assertBlocking(`blockingBody`) |
| bt.assertBlockingLit(4, ``) |
| |
| bt.assertBlocking(`blockingArg`) |
| bt.assertNotBlockingLit(10, ``) |
| |
| bt.assertNotBlocking(`notBlocking`) |
| bt.assertNotBlockingLit(16, ``) |
| } |
| |
| func TestBlocking_Defers_WithoutReturns_WithNamedFuncs(t *testing.T) { |
| bt := newBlockingTest(t, |
| `package test |
| |
| func blockingPrint(c chan bool) { |
| println(<-c) |
| } |
| |
| func nonBlockingPrint(v bool) { |
| println(v) |
| } |
| |
| func blockingBody(c chan bool) { |
| defer blockingPrint(c) |
| } |
| |
| func blockingArg(c chan bool) { |
| defer nonBlockingPrint(<-c) |
| } |
| |
| func notBlocking(c chan bool) { |
| defer nonBlockingPrint(true) |
| }`) |
| bt.assertFuncInstCount(5) |
| bt.assertFuncLitCount(0) |
| |
| bt.assertBlocking(`blockingPrint`) |
| bt.assertNotBlocking(`nonBlockingPrint`) |
| |
| bt.assertBlocking(`blockingBody`) |
| bt.assertBlocking(`blockingArg`) |
| bt.assertNotBlocking(`notBlocking`) |
| } |
| |
| func TestBlocking_Defers_WithReturns_WithFuncLiterals(t *testing.T) { |
| bt := newBlockingTest(t, |
| `package test |
| |
| func blockingBody(c chan bool) int { |
| defer func(c chan bool) { // line 4 |
| println(<-c) |
| }(c) |
| return 42 |
| } |
| |
| func blockingArg(c chan bool) int { |
| defer func(v bool) { // line 11 |
| println(v) |
| }(<-c) |
| return 42 |
| } |
| |
| func notBlocking(c chan bool) int { |
| defer func(v bool) { // line 18 |
| println(v) |
| }(true) |
| return 42 |
| }`) |
| bt.assertBlocking(`blockingBody`) |
| bt.assertBlockingLit(4, ``) |
| |
| bt.assertBlocking(`blockingArg`) |
| bt.assertNotBlockingLit(11, ``) |
| |
| bt.assertNotBlocking(`notBlocking`) |
| bt.assertNotBlockingLit(18, ``) |
| } |
| |
| func TestBlocking_Defers_WithReturns_WithNamedFuncs(t *testing.T) { |
| bt := newBlockingTest(t, |
| `package test |
| |
| func blockingPrint(c chan bool) { |
| println(<-c) |
| } |
| |
| func nonBlockingPrint(v bool) { |
| println(v) |
| } |
| |
| func blockingBody(c chan bool) int { |
| defer blockingPrint(c) |
| return 42 // line 13 |
| } |
| |
| func blockingArg(c chan bool) int { |
| defer nonBlockingPrint(<-c) |
| return 42 // line 18 |
| } |
| |
| func notBlocking(c chan bool) int { |
| defer nonBlockingPrint(true) |
| return 42 // line 23 |
| }`) |
| bt.assertBlocking(`blockingPrint`) |
| bt.assertNotBlocking(`nonBlockingPrint`) |
| |
| bt.assertBlocking(`blockingBody`) |
| bt.assertBlockingReturn(13, ``) |
| |
| bt.assertBlocking(`blockingArg`) |
| // The defer is non-blocking so the return is not blocking |
| // even though the function is blocking. |
| bt.assertNotBlockingReturn(18, ``) |
| |
| bt.assertNotBlocking(`notBlocking`) |
| bt.assertNotBlockingReturn(23, ``) |
| } |
| |
| func TestBlocking_Defers_WithMultipleReturns(t *testing.T) { |
| bt := newBlockingTest(t, |
| `package test |
| |
| func foo(c chan int) bool { |
| defer func() { // line 4 |
| if r := recover(); r != nil { |
| println("Error", r) |
| } |
| }() |
| |
| if c == nil { |
| return false // line 11 |
| } |
| |
| defer func(v int) { // line 14 |
| println(v) |
| }(<-c) |
| |
| value := <-c |
| if value < 0 { |
| return false // line 20 |
| } |
| |
| if value > 0 { |
| defer func() { // line 24 |
| println(<-c) |
| }() |
| |
| return false // line 28 |
| } |
| |
| return true // line 31 |
| }`) |
| bt.assertBlocking(`foo`) |
| bt.assertNotBlockingLit(4, ``) |
| // Early escape from function without blocking defers is not blocking. |
| bt.assertNotBlockingReturn(11, ``) |
| bt.assertNotBlockingLit(14, ``) |
| // Function has had blocking by this point but no blocking defers yet. |
| bt.assertNotBlockingReturn(20, ``) |
| bt.assertBlockingLit(24, ``) |
| // The return is blocking because of a blocking defer. |
| bt.assertBlockingReturn(28, ``) |
| // Technically the return on line 31 is not blocking since the defer that |
| // is blocking can only exit through the return on line 28, but it would be |
| // difficult to determine which defers would only affect certain returns |
| // without doing full control flow analysis. |
| // |
| // TODO(grantnelson-wf): We could fix this at some point by keeping track |
| // of which flow control statements (e.g. if-statements) are terminating |
| // or not. Any defers added in a terminating control flow would not |
| // propagate to returns that are not in that block. |
| // |
| // For now we simply build up the list of defers as we go making |
| // the return on line 31 also blocking. |
| bt.assertBlockingReturn(31, ``) |
| } |
| |
| func TestBlocking_Defers_WithReturnsAndDefaultBlocking(t *testing.T) { |
| bt := newBlockingTest(t, |
| `package test |
| |
| type foo struct {} |
| func (f foo) Bar() { |
| println("foo") |
| } |
| |
| type stringer interface { |
| Bar() |
| } |
| |
| var fb = foo{}.Bar |
| |
| func deferInterfaceCall() bool { |
| var s stringer = foo{} |
| defer s.Bar() |
| return true // line 17 |
| } |
| |
| func deferVarCall() bool { |
| defer fb() |
| return true // line 22 |
| } |
| |
| func deferLocalVarCall() bool { |
| fp := foo{}.Bar |
| defer fp() |
| return true // line 28 |
| } |
| |
| func deferMethodExpressionCall() bool { |
| fp := foo.Bar |
| defer fp(foo{}) |
| return true // line 34 |
| } |
| |
| func deferSlicedFuncCall() bool { |
| s := []func() { fb, foo{}.Bar } |
| defer s[0]() |
| return true // line 40 |
| } |
| |
| func deferMappedFuncCall() bool { |
| m := map[string]func() { |
| "fb": fb, |
| "fNew": foo{}.Bar, |
| } |
| defer m["fb"]() |
| return true // line 49 |
| }`) |
| |
| bt.assertFuncInstCount(7) |
| bt.assertNotBlocking(`foo.Bar`) |
| |
| // None of these are actually blocking but we treat them like they are |
| // because the defers invoke functions via interfaces and function pointers. |
| bt.assertBlocking(`deferInterfaceCall`) |
| bt.assertBlocking(`deferVarCall`) |
| bt.assertBlocking(`deferLocalVarCall`) |
| bt.assertBlocking(`deferMethodExpressionCall`) |
| bt.assertBlocking(`deferSlicedFuncCall`) |
| bt.assertBlocking(`deferMappedFuncCall`) |
| |
| // All of these returns are blocking because they have blocking defers. |
| bt.assertBlockingReturn(17, ``) |
| bt.assertBlockingReturn(22, ``) |
| bt.assertBlockingReturn(28, ``) |
| bt.assertBlockingReturn(34, ``) |
| bt.assertBlockingReturn(40, ``) |
| bt.assertBlockingReturn(49, ``) |
| } |
| |
| func TestBlocking_Defers_WithReturnsAndDeferBuiltin(t *testing.T) { |
| bt := newBlockingTest(t, |
| `package test |
| |
| type strSet map[string]bool |
| |
| func deferBuiltinCall() strSet { |
| m := strSet{ |
| "foo": true, |
| } |
| defer delete(m, "foo") |
| return m // line 10 |
| }`) |
| |
| bt.assertFuncInstCount(1) |
| bt.assertNotBlocking(`deferBuiltinCall`) |
| bt.assertNotBlockingReturn(10, ``) |
| } |
| |
| func TestBlocking_Defers_WithReturnsInLoops(t *testing.T) { |
| // These are example of where a defer can affect the return that |
| // occurs prior to the defer in the function body. |
| bt := newBlockingTest(t, |
| `package test |
| |
| func blocking(c chan int) { |
| println(<-c) |
| } |
| |
| func deferInForLoop(c chan int) bool { |
| i := 1000 |
| for { |
| i-- |
| if i <= 0 { |
| return true // line 12 |
| } |
| defer blocking(c) |
| } |
| } |
| |
| func deferInForLoopReturnAfter(c chan int) bool { |
| for i := 1000; i > 0; i-- { |
| defer blocking(c) |
| } |
| return true // line 22 |
| } |
| |
| func deferInNamedForLoop(c chan int) bool { |
| i := 1000 |
| Start: |
| for { |
| i-- |
| if i <= 0 { |
| return true // line 31 |
| } |
| defer blocking(c) |
| continue Start |
| } |
| } |
| |
| func deferInNamedForLoopReturnAfter(c chan int) bool { |
| Start: |
| for i := 1000; i > 0; i-- { |
| defer blocking(c) |
| continue Start |
| } |
| return true // line 44 |
| } |
| |
| func deferInGotoLoop(c chan int) bool { |
| i := 1000 |
| Start: |
| i-- |
| if i <= 0 { |
| return true // line 52 |
| } |
| defer blocking(c) |
| goto Start |
| } |
| |
| func deferInGotoLoopReturnAfter(c chan int) bool { |
| i := 1000 |
| Start: |
| defer blocking(c) |
| i-- |
| if i > 0 { |
| goto Start |
| } |
| return true // line 66 |
| } |
| |
| func deferInRangeLoop(c chan int) bool { |
| s := []int{1, 2, 3} |
| for i := range s { |
| if i > 3 { |
| return true // line 73 |
| } |
| defer blocking(c) |
| } |
| return false // line 77 |
| }`) |
| |
| bt.assertFuncInstCount(8) |
| bt.assertBlocking(`blocking`) |
| bt.assertBlocking(`deferInForLoop`) |
| bt.assertBlocking(`deferInForLoopReturnAfter`) |
| bt.assertBlocking(`deferInNamedForLoop`) |
| bt.assertBlocking(`deferInNamedForLoopReturnAfter`) |
| bt.assertBlocking(`deferInGotoLoop`) |
| bt.assertBlocking(`deferInGotoLoopReturnAfter`) |
| bt.assertBlocking(`deferInRangeLoop`) |
| // When the following 2 returns are defined there are no defers, however, |
| // because of the loop, the blocking defers defined after the return will |
| // block the returns. |
| bt.assertBlockingReturn(12, ``) |
| bt.assertBlockingReturn(22, ``) |
| bt.assertBlockingReturn(31, ``) |
| bt.assertBlockingReturn(44, ``) |
| bt.assertBlockingReturn(52, ``) |
| bt.assertBlockingReturn(66, ``) |
| bt.assertBlockingReturn(73, ``) |
| bt.assertBlockingReturn(77, ``) |
| } |
| |
| func TestBlocking_Defers_WithReturnsInLoopsInLoops(t *testing.T) { |
| // These are example of where a defer can affect the return that |
| // occurs prior to the defer in the function body. |
| bt := newBlockingTest(t, |
| `package test |
| |
| func blocking(c chan int) { |
| println(<-c) |
| } |
| |
| func forLoopTheLoop(c chan int) bool { |
| if c == nil { |
| return false // line 9 |
| } |
| for i := 0; i < 10; i++ { |
| if i > 3 { |
| return true // line 13 |
| } |
| for j := 0; j < 10; j++ { |
| if j > 3 { |
| return true // line 17 |
| } |
| defer blocking(c) |
| if j > 2 { |
| return false // line 21 |
| } |
| } |
| if i > 2 { |
| return false // line 25 |
| } |
| } |
| return false // line 28 |
| } |
| |
| func rangeLoopTheLoop(c chan int) bool { |
| data := []int{1, 2, 3} |
| for i := range data { |
| for j := range data { |
| if i + j > 3 { |
| return true // line 36 |
| } |
| } |
| defer blocking(c) |
| } |
| return false // line 41 |
| } |
| |
| func noopThenLoop(c chan int) bool { |
| data := []int{1, 2, 3} |
| for i := range data { |
| if i > 13 { |
| return true // line 48 |
| } |
| defer func() { println("hi") }() |
| } |
| for i := range data { |
| if i > 3 { |
| return true // line 54 |
| } |
| defer blocking(c) |
| } |
| return false // line 58 |
| }`) |
| |
| bt.assertFuncInstCount(4) |
| bt.assertBlocking(`blocking`) |
| bt.assertBlocking(`forLoopTheLoop`) |
| bt.assertNotBlockingReturn(9, ``) |
| bt.assertBlockingReturn(13, ``) |
| bt.assertBlockingReturn(17, ``) |
| bt.assertBlockingReturn(21, ``) |
| bt.assertBlockingReturn(25, ``) |
| bt.assertBlockingReturn(28, ``) |
| bt.assertBlocking(`rangeLoopTheLoop`) |
| bt.assertBlockingReturn(36, ``) |
| bt.assertBlockingReturn(41, ``) |
| bt.assertBlocking(`noopThenLoop`) |
| bt.assertNotBlockingReturn(48, ``) |
| bt.assertBlockingReturn(54, ``) |
| bt.assertBlockingReturn(58, ``) |
| } |
| |
| func TestBlocking_Returns_WithoutDefers(t *testing.T) { |
| bt := newBlockingTest(t, |
| `package test |
| |
| func blocking(c chan bool) bool { |
| return <-c // line 4 |
| } |
| |
| func blockingBeforeReturn(c chan bool) bool { |
| v := <-c |
| return v // line 9 |
| } |
| |
| func indirectlyBlocking(c chan bool) bool { |
| return blocking(c) // line 13 |
| } |
| |
| func indirectlyBlockingBeforeReturn(c chan bool) bool { |
| v := blocking(c) |
| return v // line 18 |
| } |
| |
| func notBlocking(c chan bool) bool { |
| return true // line 22 |
| }`) |
| bt.assertBlocking(`blocking`) |
| bt.assertBlockingReturn(4, ``) |
| |
| bt.assertBlocking(`blockingBeforeReturn`) |
| bt.assertNotBlockingReturn(9, ``) |
| |
| bt.assertBlocking(`indirectlyBlocking`) |
| bt.assertBlockingReturn(13, ``) |
| |
| bt.assertBlocking(`indirectlyBlockingBeforeReturn`) |
| bt.assertNotBlockingReturn(18, ``) |
| |
| bt.assertNotBlocking(`notBlocking`) |
| bt.assertNotBlockingReturn(22, ``) |
| } |
| |
| func TestBlocking_Defers_WithReturnsInInstances(t *testing.T) { |
| // This is an example of a deferred function literal inside of |
| // an instance of a generic function affecting the return |
| // differently based on the type arguments of the instance. |
| bt := newBlockingTest(t, |
| `package test |
| |
| type BazBlocker struct { |
| c chan bool |
| } |
| func (bb BazBlocker) Baz() { |
| println(<-bb.c) |
| } |
| |
| type BazNotBlocker struct {} |
| func (bnb BazNotBlocker) Baz() { |
| println("hi") |
| } |
| |
| type Foo interface { Baz() } |
| func FooBaz[T Foo]() bool { |
| defer func() { // line 17 |
| var foo T |
| foo.Baz() |
| }() |
| return true // line 21 |
| } |
| |
| func main() { |
| FooBaz[BazBlocker]() |
| FooBaz[BazNotBlocker]() |
| }`) |
| |
| bt.assertFuncInstCount(5) |
| bt.assertBlocking(`BazBlocker.Baz`) |
| bt.assertNotBlocking(`BazNotBlocker.Baz`) |
| bt.assertBlockingInst(`pkg/test.FooBaz<pkg/test.BazBlocker>`) |
| bt.assertNotBlockingInst(`pkg/test.FooBaz<pkg/test.BazNotBlocker>`) |
| bt.assertBlocking(`main`) |
| |
| bt.assertFuncLitCount(2) |
| bt.assertBlockingLit(17, `pkg/test.BazBlocker`) |
| bt.assertNotBlockingLit(17, `pkg/test.BazNotBlocker`) |
| |
| bt.assertBlockingReturn(21, `pkg/test.BazBlocker`) |
| bt.assertNotBlockingReturn(21, `pkg/test.BazNotBlocker`) |
| } |
| |
| func TestBlocking_Defers_WithReturnsAndOtherPackages(t *testing.T) { |
| otherSrc := `package other |
| |
| func Blocking() { |
| c := make(chan int) |
| println(<-c) |
| } |
| |
| func NotBlocking() { |
| println("Hello") |
| }` |
| |
| testSrc := `package test |
| |
| import "pkg/other" |
| |
| func deferOtherBlocking() bool { |
| defer other.Blocking() |
| return true // line 7 |
| } |
| |
| func deferOtherNotBlocking() bool { |
| defer other.NotBlocking() |
| return true // line 12 |
| }` |
| |
| bt := newBlockingTestWithOtherPackage(t, testSrc, otherSrc) |
| |
| bt.assertBlocking(`deferOtherBlocking`) |
| bt.assertBlockingReturn(7, ``) |
| |
| bt.assertNotBlocking(`deferOtherNotBlocking`) |
| bt.assertNotBlockingReturn(12, ``) |
| } |
| |
| func TestBlocking_FunctionLiteral(t *testing.T) { |
| // See: https://github.com/gopherjs/gopherjs/issues/955. |
| bt := newBlockingTest(t, |
| `package test |
| |
| func blocking() { |
| c := make(chan bool) |
| <-c |
| } |
| |
| func indirectlyBlocking() { |
| func() { blocking() }() // line 9 |
| } |
| |
| func directlyBlocking() { |
| func() { // line 13 |
| c := make(chan bool) |
| <-c |
| }() |
| } |
| |
| func notBlocking() { |
| func() { println() } () // line 20 |
| }`) |
| bt.assertBlocking(`blocking`) |
| |
| bt.assertBlocking(`indirectlyBlocking`) |
| bt.assertBlockingLit(9, ``) |
| |
| bt.assertBlocking(`directlyBlocking`) |
| bt.assertBlockingLit(13, ``) |
| |
| bt.assertNotBlocking(`notBlocking`) |
| bt.assertNotBlockingLit(20, ``) |
| } |
| |
| func TestBlocking_LinkedFunction(t *testing.T) { |
| bt := newBlockingTest(t, |
| `package test |
| |
| // linked to some other function |
| func blocking() |
| |
| func indirectlyBlocking() { |
| blocking() |
| }`) |
| bt.assertBlocking(`blocking`) |
| bt.assertBlocking(`indirectlyBlocking`) |
| } |
| |
| func TestBlocking_Instances_WithSingleTypeArg(t *testing.T) { |
| bt := newBlockingTest(t, |
| `package test |
| |
| func blocking[T any]() { |
| c := make(chan T) |
| <-c |
| } |
| |
| func notBlocking[T any]() { |
| var v T |
| println(v) |
| } |
| |
| func bInt() { |
| blocking[int]() |
| } |
| |
| func nbUint() { |
| notBlocking[uint]() |
| }`) |
| bt.assertFuncInstCount(4) |
| // blocking and notBlocking as generics do not have FuncInfo, |
| // only non-generic and instances have FuncInfo. |
| |
| bt.assertBlockingInst(`pkg/test.blocking<int>`) |
| bt.assertBlocking(`bInt`) |
| bt.assertNotBlockingInst(`pkg/test.notBlocking<uint>`) |
| bt.assertNotBlocking(`nbUint`) |
| } |
| |
| func TestBlocking_Instances_WithMultipleTypeArgs(t *testing.T) { |
| bt := newBlockingTest(t, |
| `package test |
| |
| func blocking[K comparable, V any, M ~map[K]V]() { |
| c := make(chan M) |
| <-c |
| } |
| |
| func notBlocking[K comparable, V any, M ~map[K]V]() { |
| var m M |
| println(m) |
| } |
| |
| func bInt() { |
| blocking[string, int, map[string]int]() |
| } |
| |
| func nbUint() { |
| notBlocking[string, uint, map[string]uint]() |
| }`) |
| bt.assertFuncInstCount(4) |
| // blocking and notBlocking as generics do not have FuncInfo, |
| // only non-generic and instances have FuncInfo. |
| |
| bt.assertBlockingInst(`pkg/test.blocking<string, int, map[string]int>`) |
| bt.assertBlocking(`bInt`) |
| bt.assertNotBlockingInst(`pkg/test.notBlocking<string, uint, map[string]uint>`) |
| bt.assertNotBlocking(`nbUint`) |
| } |
| |
| func TestBlocking_Indexed_FunctionSlice(t *testing.T) { |
| // This calls notBlocking but since the function pointers |
| // are in the slice they will both be considered as blocking. |
| bt := newBlockingTest(t, |
| `package test |
| |
| func blocking() { |
| c := make(chan int) |
| <-c |
| } |
| |
| func notBlocking() { |
| println() |
| } |
| |
| var funcs = []func() { blocking, notBlocking } |
| |
| func indexer(i int) { |
| funcs[i]() |
| }`) |
| bt.assertBlocking(`blocking`) |
| bt.assertBlocking(`indexer`) |
| bt.assertNotBlocking(`notBlocking`) |
| } |
| |
| func TestBlocking_Indexed_FunctionMap(t *testing.T) { |
| // This calls notBlocking but since the function pointers |
| // are in the map they will both be considered as blocking. |
| bt := newBlockingTest(t, |
| `package test |
| |
| func blocking() { |
| c := make(chan int) |
| <-c |
| } |
| |
| func notBlocking() { |
| println() |
| } |
| |
| var funcs = map[string]func() { |
| "b": blocking, |
| "nb": notBlocking, |
| } |
| |
| func indexer(key string) { |
| funcs[key]() |
| }`) |
| bt.assertBlocking(`blocking`) |
| bt.assertBlocking(`indexer`) |
| bt.assertNotBlocking(`notBlocking`) |
| } |
| |
| func TestBlocking_Indexed_FunctionArray(t *testing.T) { |
| // This calls notBlocking but since the function pointers |
| // are in the array they will both be considered as blocking. |
| bt := newBlockingTest(t, |
| `package test |
| |
| func blocking() { |
| c := make(chan int) |
| <-c |
| } |
| |
| func notBlocking() { |
| println() |
| } |
| |
| var funcs = [2]func() { blocking, notBlocking } |
| |
| func indexer(i int) { |
| funcs[i]() |
| }`) |
| bt.assertBlocking(`blocking`) |
| bt.assertBlocking(`indexer`) |
| bt.assertNotBlocking(`notBlocking`) |
| } |
| |
| func TestBlocking_Casting_InterfaceInstanceWithSingleTypeParam(t *testing.T) { |
| // This checks that casting to an instance type with a single type parameter |
| // is treated as a cast and not accidentally treated as a function call. |
| bt := newBlockingTest(t, |
| `package test |
| |
| type Foo[T any] interface { |
| Baz() T |
| } |
| |
| type Bar struct { |
| name string |
| } |
| |
| func (b Bar) Baz() string { |
| return b.name |
| } |
| |
| func caster() Foo[string] { |
| b := Bar{name: "foo"} |
| return Foo[string](b) |
| }`) |
| bt.assertNotBlocking(`caster`) |
| } |
| |
| func TestBlocking_Casting_InterfaceInstanceWithMultipleTypeParams(t *testing.T) { |
| // This checks that casting to an instance type with multiple type parameters |
| // is treated as a cast and not accidentally treated as a function call. |
| bt := newBlockingTest(t, |
| `package test |
| |
| type Foo[K comparable, V any] interface { |
| Baz(K) V |
| } |
| |
| type Bar struct { |
| dat map[string]int |
| } |
| |
| func (b Bar) Baz(key string) int { |
| return b.dat[key] |
| } |
| |
| func caster() Foo[string, int] { |
| b := Bar{ dat: map[string]int{ "foo": 2 }} |
| return Foo[string, int](b) |
| }`) |
| bt.assertNotBlocking(`caster`) |
| } |
| |
| func TestBlocking_Casting_Interface(t *testing.T) { |
| // This checks that non-generic casting of type is treated as a |
| // cast and not accidentally treated as a function call. |
| bt := newBlockingTest(t, |
| `package test |
| |
| type Foo interface { |
| Baz() string |
| } |
| |
| type Bar struct { |
| name string |
| } |
| |
| func (b Bar) Baz() string { |
| return b.name |
| } |
| |
| func caster() Foo { |
| b := Bar{"foo"} |
| return Foo(b) |
| }`) |
| bt.assertNotBlocking(`caster`) |
| } |
| |
| func TestBlocking_ComplexCasting(t *testing.T) { |
| // This checks a complex casting to a type is treated as a |
| // cast and not accidentally treated as a function call. |
| bt := newBlockingTest(t, |
| `package test |
| |
| type Foo interface { |
| Bar() string |
| } |
| |
| func doNothing(f Foo) Foo { |
| return interface{ Bar() string }(f) |
| }`) |
| bt.assertNotBlocking(`doNothing`) |
| } |
| |
| func TestBlocking_ComplexCall(t *testing.T) { |
| // This checks a complex call of a function is defaulted to blocking. |
| bt := newBlockingTest(t, |
| `package test |
| |
| type Foo func() string |
| |
| func bar(f any) string { |
| return f.(Foo)() |
| }`) |
| bt.assertBlocking(`bar`) |
| } |
| |
| func TestBlocking_CallWithNamedInterfaceReceiver(t *testing.T) { |
| // This checks that calling a named interface function is defaulted to blocking. |
| bt := newBlockingTest(t, |
| `package test |
| |
| type Foo interface { |
| Baz() |
| } |
| |
| func bar(f Foo) { |
| f.Baz() |
| }`) |
| bt.assertBlocking(`bar`) |
| } |
| |
| func TestBlocking_CallWithUnnamedInterfaceReceiver(t *testing.T) { |
| // This checks that calling an unnamed interface function is defaulted to blocking. |
| bt := newBlockingTest(t, |
| `package test |
| |
| func bar(f interface { Baz() }) { |
| f.Baz() |
| }`) |
| bt.assertBlocking(`bar`) |
| } |
| |
| func TestBlocking_VarFunctionCall(t *testing.T) { |
| // This checks that calling a function in a var is defaulted to blocking. |
| bt := newBlockingTest(t, |
| `package test |
| |
| var foo = func() { // line 3 |
| println("hi") |
| } |
| |
| func bar() { |
| foo() |
| }`) |
| bt.assertNotBlockingLit(3, ``) |
| bt.assertBlocking(`bar`) |
| } |
| |
| func TestBlocking_FieldFunctionCallOnNamed(t *testing.T) { |
| // This checks that calling a function in a field is defaulted to blocking. |
| // This should be the same as the previous test but with a field since |
| // all function pointers are treated as blocking. |
| bt := newBlockingTest(t, |
| `package test |
| |
| type foo struct { |
| Baz func() |
| } |
| |
| func bar(f foo) { |
| f.Baz() |
| }`) |
| bt.assertBlocking(`bar`) |
| } |
| |
| func TestBlocking_FieldFunctionCallOnUnnamed(t *testing.T) { |
| // Same as previous test but with an unnamed struct. |
| bt := newBlockingTest(t, |
| `package test |
| |
| func bar(f struct { Baz func() }) { |
| f.Baz() |
| }`) |
| bt.assertBlocking(`bar`) |
| } |
| |
| func TestBlocking_ParamFunctionCall(t *testing.T) { |
| // Same as previous test but with an unnamed function parameter. |
| bt := newBlockingTest(t, |
| `package test |
| |
| func bar(baz func()) { |
| baz() |
| }`) |
| bt.assertBlocking(`bar`) |
| } |
| |
| func TestBlocking_FunctionUnwrapping(t *testing.T) { |
| // Test that calling a function that calls a function etc. |
| // is defaulted to blocking. |
| bt := newBlockingTest(t, |
| `package test |
| |
| func bar(baz func()func()func()) { |
| baz()()() |
| }`) |
| bt.assertBlocking(`bar`) |
| } |
| |
| func TestBlocking_MethodCall_NonPointer(t *testing.T) { |
| // Test that calling a method on a non-pointer receiver. |
| bt := newBlockingTest(t, |
| `package test |
| |
| type Foo struct {} |
| |
| func (f Foo) blocking() { |
| ch := make(chan bool) |
| <-ch |
| } |
| |
| func (f Foo) notBlocking() { |
| println("hi") |
| } |
| |
| func blocking(f Foo) { |
| f.blocking() |
| } |
| |
| func notBlocking(f Foo) { |
| f.notBlocking() |
| }`) |
| bt.assertBlocking(`Foo.blocking`) |
| bt.assertNotBlocking(`Foo.notBlocking`) |
| bt.assertBlocking(`blocking`) |
| bt.assertNotBlocking(`notBlocking`) |
| } |
| |
| func TestBlocking_MethodCall_Pointer(t *testing.T) { |
| // Test that calling a method on a pointer receiver. |
| bt := newBlockingTest(t, |
| `package test |
| |
| type Foo struct {} |
| |
| func (f *Foo) blocking() { |
| ch := make(chan bool) |
| <-ch |
| } |
| |
| func (f *Foo) notBlocking() { |
| println("hi") |
| } |
| |
| func blocking(f *Foo) { |
| f.blocking() |
| } |
| |
| func notBlocking(f *Foo) { |
| f.notBlocking() |
| }`) |
| bt.assertBlocking(`Foo.blocking`) |
| bt.assertNotBlocking(`Foo.notBlocking`) |
| bt.assertBlocking(`blocking`) |
| bt.assertNotBlocking(`notBlocking`) |
| } |
| |
| func TestBlocking_InstantiationBlocking(t *testing.T) { |
| // This checks that the instantiation of a generic function is |
| // being used when checking for blocking not the type argument interface. |
| bt := newBlockingTest(t, |
| `package test |
| |
| type BazBlocker struct { |
| c chan bool |
| } |
| func (bb BazBlocker) Baz() { |
| println(<-bb.c) |
| } |
| |
| type BazNotBlocker struct {} |
| func (bnb BazNotBlocker) Baz() { |
| println("hi") |
| } |
| |
| type Foo interface { Baz() } |
| func FooBaz[T Foo](foo T) { |
| foo.Baz() |
| } |
| |
| func blockingViaExplicit() { |
| FooBaz[BazBlocker](BazBlocker{c: make(chan bool)}) |
| } |
| |
| func notBlockingViaExplicit() { |
| FooBaz[BazNotBlocker](BazNotBlocker{}) |
| } |
| |
| func blockingViaImplicit() { |
| FooBaz(BazBlocker{c: make(chan bool)}) |
| } |
| |
| func notBlockingViaImplicit() { |
| FooBaz(BazNotBlocker{}) |
| }`) |
| bt.assertFuncInstCount(8) |
| // `FooBaz` as a generic function does not have FuncInfo for it, |
| // only non-generic or instantiations of a generic functions have FuncInfo. |
| |
| bt.assertBlocking(`BazBlocker.Baz`) |
| bt.assertBlocking(`blockingViaExplicit`) |
| bt.assertBlocking(`blockingViaImplicit`) |
| bt.assertBlockingInst(`pkg/test.FooBaz<pkg/test.BazBlocker>`) |
| |
| bt.assertNotBlocking(`BazNotBlocker.Baz`) |
| bt.assertNotBlocking(`notBlockingViaExplicit`) |
| bt.assertNotBlocking(`notBlockingViaImplicit`) |
| bt.assertNotBlockingInst(`pkg/test.FooBaz<pkg/test.BazNotBlocker>`) |
| } |
| |
| func TestBlocking_NestedInstantiations(t *testing.T) { |
| // Checking that the type parameters are being propagated down into calls. |
| bt := newBlockingTest(t, |
| `package test |
| |
| func Foo[T any](t T) { |
| println(t) |
| } |
| |
| func Bar[K comparable, V any, M ~map[K]V](m M) { |
| Foo(m) |
| } |
| |
| func Baz[T any, S ~[]T](s S) { |
| m:= map[int]T{} |
| for i, v := range s { |
| m[i] = v |
| } |
| Bar(m) |
| } |
| |
| func bazInt() { |
| Baz([]int{1, 2, 3}) |
| } |
| |
| func bazString() { |
| Baz([]string{"one", "two", "three"}) |
| }`) |
| bt.assertFuncInstCount(8) |
| bt.assertNotBlocking(`bazInt`) |
| bt.assertNotBlocking(`bazString`) |
| bt.assertNotBlockingInst(`pkg/test.Foo<map[int]int>`) |
| bt.assertNotBlockingInst(`pkg/test.Foo<map[int]string>`) |
| bt.assertNotBlockingInst(`pkg/test.Bar<int, int, map[int]int>`) |
| bt.assertNotBlockingInst(`pkg/test.Bar<int, string, map[int]string>`) |
| bt.assertNotBlockingInst(`pkg/test.Baz<int, []int>`) |
| bt.assertNotBlockingInst(`pkg/test.Baz<string, []string>`) |
| } |
| |
| func TestBlocking_UnusedGenericFunctions(t *testing.T) { |
| // Checking that the type parameters are being propagated down into callee. |
| // This is based off of go1.19.13/test/typeparam/orderedmap.go |
| bt := newBlockingTest(t, |
| `package test |
| |
| type node[K, V any] struct { |
| key K |
| val V |
| left, right *node[K, V] |
| } |
| |
| type Tree[K, V any] struct { |
| root *node[K, V] |
| eq func(K, K) bool |
| } |
| |
| func New[K, V any](eq func(K, K) bool) *Tree[K, V] { |
| return &Tree[K, V]{eq: eq} |
| } |
| |
| func NewStrKey[K ~string, V any]() *Tree[K, V] { // unused |
| return New[K, V](func(k1, k2 K) bool { |
| return string(k1) == string(k2) |
| }) |
| } |
| |
| func NewStrStr[V any]() *Tree[string, V] { // unused |
| return NewStrKey[string, V]() |
| } |
| |
| func main() { |
| t := New[int, string](func(k1, k2 int) bool { |
| return k1 == k2 |
| }) |
| println(t) |
| }`) |
| bt.assertFuncInstCount(2) |
| // Notice that `NewStrKey` and `NewStrStr` are not called so doesn't have |
| // any known instances and therefore they don't have any FuncInfos. |
| bt.assertNotBlockingInst(`pkg/test.New<int, string>`) |
| bt.assertNotBlocking(`main`) |
| } |
| |
| func TestBlocking_LitInstanceCalls(t *testing.T) { |
| // Literals defined inside a generic function must inherit the |
| // type arguments (resolver) of the enclosing instance it is defined in |
| // so that things like calls to other generic functions create the |
| // call to the correct concrete instance. |
| bt := newBlockingTest(t, |
| `package test |
| |
| func foo[T any](x T) { |
| println(x) |
| } |
| |
| func bar[T any](x T) { |
| f := func(v T) { // line 8 |
| foo[T](v) |
| } |
| f(x) |
| } |
| |
| func main() { |
| bar[int](42) |
| bar[float64](3.14) |
| }`) |
| bt.assertFuncInstCount(5) |
| |
| bt.assertNotBlockingInst(`pkg/test.foo<int>`) |
| bt.assertNotBlockingInst(`pkg/test.foo<float64>`) |
| bt.assertNotBlockingLit(8, `int`) |
| bt.assertNotBlockingLit(8, `float64`) |
| // The following are blocking because the function literal call. |
| bt.assertBlockingInst(`pkg/test.bar<int>`) |
| bt.assertBlockingInst(`pkg/test.bar<float64>`) |
| } |
| |
| func TestBlocking_BlockingLitInstance(t *testing.T) { |
| bt := newBlockingTest(t, |
| `package test |
| |
| type BazBlocker struct { |
| c chan bool |
| } |
| func (bb BazBlocker) Baz() { |
| println(<-bb.c) |
| } |
| |
| type BazNotBlocker struct {} |
| func (bnb BazNotBlocker) Baz() { |
| println("hi") |
| } |
| |
| type Foo interface { Baz() } |
| func FooBaz[T Foo](foo T) func() { |
| return func() { // line 17 |
| foo.Baz() |
| } |
| } |
| |
| func main() { |
| _ = FooBaz(BazBlocker{}) |
| _ = FooBaz(BazNotBlocker{}) |
| }`) |
| bt.assertFuncInstCount(5) |
| |
| bt.assertBlocking(`BazBlocker.Baz`) |
| // THe following is not blocking because the function literal is not called. |
| bt.assertNotBlockingInst(`pkg/test.FooBaz<pkg/test.BazBlocker>`) |
| bt.assertBlockingLit(17, `pkg/test.BazBlocker`) |
| |
| bt.assertNotBlocking(`BazNotBlocker.Baz`) |
| bt.assertNotBlockingInst(`pkg/test.FooBaz<pkg/test.BazNotBlocker>`) |
| bt.assertNotBlockingLit(17, `pkg/test.BazNotBlocker`) |
| } |
| |
| func TestBlocking_MethodSelection(t *testing.T) { |
| // This tests method selection using method expression (receiver as the first |
| // argument) selecting on type and method call selecting on a variable. |
| // This tests in both generic (FooBaz[T]) and non-generic contexts. |
| bt := newBlockingTest(t, |
| `package test |
| |
| type Foo interface { Baz() } |
| |
| type BazBlocker struct { |
| c chan bool |
| } |
| func (bb BazBlocker) Baz() { |
| println(<-bb.c) |
| } |
| |
| type BazNotBlocker struct {} |
| func (bnb BazNotBlocker) Baz() { |
| println("hi") |
| } |
| |
| type FooBaz[T Foo] struct {} |
| func (fb FooBaz[T]) ByMethodExpression() { |
| var foo T |
| T.Baz(foo) |
| } |
| func (fb FooBaz[T]) ByInstance() { |
| var foo T |
| foo.Baz() |
| } |
| |
| func blocking() { |
| fb := FooBaz[BazBlocker]{} |
| |
| FooBaz[BazBlocker].ByMethodExpression(fb) |
| FooBaz[BazBlocker].ByInstance(fb) |
| |
| fb.ByMethodExpression() |
| fb.ByInstance() |
| } |
| |
| func notBlocking() { |
| fb := FooBaz[BazNotBlocker]{} |
| |
| FooBaz[BazNotBlocker].ByMethodExpression(fb) |
| FooBaz[BazNotBlocker].ByInstance(fb) |
| |
| fb.ByMethodExpression() |
| fb.ByInstance() |
| }`) |
| bt.assertFuncInstCount(8) |
| |
| bt.assertBlocking(`BazBlocker.Baz`) |
| bt.assertBlockingInst(`pkg/test.FooBaz.ByMethodExpression<pkg/test.BazBlocker>`) |
| bt.assertBlockingInst(`pkg/test.FooBaz.ByInstance<pkg/test.BazBlocker>`) |
| bt.assertBlocking(`blocking`) |
| |
| bt.assertNotBlocking(`BazNotBlocker.Baz`) |
| bt.assertNotBlockingInst(`pkg/test.FooBaz.ByMethodExpression<pkg/test.BazNotBlocker>`) |
| bt.assertNotBlockingInst(`pkg/test.FooBaz.ByInstance<pkg/test.BazNotBlocker>`) |
| bt.assertNotBlocking(`notBlocking`) |
| } |
| |
| func TestBlocking_IsImportBlocking_Simple(t *testing.T) { |
| otherSrc := `package other |
| |
| func Blocking() { |
| ch := make(chan bool) |
| <-ch |
| } |
| |
| func NotBlocking() { |
| println("hi") |
| }` |
| |
| testSrc := `package test |
| |
| import "pkg/other" |
| |
| func blocking() { |
| other.Blocking() |
| } |
| |
| func notBlocking() { |
| other.NotBlocking() |
| }` |
| |
| bt := newBlockingTestWithOtherPackage(t, testSrc, otherSrc) |
| bt.assertBlocking(`blocking`) |
| bt.assertNotBlocking(`notBlocking`) |
| } |
| |
| func TestBlocking_IsImportBlocking_ForwardInstances(t *testing.T) { |
| otherSrc := `package other |
| |
| type BazBlocker struct { |
| c chan bool |
| } |
| func (bb BazBlocker) Baz() { |
| println(<-bb.c) |
| } |
| |
| type BazNotBlocker struct {} |
| func (bnb BazNotBlocker) Baz() { |
| println("hi") |
| }` |
| |
| testSrc := `package test |
| |
| import "pkg/other" |
| |
| type Foo interface { Baz() } |
| func FooBaz[T Foo](f T) { |
| f.Baz() |
| } |
| |
| func blocking() { |
| FooBaz(other.BazBlocker{}) |
| } |
| |
| func notBlocking() { |
| FooBaz(other.BazNotBlocker{}) |
| }` |
| |
| bt := newBlockingTestWithOtherPackage(t, testSrc, otherSrc) |
| bt.assertBlocking(`blocking`) |
| bt.assertNotBlocking(`notBlocking`) |
| } |
| |
| func TestBlocking_IsImportBlocking_BackwardInstances(t *testing.T) { |
| t.Skip(`isImportedBlocking doesn't fully handle instances yet`) |
| // TODO(grantnelson-wf): This test is currently failing because the info |
| // for the test package is need while creating the instances for FooBaz |
| // while analyzing the other package. However the other package is analyzed |
| // first since the test package is dependent on it. One possible fix is that |
| // we add some mechanism similar to the localInstCallees but for remote |
| // instances then perform the blocking propagation steps for all packages |
| // including the localInstCallees propagation at the same time. After all the |
| // propagation of the calls then the flow control statements can be marked. |
| |
| otherSrc := `package other |
| |
| type Foo interface { Baz() } |
| func FooBaz[T Foo](f T) { |
| f.Baz() |
| }` |
| |
| testSrc := `package test |
| |
| import "pkg/other" |
| |
| type BazBlocker struct { |
| c chan bool |
| } |
| func (bb BazBlocker) Baz() { |
| println(<-bb.c) |
| } |
| |
| type BazNotBlocker struct {} |
| func (bnb BazNotBlocker) Baz() { |
| println("hi") |
| } |
| |
| func blocking() { |
| other.FooBaz(BazBlocker{}) |
| } |
| |
| func notBlocking() { |
| other.FooBaz(BazNotBlocker{}) |
| }` |
| |
| bt := newBlockingTestWithOtherPackage(t, testSrc, otherSrc) |
| bt.assertBlocking(`blocking`) |
| bt.assertNotBlocking(`notBlocking`) |
| } |
| |
| type blockingTest struct { |
| f *srctesting.Fixture |
| file *ast.File |
| pkgInfo *Info |
| } |
| |
| func newBlockingTest(t *testing.T, src string) *blockingTest { |
| f := srctesting.New(t) |
| tc := typeparams.Collector{ |
| TContext: types.NewContext(), |
| Info: f.Info, |
| Instances: &typeparams.PackageInstanceSets{}, |
| } |
| |
| file := f.Parse(`test.go`, src) |
| testInfo, testPkg := f.Check(`pkg/test`, file) |
| tc.Scan(testPkg, file) |
| |
| isImportBlocking := func(i typeparams.Instance) bool { |
| t.Fatalf(`isImportBlocking should not be called in this test, called with %v`, i) |
| return true |
| } |
| pkgInfo := AnalyzePkg([]*ast.File{file}, f.FileSet, testInfo, types.NewContext(), testPkg, tc.Instances, isImportBlocking) |
| |
| return &blockingTest{ |
| f: f, |
| file: file, |
| pkgInfo: pkgInfo, |
| } |
| } |
| |
| func newBlockingTestWithOtherPackage(t *testing.T, testSrc string, otherSrc string) *blockingTest { |
| f := srctesting.New(t) |
| tc := typeparams.Collector{ |
| TContext: types.NewContext(), |
| Info: f.Info, |
| Instances: &typeparams.PackageInstanceSets{}, |
| } |
| |
| pkgInfo := map[*types.Package]*Info{} |
| isImportBlocking := func(i typeparams.Instance) bool { |
| if info, ok := pkgInfo[i.Object.Pkg()]; ok { |
| return info.IsBlocking(i) |
| } |
| t.Fatalf(`unexpected package in isImportBlocking for %v`, i) |
| return true |
| } |
| |
| otherFile := f.Parse(`other.go`, otherSrc) |
| _, otherPkg := f.Check(`pkg/other`, otherFile) |
| tc.Scan(otherPkg, otherFile) |
| |
| testFile := f.Parse(`test.go`, testSrc) |
| _, testPkg := f.Check(`pkg/test`, testFile) |
| tc.Scan(testPkg, testFile) |
| |
| otherPkgInfo := AnalyzePkg([]*ast.File{otherFile}, f.FileSet, f.Info, types.NewContext(), otherPkg, tc.Instances, isImportBlocking) |
| pkgInfo[otherPkg] = otherPkgInfo |
| |
| testPkgInfo := AnalyzePkg([]*ast.File{testFile}, f.FileSet, f.Info, types.NewContext(), testPkg, tc.Instances, isImportBlocking) |
| pkgInfo[testPkg] = testPkgInfo |
| |
| return &blockingTest{ |
| f: f, |
| file: testFile, |
| pkgInfo: testPkgInfo, |
| } |
| } |
| |
| func (bt *blockingTest) assertFuncInstCount(expCount int) { |
| bt.f.T.Helper() |
| if got := bt.pkgInfo.funcInstInfos.Len(); got != expCount { |
| bt.f.T.Errorf(`Got %d function instance infos but expected %d.`, got, expCount) |
| for i, inst := range bt.pkgInfo.funcInstInfos.Keys() { |
| bt.f.T.Logf(` %d. %q`, i+1, inst.String()) |
| } |
| } |
| } |
| |
| func (bt *blockingTest) assertFuncLitCount(expCount int) { |
| bt.f.T.Helper() |
| got := 0 |
| for _, fis := range bt.pkgInfo.funcLitInfos { |
| got += len(fis) |
| } |
| if got != expCount { |
| bt.f.T.Errorf(`Got %d function literal infos but expected %d.`, got, expCount) |
| |
| lits := make([]string, 0, len(bt.pkgInfo.funcLitInfos)) |
| for fl, fis := range bt.pkgInfo.funcLitInfos { |
| pos := bt.f.FileSet.Position(fl.Pos()).String() |
| for _, fi := range fis { |
| lits = append(lits, pos+`<`+fi.typeArgs.String()+`>`) |
| } |
| } |
| sort.Strings(lits) |
| for i := range lits { |
| bt.f.T.Logf(` %d. %q`, i+1, lits[i]) |
| } |
| } |
| } |
| |
| func (bt *blockingTest) assertBlocking(funcName string) { |
| bt.f.T.Helper() |
| if !bt.isTypesFuncBlocking(funcName) { |
| bt.f.T.Errorf(`Got %q as not blocking but expected it to be blocking.`, funcName) |
| } |
| } |
| |
| func (bt *blockingTest) assertNotBlocking(funcName string) { |
| bt.f.T.Helper() |
| if bt.isTypesFuncBlocking(funcName) { |
| bt.f.T.Errorf(`Got %q as blocking but expected it to be not blocking.`, funcName) |
| } |
| } |
| |
| func getFuncDeclName(fd *ast.FuncDecl) string { |
| name := fd.Name.Name |
| if fd.Recv != nil && len(fd.Recv.List) == 1 && fd.Recv.List[0].Type != nil { |
| typ := fd.Recv.List[0].Type |
| if p, ok := typ.(*ast.StarExpr); ok { |
| typ = p.X |
| } |
| if id, ok := typ.(*ast.Ident); ok { |
| name = id.Name + `.` + name |
| } |
| } |
| return name |
| } |
| |
| func (bt *blockingTest) isTypesFuncBlocking(funcName string) bool { |
| bt.f.T.Helper() |
| var decl *ast.FuncDecl |
| ast.Inspect(bt.file, func(n ast.Node) bool { |
| if f, ok := n.(*ast.FuncDecl); ok && getFuncDeclName(f) == funcName { |
| decl = f |
| return false |
| } |
| return decl == nil |
| }) |
| |
| if decl == nil { |
| bt.f.T.Fatalf(`Declaration of %q is not found in the AST.`, funcName) |
| } |
| |
| blockingType, ok := bt.pkgInfo.Defs[decl.Name] |
| if !ok { |
| bt.f.T.Fatalf(`No function declaration found for %q.`, decl.Name) |
| } |
| |
| inst := typeparams.Instance{Object: blockingType.(*types.Func)} |
| return bt.pkgInfo.IsBlocking(inst) |
| } |
| |
| func (bt *blockingTest) assertBlockingLit(lineNo int, typeArgsStr string) { |
| bt.f.T.Helper() |
| if !bt.isFuncLitBlocking(lineNo, typeArgsStr) { |
| bt.f.T.Errorf(`Got FuncLit at line %d with type args %q as not blocking but expected it to be blocking.`, lineNo, typeArgsStr) |
| } |
| } |
| |
| func (bt *blockingTest) assertNotBlockingLit(lineNo int, typeArgsStr string) { |
| bt.f.T.Helper() |
| if bt.isFuncLitBlocking(lineNo, typeArgsStr) { |
| bt.f.T.Errorf(`Got FuncLit at line %d with type args %q as blocking but expected it to be not blocking.`, lineNo, typeArgsStr) |
| } |
| } |
| |
| func (bt *blockingTest) isFuncLitBlocking(lineNo int, typeArgsStr string) bool { |
| bt.f.T.Helper() |
| fnLit := srctesting.GetNodeAtLineNo[*ast.FuncLit](bt.file, bt.f.FileSet, lineNo) |
| if fnLit == nil { |
| bt.f.T.Fatalf(`FuncLit on line %d not found in the AST.`, lineNo) |
| } |
| |
| fis, ok := bt.pkgInfo.funcLitInfos[fnLit] |
| if !ok { |
| bt.f.T.Fatalf(`No FuncInfo found for FuncLit at line %d.`, lineNo) |
| } |
| |
| for _, fi := range fis { |
| if fi.typeArgs.String() == typeArgsStr { |
| return fi.IsBlocking() |
| } |
| } |
| |
| bt.f.T.Logf("FuncList instances:") |
| for i, fi := range fis { |
| bt.f.T.Logf("\t%d. %q\n", i+1, fi.typeArgs.String()) |
| } |
| bt.f.T.Fatalf(`No FuncInfo found for FuncLit at line %d with type args %q.`, lineNo, typeArgsStr) |
| return false |
| } |
| |
| func (bt *blockingTest) assertBlockingInst(instanceStr string) { |
| bt.f.T.Helper() |
| if !bt.isFuncInstBlocking(instanceStr) { |
| bt.f.T.Errorf(`Got function instance of %q as not blocking but expected it to be blocking.`, instanceStr) |
| } |
| } |
| |
| func (bt *blockingTest) assertNotBlockingInst(instanceStr string) { |
| bt.f.T.Helper() |
| if bt.isFuncInstBlocking(instanceStr) { |
| bt.f.T.Errorf(`Got function instance of %q as blocking but expected it to be not blocking.`, instanceStr) |
| } |
| } |
| |
| func (bt *blockingTest) isFuncInstBlocking(instanceStr string) bool { |
| bt.f.T.Helper() |
| instances := bt.pkgInfo.funcInstInfos.Keys() |
| for _, inst := range instances { |
| if inst.String() == instanceStr { |
| return bt.pkgInfo.FuncInfo(inst).IsBlocking() |
| } |
| } |
| bt.f.T.Logf(`Function instances found in package info:`) |
| for i, inst := range instances { |
| bt.f.T.Logf("\t%d. %s", i+1, inst.String()) |
| } |
| bt.f.T.Fatalf(`No function instance found for %q in package info.`, instanceStr) |
| return false |
| } |
| |
| func (bt *blockingTest) assertBlockingReturn(lineNo int, typeArgsStr string) { |
| bt.f.T.Helper() |
| if !bt.isReturnBlocking(lineNo, typeArgsStr) { |
| bt.f.T.Errorf(`Got return at line %d (%q) as not blocking but expected it to be blocking.`, lineNo, typeArgsStr) |
| } |
| } |
| |
| func (bt *blockingTest) assertNotBlockingReturn(lineNo int, typeArgsStr string) { |
| bt.f.T.Helper() |
| if bt.isReturnBlocking(lineNo, typeArgsStr) { |
| bt.f.T.Errorf(`Got return at line %d (%q) as blocking but expected it to be not blocking.`, lineNo, typeArgsStr) |
| } |
| } |
| |
| func (bt *blockingTest) isReturnBlocking(lineNo int, typeArgsStr string) bool { |
| bt.f.T.Helper() |
| ret := srctesting.GetNodeAtLineNo[*ast.ReturnStmt](bt.file, bt.f.FileSet, lineNo) |
| if ret == nil { |
| bt.f.T.Fatalf(`ReturnStmt on line %d not found in the AST.`, lineNo) |
| } |
| |
| foundInfo := []*FuncInfo{} |
| for _, info := range bt.pkgInfo.allInfos { |
| for _, rs := range info.returnStmts { |
| if rs.analyzeStack[len(rs.analyzeStack)-1] == ret { |
| if info.typeArgs.String() == typeArgsStr { |
| // Found info that matches the type args and |
| // has the return statement so return the blocking value. |
| return info.Blocking[ret] |
| } |
| |
| // Wrong instance, record for error message in the case |
| // that the correct one instance is not found. |
| foundInfo = append(foundInfo, info) |
| break |
| } |
| } |
| } |
| |
| bt.f.T.Logf("FuncInfo instances with ReturnStmt at line %d:", lineNo) |
| for i, info := range foundInfo { |
| bt.f.T.Logf("\t%d. %q\n", i+1, info.typeArgs.String()) |
| } |
| bt.f.T.Fatalf(`No FuncInfo found for ReturnStmt at line %d with type args %q.`, lineNo, typeArgsStr) |
| return false |
| } |