aboutsummaryrefslogtreecommitdiff
path: root/src/runtime
diff options
context:
space:
mode:
authordoujiang24 <doujiang24@gmail.com>2023-08-25 17:06:31 +0000
committerGopher Robot <gobot@golang.org>2023-08-25 17:39:23 +0000
commit24b9ef1a7366fe751880ab2098cff630144b8ac8 (patch)
tree0f1de5cac722c84485bdfd9c1cc80f6422d7a037 /src/runtime
parent1a01cb22f9ab07d55ee61c95a34e1e18e49596c0 (diff)
downloadgo-24b9ef1a7366fe751880ab2098cff630144b8ac8.tar.xz
cmd/cgo: add #cgo noescape/nocallback annotations
When passing pointers of Go objects from Go to C, the cgo command generate _Cgo_use(pN) for the unsafe.Pointer type arguments, so that the Go compiler will escape these object to heap. Since the C function may callback to Go, then the Go stack might grow/shrink, that means the pointers that the C function have will be invalid. After adding the #cgo noescape annotation for a C function, the cgo command won't generate _Cgo_use(pN), and the Go compiler won't force the object escape to heap. After adding the #cgo nocallback annotation for a C function, which means the C function won't callback to Go, if it do callback to Go, the Go process will crash. Fixes #56378 Change-Id: Ifdca070584e0d349c7b12276270e50089e481f7a GitHub-Last-Rev: f1a17b08b0590eca2670e404bbfedad5461df72f GitHub-Pull-Request: golang/go#60399 Reviewed-on: https://go-review.googlesource.com/c/go/+/497837 Reviewed-by: Ian Lance Taylor <iant@google.com> Reviewed-by: Bryan Mills <bcmills@google.com> Run-TryBot: Bryan Mills <bcmills@google.com> Auto-Submit: Bryan Mills <bcmills@google.com> TryBot-Result: Gopher Robot <gobot@golang.org>
Diffstat (limited to 'src/runtime')
-rw-r--r--src/runtime/cgo.go8
-rw-r--r--src/runtime/cgocall.go4
-rw-r--r--src/runtime/crash_cgo_test.go16
-rw-r--r--src/runtime/runtime2.go1
-rw-r--r--src/runtime/testdata/testprogcgo/cgonocallback.c9
-rw-r--r--src/runtime/testdata/testprogcgo/cgonocallback.go32
-rw-r--r--src/runtime/testdata/testprogcgo/cgonoescape.go84
7 files changed, 154 insertions, 0 deletions
diff --git a/src/runtime/cgo.go b/src/runtime/cgo.go
index 395303552c..40c8c748d3 100644
--- a/src/runtime/cgo.go
+++ b/src/runtime/cgo.go
@@ -61,3 +61,11 @@ func cgoUse(any) { throw("cgoUse should not be called") }
var cgoAlwaysFalse bool
var cgo_yield = &_cgo_yield
+
+func cgoNoCallback(v bool) {
+ g := getg()
+ if g.nocgocallback && v {
+ panic("runtime: unexpected setting cgoNoCallback")
+ }
+ g.nocgocallback = v
+}
diff --git a/src/runtime/cgocall.go b/src/runtime/cgocall.go
index f6e2f63813..802d6f2084 100644
--- a/src/runtime/cgocall.go
+++ b/src/runtime/cgocall.go
@@ -242,6 +242,10 @@ func cgocallbackg(fn, frame unsafe.Pointer, ctxt uintptr) {
osPreemptExtExit(gp.m)
+ if gp.nocgocallback {
+ panic("runtime: function marked with #cgo nocallback called back into Go")
+ }
+
cgocallbackg1(fn, frame, ctxt) // will call unlockOSThread
// At this point unlockOSThread has been called.
diff --git a/src/runtime/crash_cgo_test.go b/src/runtime/crash_cgo_test.go
index e1851808f3..88044caacf 100644
--- a/src/runtime/crash_cgo_test.go
+++ b/src/runtime/crash_cgo_test.go
@@ -753,6 +753,22 @@ func TestNeedmDeadlock(t *testing.T) {
}
}
+func TestCgoNoCallback(t *testing.T) {
+ got := runTestProg(t, "testprogcgo", "CgoNoCallback")
+ want := "function marked with #cgo nocallback called back into Go"
+ if !strings.Contains(got, want) {
+ t.Fatalf("did not see %q in output:\n%s", want, got)
+ }
+}
+
+func TestCgoNoEscape(t *testing.T) {
+ got := runTestProg(t, "testprogcgo", "CgoNoEscape")
+ want := "OK\n"
+ if got != want {
+ t.Fatalf("want %s, got %s\n", want, got)
+ }
+}
+
func TestCgoTracebackGoroutineProfile(t *testing.T) {
output := runTestProg(t, "testprogcgo", "GoroutineProfile")
want := "OK\n"
diff --git a/src/runtime/runtime2.go b/src/runtime/runtime2.go
index c3a3679302..8809b5d569 100644
--- a/src/runtime/runtime2.go
+++ b/src/runtime/runtime2.go
@@ -481,6 +481,7 @@ type g struct {
parkingOnChan atomic.Bool
raceignore int8 // ignore race detection events
+ nocgocallback bool // whether disable callback from C
tracking bool // whether we're tracking this G for sched latency statistics
trackingSeq uint8 // used to decide whether to track this G
trackingStamp int64 // timestamp of when the G last started being tracked
diff --git a/src/runtime/testdata/testprogcgo/cgonocallback.c b/src/runtime/testdata/testprogcgo/cgonocallback.c
new file mode 100644
index 0000000000..465a484361
--- /dev/null
+++ b/src/runtime/testdata/testprogcgo/cgonocallback.c
@@ -0,0 +1,9 @@
+// Copyright 2023 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.
+
+#include "_cgo_export.h"
+
+void runCShouldNotCallback() {
+ CallbackToGo();
+}
diff --git a/src/runtime/testdata/testprogcgo/cgonocallback.go b/src/runtime/testdata/testprogcgo/cgonocallback.go
new file mode 100644
index 0000000000..8cbbfd1957
--- /dev/null
+++ b/src/runtime/testdata/testprogcgo/cgonocallback.go
@@ -0,0 +1,32 @@
+// Copyright 2023 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 main
+
+// #cgo nocallback annotations for a C function means it should not callback to Go.
+// But it do callback to go in this test, Go should crash here.
+
+/*
+#cgo nocallback runCShouldNotCallback
+
+extern void runCShouldNotCallback();
+*/
+import "C"
+
+import (
+ "fmt"
+)
+
+func init() {
+ register("CgoNoCallback", CgoNoCallback)
+}
+
+//export CallbackToGo
+func CallbackToGo() {
+}
+
+func CgoNoCallback() {
+ C.runCShouldNotCallback()
+ fmt.Println("OK")
+}
diff --git a/src/runtime/testdata/testprogcgo/cgonoescape.go b/src/runtime/testdata/testprogcgo/cgonoescape.go
new file mode 100644
index 0000000000..056be44889
--- /dev/null
+++ b/src/runtime/testdata/testprogcgo/cgonoescape.go
@@ -0,0 +1,84 @@
+// Copyright 2023 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 main
+
+// #cgo noescape annotations for a C function means its arguments won't escape to heap.
+
+// We assume that there won't be 100 new allocated heap objects in other places,
+// i.e. runtime.ReadMemStats or other runtime background works.
+// So, the tests are:
+// 1. at least 100 new allocated heap objects after invoking withoutNoEscape 100 times.
+// 2. less than 100 new allocated heap objects after invoking withoutNoEscape 100 times.
+
+/*
+#cgo noescape runCWithNoEscape
+
+void runCWithNoEscape(void *p) {
+}
+void runCWithoutNoEscape(void *p) {
+}
+*/
+import "C"
+
+import (
+ "fmt"
+ "runtime"
+ "runtime/debug"
+ "unsafe"
+)
+
+const num = 100
+
+func init() {
+ register("CgoNoEscape", CgoNoEscape)
+}
+
+//go:noinline
+func withNoEscape() {
+ var str string
+ C.runCWithNoEscape(unsafe.Pointer(&str))
+}
+
+//go:noinline
+func withoutNoEscape() {
+ var str string
+ C.runCWithoutNoEscape(unsafe.Pointer(&str))
+}
+
+func CgoNoEscape() {
+ // make GC stop to see the heap objects allocated
+ debug.SetGCPercent(-1)
+
+ var stats runtime.MemStats
+ runtime.ReadMemStats(&stats)
+ preHeapObjects := stats.HeapObjects
+
+ for i := 0; i < num; i++ {
+ withNoEscape()
+ }
+
+ runtime.ReadMemStats(&stats)
+ nowHeapObjects := stats.HeapObjects
+
+ if nowHeapObjects-preHeapObjects >= num {
+ fmt.Printf("too many heap objects allocated, pre: %v, now: %v\n", preHeapObjects, nowHeapObjects)
+ }
+
+ runtime.ReadMemStats(&stats)
+ preHeapObjects = stats.HeapObjects
+
+ for i := 0; i < num; i++ {
+ withoutNoEscape()
+ }
+
+ runtime.ReadMemStats(&stats)
+ nowHeapObjects = stats.HeapObjects
+
+ if nowHeapObjects-preHeapObjects < num {
+ fmt.Printf("too few heap objects allocated, pre: %v, now: %v\n", preHeapObjects, nowHeapObjects)
+ }
+
+ fmt.Println("OK")
+}