From 4068ae5c2d1e70a504562338f1a18207124b6691 Mon Sep 17 00:00:00 2001 From: Li Jie Date: Sun, 24 May 2026 21:00:07 +0800 Subject: [PATCH] fix zero-sized deref comparisons --- cl/_testgo/genericembediface/in.go | 30 ++++- cl/_testgo/ifaceconv/in.go | 188 ++++++++++++++++++----------- cl/_testgo/selects/in.go | 106 ++++++++++------ cl/_testgo/tpnamed/in.go | 102 ++++++++++++---- cl/_testrt/closurebound/in.go | 77 ++++++++---- cl/compile.go | 5 + cl/zero_size_deref_test.go | 25 ++++ ssa/expr.go | 3 + ssa/memory.go | 4 + ssa/ssa_test.go | 27 +++++ test/go/zero_size_compare_test.go | 66 ++++++++++ test/goroot/xfail.yaml | 19 --- 12 files changed, 475 insertions(+), 177 deletions(-) create mode 100644 cl/zero_size_deref_test.go create mode 100644 test/go/zero_size_compare_test.go diff --git a/cl/_testgo/genericembediface/in.go b/cl/_testgo/genericembediface/in.go index 70d5427492..4f859a3709 100644 --- a/cl/_testgo/genericembediface/in.go +++ b/cl/_testgo/genericembediface/in.go @@ -52,6 +52,20 @@ type ReflectionServer interface { // CHECK-NEXT: unreachable // CHECK-NEXT: } +// CHECK-LABEL: define void @"{{.*}}/cl/_testgo/genericembediface.init"(){{.*}} { +// CHECK-NEXT: _llgo_0: +// CHECK-NEXT: %0 = load i1, ptr @"{{.*}}/cl/_testgo/genericembediface.init$guard", align 1 +// CHECK-NEXT: br i1 %0, label %_llgo_2, label %_llgo_1 +// CHECK-EMPTY: +// CHECK-NEXT: _llgo_1: ; preds = %_llgo_0 +// CHECK-NEXT: store i1 true, ptr @"{{.*}}/cl/_testgo/genericembediface.init$guard", align 1 +// CHECK-NEXT: call void @"{{.*}}/cl/_testgo/genericembediface/streamlib.init"() +// CHECK-NEXT: br label %_llgo_2 +// CHECK-EMPTY: +// CHECK-NEXT: _llgo_2: ; preds = %_llgo_1, %_llgo_0 +// CHECK-NEXT: ret void +// CHECK-NEXT: } + func handler(srv any, stream streamlib.ServerStream) error { return srv.(ReflectionServer).ServerReflectionInfo(&streamlib.GenericServerStream[Request, Response]{ServerStream: stream}) } @@ -96,8 +110,9 @@ func main() { // CHECK-LABEL: define %"{{.*}}/runtime/internal/runtime.iface" @"{{.*}}/cl/_testgo/genericembediface.(*server).ServerReflectionInfo"(ptr %0, %"{{.*}}/runtime/internal/runtime.iface" %1){{.*}} { // CHECK-NEXT: _llgo_0: -// CHECK-NEXT: %2 = load %"{{.*}}/cl/_testgo/genericembediface.server", ptr %0, align 1 -// CHECK-NEXT: %3 = call %"{{.*}}/runtime/internal/runtime.iface" @"{{.*}}/cl/_testgo/genericembediface.server.ServerReflectionInfo"(%"{{.*}}/cl/_testgo/genericembediface.server" %2, %"{{.*}}/runtime/internal/runtime.iface" %1) +// CHECK-NEXT: %2 = icmp eq ptr %0, null +// CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.AssertNilDeref"(i1 %2) +// CHECK-NEXT: %3 = call %"{{.*}}/runtime/internal/runtime.iface" @"{{.*}}/cl/_testgo/genericembediface.server.ServerReflectionInfo"(%"{{.*}}/cl/_testgo/genericembediface.server" zeroinitializer, %"{{.*}}/runtime/internal/runtime.iface" %1) // CHECK-NEXT: ret %"{{.*}}/runtime/internal/runtime.iface" %3 // CHECK-NEXT: } @@ -108,8 +123,9 @@ func main() { // CHECK-LABEL: define %"{{.*}}/runtime/internal/runtime.String" @"{{.*}}/cl/_testgo/genericembediface.(*stream).Context"(ptr %0){{.*}} { // CHECK-NEXT: _llgo_0: -// CHECK-NEXT: %1 = load %"{{.*}}/cl/_testgo/genericembediface.stream", ptr %0, align 1 -// CHECK-NEXT: %2 = call %"{{.*}}/runtime/internal/runtime.String" @"{{.*}}/cl/_testgo/genericembediface.stream.Context"(%"{{.*}}/cl/_testgo/genericembediface.stream" %1) +// CHECK-NEXT: %1 = icmp eq ptr %0, null +// CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.AssertNilDeref"(i1 %1) +// CHECK-NEXT: %2 = call %"{{.*}}/runtime/internal/runtime.String" @"{{.*}}/cl/_testgo/genericembediface.stream.Context"(%"{{.*}}/cl/_testgo/genericembediface.stream" zeroinitializer) // CHECK-NEXT: ret %"{{.*}}/runtime/internal/runtime.String" %2 // CHECK-NEXT: } @@ -153,3 +169,9 @@ func main() { // CHECK-NEXT: %12 = call %"{{.*}}/runtime/internal/runtime.String" %11(ptr %10) // CHECK-NEXT: ret %"{{.*}}/runtime/internal/runtime.String" %12 // CHECK-NEXT: } + +// CHECK-LABEL: define linkonce i1 @"__llgo_stub.{{.*}}/runtime/internal/runtime.memequal0"(ptr %0, ptr %1, ptr %2){{.*}} { +// CHECK-NEXT: _llgo_0: +// CHECK-NEXT: %3 = tail call i1 @"{{.*}}/runtime/internal/runtime.memequal0"(ptr %1, ptr %2) +// CHECK-NEXT: ret i1 %3 +// CHECK-NEXT: } diff --git a/cl/_testgo/ifaceconv/in.go b/cl/_testgo/ifaceconv/in.go index c79216e421..340e93cb85 100644 --- a/cl/_testgo/ifaceconv/in.go +++ b/cl/_testgo/ifaceconv/in.go @@ -3,6 +3,19 @@ package main // Tests of interface conversions and type assertions. +// CHECK-LINE: @2 = private unnamed_addr constant [21 x i8] c"nil i0.(I0) succeeded", align 1 +// CHECK-LINE: @7 = private unnamed_addr constant [21 x i8] c"nil i1.(I1) succeeded", align 1 +// CHECK-LINE: @10 = private unnamed_addr constant [21 x i8] c"nil i2.(I2) succeeded", align 1 +// CHECK-LINE: @14 = private unnamed_addr constant [17 x i8] c"C1 i1.(I0) failed", align 1 +// CHECK-LINE: @16 = private unnamed_addr constant [17 x i8] c"C1 i1.(I1) failed", align 1 +// CHECK-LINE: @17 = private unnamed_addr constant [20 x i8] c"C1 i1.(I2) succeeded", align 1 +// CHECK-LINE: @20 = private unnamed_addr constant [17 x i8] c"C2 i1.(I0) failed", align 1 +// CHECK-LINE: @21 = private unnamed_addr constant [17 x i8] c"C2 i1.(I1) failed", align 1 +// CHECK-LINE: @22 = private unnamed_addr constant [17 x i8] c"C2 i1.(I2) failed", align 1 +// CHECK-LINE: @23 = private unnamed_addr constant [17 x i8] c"C1 I0(i1) was nil", align 1 +// CHECK-LINE: @24 = private unnamed_addr constant [17 x i8] c"C1 I1(i1) was nil", align 1 +// CHECK-LINE: @25 = private unnamed_addr constant [4 x i8] c"pass", align 1 + type I0 interface { } type I1 interface { @@ -21,15 +34,82 @@ type C1 struct{} // CHECK-NEXT: ret void // CHECK-NEXT: } +func (C1) f() {} + +type C2 struct{} + +func (C2) f() {} +func (C2) g() {} + +func main() { + var i0 I0 + var i1 I1 + var i2 I2 + + // Nil always causes a type assertion to fail, even to the + // same type. + if _, ok := i0.(I0); ok { + panic("nil i0.(I0) succeeded") + } + if _, ok := i1.(I1); ok { + panic("nil i1.(I1) succeeded") + } + if _, ok := i2.(I2); ok { + panic("nil i2.(I2) succeeded") + } + + // Conversions can't fail, even with nil. + _ = I0(i0) + + _ = I0(i1) + _ = I1(i1) + + _ = I0(i2) + _ = I1(i2) + _ = I2(i2) + + // Non-nil type assertions pass or fail based on the concrete type. + i1 = C1{} + if _, ok := i1.(I0); !ok { + panic("C1 i1.(I0) failed") + } + if _, ok := i1.(I1); !ok { + panic("C1 i1.(I1) failed") + } + if _, ok := i1.(I2); ok { + panic("C1 i1.(I2) succeeded") + } + + i1 = C2{} + if _, ok := i1.(I0); !ok { + panic("C2 i1.(I0) failed") + } + if _, ok := i1.(I1); !ok { + panic("C2 i1.(I1) failed") + } + if _, ok := i1.(I2); !ok { + panic("C2 i1.(I2) failed") + } + + // Conversions can't fail. + i1 = C1{} + if I0(i1) == nil { + panic("C1 I0(i1) was nil") + } + if I1(i1) == nil { + panic("C1 I1(i1) was nil") + } + + println("pass") +} + // CHECK-LABEL: define void @"{{.*}}/cl/_testgo/ifaceconv.(*C1).f"(ptr %0){{.*}} { // CHECK-NEXT: _llgo_0: -// CHECK-NEXT: %1 = load %"{{.*}}/cl/_testgo/ifaceconv.C1", ptr %0, align 1 -// CHECK-NEXT: call void @"{{.*}}/cl/_testgo/ifaceconv.C1.f"(%"{{.*}}/cl/_testgo/ifaceconv.C1" %1) +// CHECK-NEXT: %1 = icmp eq ptr %0, null +// CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.AssertNilDeref"(i1 %1) +// CHECK-NEXT: call void @"{{.*}}/cl/_testgo/ifaceconv.C1.f"(%"{{.*}}/cl/_testgo/ifaceconv.C1" zeroinitializer) // CHECK-NEXT: ret void // CHECK-NEXT: } -func (C1) f() {} - -type C2 struct{} // CHECK-LABEL: define void @"{{.*}}/cl/_testgo/ifaceconv.C2.f"(%"{{.*}}/cl/_testgo/ifaceconv.C2" %0){{.*}} { // CHECK-NEXT: _llgo_0: @@ -43,19 +123,32 @@ type C2 struct{} // CHECK-LABEL: define void @"{{.*}}/cl/_testgo/ifaceconv.(*C2).f"(ptr %0){{.*}} { // CHECK-NEXT: _llgo_0: -// CHECK-NEXT: %1 = load %"{{.*}}/cl/_testgo/ifaceconv.C2", ptr %0, align 1 -// CHECK-NEXT: call void @"{{.*}}/cl/_testgo/ifaceconv.C2.f"(%"{{.*}}/cl/_testgo/ifaceconv.C2" %1) +// CHECK-NEXT: %1 = icmp eq ptr %0, null +// CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.AssertNilDeref"(i1 %1) +// CHECK-NEXT: call void @"{{.*}}/cl/_testgo/ifaceconv.C2.f"(%"{{.*}}/cl/_testgo/ifaceconv.C2" zeroinitializer) // CHECK-NEXT: ret void // CHECK-NEXT: } // CHECK-LABEL: define void @"{{.*}}/cl/_testgo/ifaceconv.(*C2).g"(ptr %0){{.*}} { // CHECK-NEXT: _llgo_0: -// CHECK-NEXT: %1 = load %"{{.*}}/cl/_testgo/ifaceconv.C2", ptr %0, align 1 -// CHECK-NEXT: call void @"{{.*}}/cl/_testgo/ifaceconv.C2.g"(%"{{.*}}/cl/_testgo/ifaceconv.C2" %1) +// CHECK-NEXT: %1 = icmp eq ptr %0, null +// CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.AssertNilDeref"(i1 %1) +// CHECK-NEXT: call void @"{{.*}}/cl/_testgo/ifaceconv.C2.g"(%"{{.*}}/cl/_testgo/ifaceconv.C2" zeroinitializer) +// CHECK-NEXT: ret void +// CHECK-NEXT: } + +// CHECK-LABEL: define void @"{{.*}}/cl/_testgo/ifaceconv.init"(){{.*}} { +// CHECK-NEXT: _llgo_0: +// CHECK-NEXT: %0 = load i1, ptr @"{{.*}}/cl/_testgo/ifaceconv.init$guard", align 1 +// CHECK-NEXT: br i1 %0, label %_llgo_2, label %_llgo_1 +// CHECK-EMPTY: +// CHECK-NEXT: _llgo_1: ; preds = %_llgo_0 +// CHECK-NEXT: store i1 true, ptr @"{{.*}}/cl/_testgo/ifaceconv.init$guard", align 1 +// CHECK-NEXT: br label %_llgo_2 +// CHECK-EMPTY: +// CHECK-NEXT: _llgo_2: ; preds = %_llgo_1, %_llgo_0 // CHECK-NEXT: ret void // CHECK-NEXT: } -func (C2) f() {} -func (C2) g() {} // CHECK-LABEL: define void @"{{.*}}/cl/_testgo/ifaceconv.main"(){{.*}} { // CHECK-NEXT: _llgo_0: @@ -361,64 +454,21 @@ func (C2) g() {} // CHECK-NEXT: %125 = extractvalue { %"{{.*}}/runtime/internal/runtime.iface", i1 } %123, 1 // CHECK-NEXT: br i1 %125, label %_llgo_18, label %_llgo_17 // CHECK-NEXT: } -func main() { - var i0 I0 - var i1 I1 - var i2 I2 - - // Nil always causes a type assertion to fail, even to the - // same type. - if _, ok := i0.(I0); ok { - panic("nil i0.(I0) succeeded") - } - if _, ok := i1.(I1); ok { - panic("nil i1.(I1) succeeded") - } - if _, ok := i2.(I2); ok { - panic("nil i2.(I2) succeeded") - } - - // Conversions can't fail, even with nil. - _ = I0(i0) - - _ = I0(i1) - _ = I1(i1) - - _ = I0(i2) - _ = I1(i2) - _ = I2(i2) - - // Non-nil type assertions pass or fail based on the concrete type. - i1 = C1{} - if _, ok := i1.(I0); !ok { - panic("C1 i1.(I0) failed") - } - if _, ok := i1.(I1); !ok { - panic("C1 i1.(I1) failed") - } - if _, ok := i1.(I2); ok { - panic("C1 i1.(I2) succeeded") - } - i1 = C2{} - if _, ok := i1.(I0); !ok { - panic("C2 i1.(I0) failed") - } - if _, ok := i1.(I1); !ok { - panic("C2 i1.(I1) failed") - } - if _, ok := i1.(I2); !ok { - panic("C2 i1.(I2) failed") - } +// CHECK-LABEL: define linkonce i1 @"__llgo_stub.{{.*}}/runtime/internal/runtime.nilinterequal"(ptr %0, ptr %1, ptr %2){{.*}} { +// CHECK-NEXT: _llgo_0: +// CHECK-NEXT: %3 = tail call i1 @"{{.*}}/runtime/internal/runtime.nilinterequal"(ptr %1, ptr %2) +// CHECK-NEXT: ret i1 %3 +// CHECK-NEXT: } - // Conversions can't fail. - i1 = C1{} - if I0(i1) == nil { - panic("C1 I0(i1) was nil") - } - if I1(i1) == nil { - panic("C1 I1(i1) was nil") - } +// CHECK-LABEL: define linkonce i1 @"__llgo_stub.{{.*}}/runtime/internal/runtime.interequal"(ptr %0, ptr %1, ptr %2){{.*}} { +// CHECK-NEXT: _llgo_0: +// CHECK-NEXT: %3 = tail call i1 @"{{.*}}/runtime/internal/runtime.interequal"(ptr %1, ptr %2) +// CHECK-NEXT: ret i1 %3 +// CHECK-NEXT: } - println("pass") -} +// CHECK-LABEL: define linkonce i1 @"__llgo_stub.{{.*}}/runtime/internal/runtime.memequal0"(ptr %0, ptr %1, ptr %2){{.*}} { +// CHECK-NEXT: _llgo_0: +// CHECK-NEXT: %3 = tail call i1 @"{{.*}}/runtime/internal/runtime.memequal0"(ptr %1, ptr %2) +// CHECK-NEXT: ret i1 %3 +// CHECK-NEXT: } diff --git a/cl/_testgo/selects/in.go b/cl/_testgo/selects/in.go index f72ba639a3..a2f44c3e9b 100644 --- a/cl/_testgo/selects/in.go +++ b/cl/_testgo/selects/in.go @@ -1,6 +1,56 @@ // LITTEST package main +// CHECK-LINE: @0 = private unnamed_addr constant [4 x i8] c"c1<-", align 1 +// CHECK-LINE: @1 = private unnamed_addr constant [4 x i8] c"<-c2", align 1 +// CHECK-LINE: @2 = private unnamed_addr constant [4 x i8] c"<-c4", align 1 +// CHECK-LINE: @3 = private unnamed_addr constant [31 x i8] c"blocking select matched no case", align 1 +// CHECK-LINE: @5 = private unnamed_addr constant [4 x i8] c"<-c1", align 1 +// CHECK-LINE: @6 = private unnamed_addr constant [4 x i8] c"c2<-", align 1 +// CHECK-LINE: @7 = private unnamed_addr constant [4 x i8] c"<-c3", align 1 + +func main() { + c1 := make(chan struct{}, 1) + c2 := make(chan struct{}, 1) + c3 := make(chan struct{}, 1) + c4 := make(chan struct{}, 1) + + go func() { + <-c1 + println("<-c1") + + select { + case c2 <- struct{}{}: + println("c2<-") + case <-c3: + println("<-c3") + } + }() + + c1 <- struct{}{} + println("c1<-") + + select { + case <-c2: + println("<-c2") + case <-c4: + println("<-c4") + } +} + +// CHECK-LABEL: define void @"{{.*}}/cl/_testgo/selects.init"(){{.*}} { +// CHECK-NEXT: _llgo_0: +// CHECK-NEXT: %0 = load i1, ptr @"{{.*}}/cl/_testgo/selects.init$guard", align 1 +// CHECK-NEXT: br i1 %0, label %_llgo_2, label %_llgo_1 +// CHECK-EMPTY: +// CHECK-NEXT: _llgo_1: ; preds = %_llgo_0 +// CHECK-NEXT: store i1 true, ptr @"{{.*}}/cl/_testgo/selects.init$guard", align 1 +// CHECK-NEXT: br label %_llgo_2 +// CHECK-EMPTY: +// CHECK-NEXT: _llgo_2: ; preds = %_llgo_1, %_llgo_0 +// CHECK-NEXT: ret void +// CHECK-NEXT: } + // CHECK-LABEL: define void @"{{.*}}/cl/_testgo/selects.main"(){{.*}} { // CHECK-NEXT: _llgo_0: // CHECK-NEXT: %0 = call ptr @"{{.*}}/runtime/internal/runtime.AllocZ"(i64 8) @@ -58,13 +108,15 @@ package main // CHECK-NEXT: %37 = extractvalue { i64, i1 } %36, 0 // CHECK-NEXT: %38 = extractvalue { i64, i1 } %36, 1 // CHECK-NEXT: %39 = extractvalue %"{{.*}}/runtime/internal/runtime.ChanOp" %24, 1 -// CHECK-NEXT: %40 = load {}, ptr %39, align 1 +// CHECK-NEXT: %40 = icmp eq ptr %39, null +// CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.AssertNilDeref"(i1 %40) // CHECK-NEXT: %41 = extractvalue %"{{.*}}/runtime/internal/runtime.ChanOp" %29, 1 -// CHECK-NEXT: %42 = load {}, ptr %41, align 1 +// CHECK-NEXT: %42 = icmp eq ptr %41, null +// CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.AssertNilDeref"(i1 %42) // CHECK-NEXT: %43 = insertvalue { i64, i1, {}, {} } undef, i64 %37, 0 // CHECK-NEXT: %44 = insertvalue { i64, i1, {}, {} } %43, i1 %38, 1 -// CHECK-NEXT: %45 = insertvalue { i64, i1, {}, {} } %44, {} %40, 2 -// CHECK-NEXT: %46 = insertvalue { i64, i1, {}, {} } %45, {} %42, 3 +// CHECK-NEXT: %45 = insertvalue { i64, i1, {}, {} } %44, {} zeroinitializer, 2 +// CHECK-NEXT: %46 = insertvalue { i64, i1, {}, {} } %45, {} zeroinitializer, 3 // CHECK-NEXT: %47 = extractvalue { i64, i1, {}, {} } %46, 0 // CHECK-NEXT: %48 = icmp eq i64 %47, 0 // CHECK-NEXT: br i1 %48, label %_llgo_2, label %_llgo_3 @@ -93,34 +145,6 @@ package main // CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.Panic"(%"{{.*}}/runtime/internal/runtime.eface" %51) // CHECK-NEXT: unreachable // CHECK-NEXT: } -func main() { - c1 := make(chan struct{}, 1) - c2 := make(chan struct{}, 1) - c3 := make(chan struct{}, 1) - c4 := make(chan struct{}, 1) - - go func() { - <-c1 - println("<-c1") - - select { - case c2 <- struct{}{}: - println("c2<-") - case <-c3: - println("<-c3") - } - }() - - c1 <- struct{}{} - println("c1<-") - - select { - case <-c2: - println("<-c2") - case <-c4: - println("<-c4") - } -} // CHECK-LABEL: define void @"{{.*}}/cl/_testgo/selects.main$1"(ptr %0){{.*}} { // CHECK-NEXT: _llgo_0: @@ -130,7 +154,8 @@ func main() { // CHECK-NEXT: %4 = alloca {}, align 8 // CHECK-NEXT: call void @llvm.memset(ptr %4, i8 0, i64 0, i1 false) // CHECK-NEXT: %5 = call i1 @"{{.*}}/runtime/internal/runtime.ChanRecv"(ptr %3, ptr %4, i64 0) -// CHECK-NEXT: %6 = load {}, ptr %4, align 1 +// CHECK-NEXT: %6 = icmp eq ptr %4, null +// CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.AssertNilDeref"(i1 %6) // CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintString"(%"{{.*}}/runtime/internal/runtime.String" { ptr @5, i64 4 }) // CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.PrintByte"(i8 10) // CHECK-NEXT: %7 = extractvalue { ptr, ptr, ptr } %1, 1 @@ -162,10 +187,11 @@ func main() { // CHECK-NEXT: %28 = extractvalue { i64, i1 } %27, 0 // CHECK-NEXT: %29 = extractvalue { i64, i1 } %27, 1 // CHECK-NEXT: %30 = extractvalue %"{{.*}}/runtime/internal/runtime.ChanOp" %20, 1 -// CHECK-NEXT: %31 = load {}, ptr %30, align 1 +// CHECK-NEXT: %31 = icmp eq ptr %30, null +// CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.AssertNilDeref"(i1 %31) // CHECK-NEXT: %32 = insertvalue { i64, i1, {} } undef, i64 %28, 0 // CHECK-NEXT: %33 = insertvalue { i64, i1, {} } %32, i1 %29, 1 -// CHECK-NEXT: %34 = insertvalue { i64, i1, {} } %33, {} %31, 2 +// CHECK-NEXT: %34 = insertvalue { i64, i1, {} } %33, {} zeroinitializer, 2 // CHECK-NEXT: %35 = extractvalue { i64, i1, {} } %34, 0 // CHECK-NEXT: %36 = icmp eq i64 %35, 0 // CHECK-NEXT: br i1 %36, label %_llgo_2, label %_llgo_3 @@ -194,3 +220,13 @@ func main() { // CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.Panic"(%"{{.*}}/runtime/internal/runtime.eface" %39) // CHECK-NEXT: unreachable // CHECK-NEXT: } + +// CHECK-LABEL: define ptr @"{{.*}}/cl/_testgo/selects._llgo_routine$1"(ptr %0){{.*}} { +// CHECK-NEXT: _llgo_0: +// CHECK-NEXT: %1 = load { { ptr, ptr } }, ptr %0, align 8 +// CHECK-NEXT: %2 = extractvalue { { ptr, ptr } } %1, 0 +// CHECK-NEXT: %3 = extractvalue { ptr, ptr } %2, 1 +// CHECK-NEXT: %4 = extractvalue { ptr, ptr } %2, 0 +// CHECK-NEXT: call void %4(ptr %3) +// CHECK-NEXT: ret ptr null +// CHECK-NEXT: } diff --git a/cl/_testgo/tpnamed/in.go b/cl/_testgo/tpnamed/in.go index 18ef90a445..faa321deb3 100644 --- a/cl/_testgo/tpnamed/in.go +++ b/cl/_testgo/tpnamed/in.go @@ -6,49 +6,103 @@ type Future[T any] func() T type IO[T any] func() Future[T] -// CHECK-LABEL: define %"{{.*}}tpnamed.IO[error]" @"{{.*}}tpnamed.WriteFile"(%"{{.*}}String" %0){{.*}} { +// CHECK-LABEL: define %"{{.*}}/cl/_testgo/tpnamed.IO[error]" @"{{.*}}/cl/_testgo/tpnamed.WriteFile"(%"{{.*}}/runtime/internal/runtime.String" %0){{.*}} { // CHECK-NEXT: _llgo_0: -// CHECK-NEXT: ret %"{{.*}}tpnamed.IO[error]" { ptr @"__llgo_stub.{{.*}}tpnamed.WriteFile$1", ptr null } +// CHECK-NEXT: ret %"{{.*}}/cl/_testgo/tpnamed.IO[error]" { ptr @"__llgo_stub.{{.*}}/cl/_testgo/tpnamed.WriteFile$1", ptr null } +// CHECK-NEXT: } + func WriteFile(fileName string) IO[error] { + // CHECK-LABEL: define %"{{.*}}/cl/_testgo/tpnamed.Future[error]" @"{{.*}}/cl/_testgo/tpnamed.WriteFile$1"(){{.*}} { + // CHECK-NEXT: _llgo_0: + // CHECK-NEXT: ret %"{{.*}}/cl/_testgo/tpnamed.Future[error]" { ptr @"__llgo_stub.{{.*}}/cl/_testgo/tpnamed.WriteFile$1$1", ptr null } + // CHECK-NEXT: } + return func() Future[error] { - // CHECK-LABEL: define %"{{.*}}tpnamed.Future[error]" @"{{.*}}tpnamed.WriteFile$1"(){{.*}} { + // CHECK-LABEL: define %"{{.*}}/runtime/internal/runtime.iface" @"{{.*}}/cl/_testgo/tpnamed.WriteFile$1$1"(){{.*}} { // CHECK-NEXT: _llgo_0: - // CHECK-NEXT: ret %"{{.*}}tpnamed.Future[error]" { ptr @"__llgo_stub.{{.*}}tpnamed.WriteFile$1$1", ptr null } + // CHECK-NEXT: ret %"{{.*}}/runtime/internal/runtime.iface" zeroinitializer + // CHECK-NEXT: } + + // CHECK-LABEL: define void @"{{.*}}/cl/_testgo/tpnamed.init"(){{.*}} { + // CHECK-NEXT: _llgo_0: + // CHECK-NEXT: %0 = load i1, ptr @"{{.*}}/cl/_testgo/tpnamed.init$guard", align 1 + // CHECK-NEXT: br i1 %0, label %_llgo_2, label %_llgo_1 + // CHECK-EMPTY: + // CHECK-NEXT: _llgo_1: ; preds = %_llgo_0 + // CHECK-NEXT: store i1 true, ptr @"{{.*}}/cl/_testgo/tpnamed.init$guard", align 1 + // CHECK-NEXT: br label %_llgo_2 + // CHECK-EMPTY: + // CHECK-NEXT: _llgo_2: ; preds = %_llgo_1, %_llgo_0 + // CHECK-NEXT: ret void + // CHECK-NEXT: } + return func() error { - // CHECK-LABEL: define %"{{.*}}iface" @"{{.*}}tpnamed.WriteFile$1$1"(){{.*}} { - // CHECK-NEXT: _llgo_0: - // CHECK-NEXT: ret %"{{.*}}iface" zeroinitializer return nil } } } -// CHECK-LABEL: define void @"{{.*}}tpnamed.main"(){{.*}} { +// CHECK-LABEL: define void @"{{.*}}/cl/_testgo/tpnamed.main"(){{.*}} { +// CHECK-NEXT: _llgo_0: +// CHECK-NEXT: %0 = call [0 x i8] @"{{.*}}/cl/_testgo/tpnamed.RunIO[[0]byte]"(%"{{.*}}/cl/_testgo/tpnamed.IO[[0]byte]" { ptr @"__llgo_stub.{{.*}}/cl/_testgo/tpnamed.main$1", ptr null }) +// CHECK-NEXT: ret void +// CHECK-NEXT: } + func main() { - // CHECK: call [0 x i8] @"{{.*}}tpnamed.RunIO[[0]byte]"(%"{{.*}}tpnamed.IO[[0]byte]" { ptr @"__llgo_stub.{{.*}}tpnamed.main$1", ptr null }) - // CHECK: ret void + // CHECK-LABEL: define %"{{.*}}/cl/_testgo/tpnamed.Future[[0]byte]" @"{{.*}}/cl/_testgo/tpnamed.main$1"(){{.*}} { + // CHECK-NEXT: _llgo_0: + // CHECK-NEXT: ret %"{{.*}}/cl/_testgo/tpnamed.Future[[0]byte]" { ptr @"__llgo_stub.{{.*}}/cl/_testgo/tpnamed.main$1$1", ptr null } + // CHECK-NEXT: } + RunIO[Void](func() Future[Void] { - // CHECK-LABEL: define %"{{.*}}tpnamed.Future[[0]byte]" @"{{.*}}tpnamed.main$1"(){{.*}} { + // CHECK-LABEL: define [0 x i8] @"{{.*}}/cl/_testgo/tpnamed.main$1$1"(){{.*}} { // CHECK-NEXT: _llgo_0: - // CHECK-NEXT: ret %"{{.*}}tpnamed.Future[[0]byte]" { ptr @"__llgo_stub.{{.*}}tpnamed.main$1$1", ptr null } + // CHECK-NEXT: ret [0 x i8] zeroinitializer + // CHECK-NEXT: } + return func() (ret Void) { - // CHECK-LABEL: define [0 x i8] @"{{.*}}tpnamed.main$1$1"(){{.*}} { - // CHECK-NEXT: _llgo_0: - // CHECK-NEXT: ret [0 x i8] zeroinitializer return } }) } -// CHECK-LABEL: define linkonce [0 x i8] @"{{.*}}tpnamed.RunIO[[0]byte]"(%"{{.*}}tpnamed.IO[[0]byte]" %0){{.*}} { -// CHECK-NEXT: _llgo_0: -// CHECK-NEXT: %1 = extractvalue %"{{.*}}tpnamed.IO[[0]byte]" %0, 1 -// CHECK-NEXT: %2 = extractvalue %"{{.*}}tpnamed.IO[[0]byte]" %0, 0 -// CHECK-NEXT: %3 = call %"{{.*}}tpnamed.Future[[0]byte]" %2(ptr %1) -// CHECK-NEXT: %4 = extractvalue %"{{.*}}tpnamed.Future[[0]byte]" %3, 1 -// CHECK-NEXT: %5 = extractvalue %"{{.*}}tpnamed.Future[[0]byte]" %3, 0 -// CHECK-NEXT: %6 = call [0 x i8] %5(ptr %4) -// CHECK-NEXT: ret [0 x i8] %6 func RunIO[T any](call IO[T]) T { return call()() } + +// CHECK-LABEL: define linkonce %"{{.*}}/cl/_testgo/tpnamed.Future[error]" @"__llgo_stub.{{.*}}/cl/_testgo/tpnamed.WriteFile$1"(ptr %0){{.*}} { +// CHECK-NEXT: _llgo_0: +// CHECK-NEXT: %1 = tail call %"{{.*}}/cl/_testgo/tpnamed.Future[error]" @"{{.*}}/cl/_testgo/tpnamed.WriteFile$1"() +// CHECK-NEXT: ret %"{{.*}}/cl/_testgo/tpnamed.Future[error]" %1 +// CHECK-NEXT: } + +// CHECK-LABEL: define linkonce %"{{.*}}/runtime/internal/runtime.iface" @"__llgo_stub.{{.*}}/cl/_testgo/tpnamed.WriteFile$1$1"(ptr %0){{.*}} { +// CHECK-NEXT: _llgo_0: +// CHECK-NEXT: %1 = tail call %"{{.*}}/runtime/internal/runtime.iface" @"{{.*}}/cl/_testgo/tpnamed.WriteFile$1$1"() +// CHECK-NEXT: ret %"{{.*}}/runtime/internal/runtime.iface" %1 +// CHECK-NEXT: } + +// CHECK-LABEL: define linkonce %"{{.*}}/cl/_testgo/tpnamed.Future[[0]byte]" @"__llgo_stub.{{.*}}/cl/_testgo/tpnamed.main$1"(ptr %0){{.*}} { +// CHECK-NEXT: _llgo_0: +// CHECK-NEXT: %1 = tail call %"{{.*}}/cl/_testgo/tpnamed.Future[[0]byte]" @"{{.*}}/cl/_testgo/tpnamed.main$1"() +// CHECK-NEXT: ret %"{{.*}}/cl/_testgo/tpnamed.Future[[0]byte]" %1 +// CHECK-NEXT: } + +// CHECK-LABEL: define linkonce [0 x i8] @"{{.*}}/cl/_testgo/tpnamed.RunIO[[0]byte]"(%"{{.*}}/cl/_testgo/tpnamed.IO[[0]byte]" %0){{.*}} { +// CHECK-NEXT: _llgo_0: +// CHECK-NEXT: %1 = extractvalue %"{{.*}}/cl/_testgo/tpnamed.IO[[0]byte]" %0, 1 +// CHECK-NEXT: %2 = extractvalue %"{{.*}}/cl/_testgo/tpnamed.IO[[0]byte]" %0, 0 +// CHECK-NEXT: %3 = call %"{{.*}}/cl/_testgo/tpnamed.Future[[0]byte]" %2(ptr %1) +// CHECK-NEXT: %4 = extractvalue %"{{.*}}/cl/_testgo/tpnamed.Future[[0]byte]" %3, 1 +// CHECK-NEXT: %5 = extractvalue %"{{.*}}/cl/_testgo/tpnamed.Future[[0]byte]" %3, 0 +// CHECK-NEXT: %6 = icmp eq ptr %5, null +// CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.AssertNilDeref"(i1 %6) +// CHECK-NEXT: %7 = call [0 x i8] %5(ptr %4) +// CHECK-NEXT: ret [0 x i8] %7 +// CHECK-NEXT: } + +// CHECK-LABEL: define linkonce [0 x i8] @"__llgo_stub.{{.*}}/cl/_testgo/tpnamed.main$1$1"(ptr %0){{.*}} { +// CHECK-NEXT: _llgo_0: +// CHECK-NEXT: %1 = tail call [0 x i8] @"{{.*}}/cl/_testgo/tpnamed.main$1$1"() +// CHECK-NEXT: ret [0 x i8] %1 +// CHECK-NEXT: } diff --git a/cl/_testrt/closurebound/in.go b/cl/_testrt/closurebound/in.go index 2104be8e46..84e6b88083 100644 --- a/cl/_testrt/closurebound/in.go +++ b/cl/_testrt/closurebound/in.go @@ -1,6 +1,8 @@ // LITTEST package main +// CHECK-LINE: @0 = private unnamed_addr constant [5 x i8] c"error", align 1 + var my = demo2{}.encode type demo1 struct { @@ -11,12 +13,6 @@ type demo1 struct { // CHECK-NEXT: ret i64 1 // CHECK-NEXT: } -// CHECK-LABEL: define i64 @"{{.*}}/cl/_testrt/closurebound.(*demo1).encode"(ptr %0){{.*}} { -// CHECK-NEXT: _llgo_0: -// CHECK-NEXT: %1 = load %"{{.*}}/cl/_testrt/closurebound.demo1", ptr %0, align 1 -// CHECK-NEXT: %2 = call i64 @"{{.*}}/cl/_testrt/closurebound.demo1.encode"(%"{{.*}}/cl/_testrt/closurebound.demo1" %1) -// CHECK-NEXT: ret i64 %2 -// CHECK-NEXT: } func (se demo1) encode() int { return 1 } @@ -24,6 +20,26 @@ func (se demo1) encode() int { type demo2 struct { } +func (se demo2) encode() int { + return 2 +} + +func main() { + se := demo1{} + f := se.encode + if f() != 1 { + panic("error") + } +} + +// CHECK-LABEL: define i64 @"{{.*}}/cl/_testrt/closurebound.(*demo1).encode"(ptr %0){{.*}} { +// CHECK-NEXT: _llgo_0: +// CHECK-NEXT: %1 = icmp eq ptr %0, null +// CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.AssertNilDeref"(i1 %1) +// CHECK-NEXT: %2 = call i64 @"{{.*}}/cl/_testrt/closurebound.demo1.encode"(%"{{.*}}/cl/_testrt/closurebound.demo1" zeroinitializer) +// CHECK-NEXT: ret i64 %2 +// CHECK-NEXT: } + // CHECK-LABEL: define i64 @"{{.*}}/cl/_testrt/closurebound.demo2.encode"(%"{{.*}}/cl/_testrt/closurebound.demo2" %0){{.*}} { // CHECK-NEXT: _llgo_0: // CHECK-NEXT: ret i64 2 @@ -31,13 +47,29 @@ type demo2 struct { // CHECK-LABEL: define i64 @"{{.*}}/cl/_testrt/closurebound.(*demo2).encode"(ptr %0){{.*}} { // CHECK-NEXT: _llgo_0: -// CHECK-NEXT: %1 = load %"{{.*}}/cl/_testrt/closurebound.demo2", ptr %0, align 1 -// CHECK-NEXT: %2 = call i64 @"{{.*}}/cl/_testrt/closurebound.demo2.encode"(%"{{.*}}/cl/_testrt/closurebound.demo2" %1) +// CHECK-NEXT: %1 = icmp eq ptr %0, null +// CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.AssertNilDeref"(i1 %1) +// CHECK-NEXT: %2 = call i64 @"{{.*}}/cl/_testrt/closurebound.demo2.encode"(%"{{.*}}/cl/_testrt/closurebound.demo2" zeroinitializer) // CHECK-NEXT: ret i64 %2 // CHECK-NEXT: } -func (se demo2) encode() int { - return 2 -} + +// CHECK-LABEL: define void @"{{.*}}/cl/_testrt/closurebound.init"(){{.*}} { +// CHECK-NEXT: _llgo_0: +// CHECK-NEXT: %0 = load i1, ptr @"{{.*}}/cl/_testrt/closurebound.init$guard", align 1 +// CHECK-NEXT: br i1 %0, label %_llgo_2, label %_llgo_1 +// CHECK-EMPTY: +// CHECK-NEXT: _llgo_1: ; preds = %_llgo_0 +// CHECK-NEXT: store i1 true, ptr @"{{.*}}/cl/_testrt/closurebound.init$guard", align 1 +// CHECK-NEXT: %1 = call ptr @"{{.*}}/runtime/internal/runtime.AllocU"(i64 0) +// CHECK-NEXT: %2 = getelementptr inbounds { %"{{.*}}/cl/_testrt/closurebound.demo2" }, ptr %1, i32 0, i32 0 +// CHECK-NEXT: store %"{{.*}}/cl/_testrt/closurebound.demo2" zeroinitializer, ptr %2, align 1 +// CHECK-NEXT: %3 = insertvalue { ptr, ptr } { ptr @"{{.*}}/cl/_testrt/closurebound.demo2.encode$bound", ptr undef }, ptr %1, 1 +// CHECK-NEXT: store { ptr, ptr } %3, ptr @"{{.*}}/cl/_testrt/closurebound.my", align 8 +// CHECK-NEXT: br label %_llgo_2 +// CHECK-EMPTY: +// CHECK-NEXT: _llgo_2: ; preds = %_llgo_1, %_llgo_0 +// CHECK-NEXT: ret void +// CHECK-NEXT: } // CHECK-LABEL: define void @"{{.*}}/cl/_testrt/closurebound.main"(){{.*}} { // CHECK-NEXT: _llgo_0: @@ -61,26 +93,19 @@ func (se demo2) encode() int { // CHECK-NEXT: _llgo_2: ; preds = %_llgo_0 // CHECK-NEXT: ret void // CHECK-NEXT: } -func main() { - se := demo1{} - f := se.encode - if f() != 1 { - panic("error") - } -} // CHECK-LABEL: define i64 @"{{.*}}/cl/_testrt/closurebound.demo2.encode$bound"(ptr %0){{.*}} { // CHECK-NEXT: _llgo_0: -// CHECK-NEXT: %1 = load { %"{{.*}}/cl/_testrt/closurebound.demo2" }, ptr %0, align 1 -// CHECK-NEXT: %2 = extractvalue { %"{{.*}}/cl/_testrt/closurebound.demo2" } %1, 0 -// CHECK-NEXT: %3 = call i64 @"{{.*}}/cl/_testrt/closurebound.demo2.encode"(%"{{.*}}/cl/_testrt/closurebound.demo2" %2) -// CHECK-NEXT: ret i64 %3 +// CHECK-NEXT: %1 = icmp eq ptr %0, null +// CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.AssertNilDeref"(i1 %1) +// CHECK-NEXT: %2 = call i64 @"{{.*}}/cl/_testrt/closurebound.demo2.encode"(%"{{.*}}/cl/_testrt/closurebound.demo2" zeroinitializer) +// CHECK-NEXT: ret i64 %2 // CHECK-NEXT: } // CHECK-LABEL: define i64 @"{{.*}}/cl/_testrt/closurebound.demo1.encode$bound"(ptr %0){{.*}} { // CHECK-NEXT: _llgo_0: -// CHECK-NEXT: %1 = load { %"{{.*}}/cl/_testrt/closurebound.demo1" }, ptr %0, align 1 -// CHECK-NEXT: %2 = extractvalue { %"{{.*}}/cl/_testrt/closurebound.demo1" } %1, 0 -// CHECK-NEXT: %3 = call i64 @"{{.*}}/cl/_testrt/closurebound.demo1.encode"(%"{{.*}}/cl/_testrt/closurebound.demo1" %2) -// CHECK-NEXT: ret i64 %3 +// CHECK-NEXT: %1 = icmp eq ptr %0, null +// CHECK-NEXT: call void @"{{.*}}/runtime/internal/runtime.AssertNilDeref"(i1 %1) +// CHECK-NEXT: %2 = call i64 @"{{.*}}/cl/_testrt/closurebound.demo1.encode"(%"{{.*}}/cl/_testrt/closurebound.demo1" zeroinitializer) +// CHECK-NEXT: ret i64 %2 // CHECK-NEXT: } diff --git a/cl/compile.go b/cl/compile.go index 5b6d7c9fbe..6038a7b765 100644 --- a/cl/compile.go +++ b/cl/compile.go @@ -906,6 +906,11 @@ func (p *context) compileInstrOrValue(b llssa.Builder, iv instrOrValue, asValue if v.Op == token.ARROW { ret = b.Recv(x, v.CommaOk) } else { + if v.Op == token.MUL { + if t := p.type_(v.Type(), llssa.InGo); t.RawType() != nil && p.prog.SizeOf(t) == 0 { + p.assertNilDerefBase(b, v.X) + } + } ret = b.UnOp(v.Op, x) } case *ssa.ChangeType: diff --git a/cl/zero_size_deref_test.go b/cl/zero_size_deref_test.go new file mode 100644 index 0000000000..6ece9780ea --- /dev/null +++ b/cl/zero_size_deref_test.go @@ -0,0 +1,25 @@ +//go:build !llgo +// +build !llgo + +package cl + +import ( + "strings" + "testing" +) + +func TestZeroSizedFieldDerefEmitsBaseNilGuard(t *testing.T) { + const src = `package zeroderef +type T struct { + n int + z struct{} +} +func Eq(p, q *T) bool { + return p.z == q.z +} +` + ir := compileWithRewrites(t, src, nil) + if got := strings.Count(ir, "AssertNilDeref"); got < 4 { + t.Fatalf("zero-sized field comparison should guard field bases and loads, got %d guards:\n%s", got, ir) + } +} diff --git a/ssa/expr.go b/ssa/expr.go index ecc19ca215..cc8442395a 100644 --- a/ssa/expr.go +++ b/ssa/expr.go @@ -1137,6 +1137,9 @@ func (b Builder) Call(fn Expr, args ...Expr) (ret Expr) { ctx := types.NewParam(token.NoPos, nil, closureCtx, types.Typ[types.UnsafePointer]) sigCtx := FuncAddCtx(ctx, sig) ret.Type = b.Prog.retType(sig) + if sig.Results().Len() == 1 && b.Prog.SizeOf(ret.Type) == 0 { + b.AssertNilDeref(fn) + } ll = b.Prog.FuncDecl(sigCtx, InC).ll ret.impl = llvm.CreateCall(b.impl, ll, fn.impl, llvmParamsEx(data, args, sigCtx.Params(), b)) return ret diff --git a/ssa/memory.go b/ssa/memory.go index 4bb3e7ce98..ab2c298c27 100644 --- a/ssa/memory.go +++ b/ssa/memory.go @@ -344,6 +344,10 @@ func (b Builder) Load(ptr Expr) Expr { return b.pyLoad(ptr) } telem := b.Prog.Elem(ptr.Type) + if b.Prog.SizeOf(telem) == 0 { + b.AssertNilDeref(ptr) + return b.Prog.Zero(telem) + } return Expr{llvm.CreateLoad(b.impl, telem.ll, ptr.impl), telem} } diff --git a/ssa/ssa_test.go b/ssa/ssa_test.go index 552675dfc6..14fd12396d 100644 --- a/ssa/ssa_test.go +++ b/ssa/ssa_test.go @@ -810,6 +810,33 @@ func TestMakeInterfaceFromPtrKinds(t *testing.T) { } } +func TestZeroSizedLoadEmitsNilDerefGuard(t *testing.T) { + prog := NewProgram(nil) + prog.sizes = types.SizesFor("gc", runtime.GOARCH) + prog.SetRuntime(func() *types.Package { + pkg, err := importer.For("source", nil).Import(PkgRuntime) + if err != nil { + t.Fatal(err) + } + return pkg + }) + pkg := prog.NewPackage("bar", "foo/bar") + + emptyStruct := types.NewStruct(nil, nil) + params := types.NewTuple(types.NewVar(0, nil, "p", types.NewPointer(emptyStruct))) + results := types.NewTuple(types.NewVar(0, nil, "", emptyStruct)) + sig := types.NewSignatureType(nil, nil, nil, params, results, false) + fn := pkg.NewFunc("loadZero", sig, InGo) + b := fn.MakeBody(1) + b.Return(b.Load(fn.Param(0))) + b.EndBuild() + + ir := pkg.Module().String() + if !strings.Contains(ir, "AssertNilDeref") { + t.Fatalf("zero-sized Load should emit nil-deref guard, got:\n%s", ir) + } +} + func TestTypeAssertSingleElemArrayUsesInsertValue(t *testing.T) { prog := NewProgram(nil) prog.sizes = types.SizesFor("gc", runtime.GOARCH) diff --git a/test/go/zero_size_compare_test.go b/test/go/zero_size_compare_test.go new file mode 100644 index 0000000000..4a8e060c6d --- /dev/null +++ b/test/go/zero_size_compare_test.go @@ -0,0 +1,66 @@ +package gotest + +import "testing" + +type zeroSizeCompareField struct { + n int + z struct{} +} + +//go:noinline +func zeroSizeDerefEqual(p, q *struct{}) bool { + return *p == *q +} + +//go:noinline +func zeroSizeFieldEqual(p, q *zeroSizeCompareField) bool { + return p.z == q.z +} + +//go:noinline +func zeroSizeResultEqual(p, q func() struct{}) bool { + return p() == q() +} + +func zeroSizeResultEqualInline(p, q func() struct{}) bool { + return p() == q() +} + +func TestZeroSizeNilPointerComparisonPanics(t *testing.T) { + expectZeroSizeComparePanic(t, func() { + zeroSizeDerefEqual(nil, nil) + }) + expectZeroSizeComparePanic(t, func() { + zeroSizeFieldEqual(nil, nil) + }) +} + +func TestZeroSizeFunctionResultComparisonCallsFunctions(t *testing.T) { + n := 0 + inc := func() struct{} { + n++ + return struct{}{} + } + if !zeroSizeResultEqual(inc, inc) { + t.Fatal("zero-sized results should compare equal") + } + if n != 2 { + t.Fatalf("calls after noinline comparison = %d, want 2", n) + } + if !zeroSizeResultEqualInline(inc, inc) { + t.Fatal("zero-sized results should compare equal") + } + if n != 4 { + t.Fatalf("calls after inline comparison = %d, want 4", n) + } +} + +func expectZeroSizeComparePanic(t *testing.T, f func()) { + t.Helper() + defer func() { + if recover() == nil { + t.Fatal("expected nil pointer dereference panic") + } + }() + f() +} diff --git a/test/goroot/xfail.yaml b/test/goroot/xfail.yaml index a5a66d10b4..a9678916e2 100644 --- a/test/goroot/xfail.yaml +++ b/test/goroot/xfail.yaml @@ -2435,10 +2435,6 @@ xfails: directive: run case: fixedbugs/issue22662.go reason: latest main goroot run failure on darwin/arm64 - - platform: darwin/arm64 - directive: run - case: fixedbugs/issue23837.go - reason: latest main goroot run failure on darwin/arm64 - platform: darwin/arm64 directive: run case: fixedbugs/issue24491b.go @@ -2616,11 +2612,6 @@ xfails: directive: run case: fixedbugs/issue22662.go reason: go1.25 goroot run failure on linux/amd64 - - version: go1.25 - platform: linux/amd64 - directive: run - case: fixedbugs/issue23837.go - reason: go1.25 goroot run failure on linux/amd64 - version: go1.25 platform: linux/amd64 directive: run @@ -3067,11 +3058,6 @@ xfails: directive: run case: fixedbugs/issue21879.go reason: go1.24 goroot run failure on linux/amd64 - - version: go1.24 - platform: linux/amd64 - directive: run - case: fixedbugs/issue23837.go - reason: go1.24 goroot run failure on linux/amd64 - version: go1.24 platform: darwin/arm64 directive: run @@ -3317,11 +3303,6 @@ xfails: directive: run case: fixedbugs/issue21879.go reason: go1.26 goroot run failure on linux/amd64 - - version: go1.26 - platform: linux/amd64 - directive: run - case: fixedbugs/issue23837.go - reason: go1.26 goroot run failure on linux/amd64 - version: go1.26 platform: darwin/arm64 directive: run