1// Copyright 2023 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5package runtime_test
6
7import (
8	"runtime"
9	"testing"
10	"time"
11	"unsafe"
12)
13
14type obj struct {
15	x int64
16	y int64
17	z int64
18}
19
20type objWith[T any] struct {
21	x int64
22	y int64
23	z int64
24	o T
25}
26
27var (
28	globalUintptr                uintptr
29	globalPtrToObj               = &obj{}
30	globalPtrToObjWithPtr        = &objWith[*uintptr]{}
31	globalPtrToRuntimeObj        = func() *obj { return &obj{} }()
32	globalPtrToRuntimeObjWithPtr = func() *objWith[*uintptr] { return &objWith[*uintptr]{} }()
33)
34
35func assertDidPanic(t *testing.T) {
36	if recover() == nil {
37		t.Fatal("did not panic")
38	}
39}
40
41func assertCgoCheckPanics(t *testing.T, p any) {
42	defer func() {
43		if recover() == nil {
44			t.Fatal("cgoCheckPointer() did not panic, make sure the tests run with cgocheck=1")
45		}
46	}()
47	runtime.CgoCheckPointer(p, true)
48}
49
50func TestPinnerSimple(t *testing.T) {
51	var pinner runtime.Pinner
52	p := new(obj)
53	addr := unsafe.Pointer(p)
54	if runtime.IsPinned(addr) {
55		t.Fatal("already marked as pinned")
56	}
57	pinner.Pin(p)
58	if !runtime.IsPinned(addr) {
59		t.Fatal("not marked as pinned")
60	}
61	if runtime.GetPinCounter(addr) != nil {
62		t.Fatal("pin counter should not exist")
63	}
64	pinner.Unpin()
65	if runtime.IsPinned(addr) {
66		t.Fatal("still marked as pinned")
67	}
68}
69
70func TestPinnerPinKeepsAliveAndReleases(t *testing.T) {
71	var pinner runtime.Pinner
72	p := new(obj)
73	done := make(chan struct{})
74	runtime.SetFinalizer(p, func(any) {
75		done <- struct{}{}
76	})
77	pinner.Pin(p)
78	p = nil
79	runtime.GC()
80	runtime.GC()
81	select {
82	case <-done:
83		t.Fatal("Pin() didn't keep object alive")
84	case <-time.After(time.Millisecond * 10):
85		break
86	}
87	pinner.Unpin()
88	runtime.GC()
89	runtime.GC()
90	select {
91	case <-done:
92		break
93	case <-time.After(time.Second):
94		t.Fatal("Unpin() didn't release object")
95	}
96}
97
98func TestPinnerMultiplePinsSame(t *testing.T) {
99	const N = 100
100	var pinner runtime.Pinner
101	p := new(obj)
102	addr := unsafe.Pointer(p)
103	if runtime.IsPinned(addr) {
104		t.Fatal("already marked as pinned")
105	}
106	for i := 0; i < N; i++ {
107		pinner.Pin(p)
108	}
109	if !runtime.IsPinned(addr) {
110		t.Fatal("not marked as pinned")
111	}
112	if cnt := runtime.GetPinCounter(addr); cnt == nil || *cnt != N-1 {
113		t.Fatalf("pin counter incorrect: %d", *cnt)
114	}
115	pinner.Unpin()
116	if runtime.IsPinned(addr) {
117		t.Fatal("still marked as pinned")
118	}
119	if runtime.GetPinCounter(addr) != nil {
120		t.Fatal("pin counter was not deleted")
121	}
122}
123
124func TestPinnerTwoPinner(t *testing.T) {
125	var pinner1, pinner2 runtime.Pinner
126	p := new(obj)
127	addr := unsafe.Pointer(p)
128	if runtime.IsPinned(addr) {
129		t.Fatal("already marked as pinned")
130	}
131	pinner1.Pin(p)
132	if !runtime.IsPinned(addr) {
133		t.Fatal("not marked as pinned")
134	}
135	if runtime.GetPinCounter(addr) != nil {
136		t.Fatal("pin counter should not exist")
137	}
138	pinner2.Pin(p)
139	if !runtime.IsPinned(addr) {
140		t.Fatal("not marked as pinned")
141	}
142	if cnt := runtime.GetPinCounter(addr); cnt == nil || *cnt != 1 {
143		t.Fatalf("pin counter incorrect: %d", *cnt)
144	}
145	pinner1.Unpin()
146	if !runtime.IsPinned(addr) {
147		t.Fatal("not marked as pinned")
148	}
149	if runtime.GetPinCounter(addr) != nil {
150		t.Fatal("pin counter should not exist")
151	}
152	pinner2.Unpin()
153	if runtime.IsPinned(addr) {
154		t.Fatal("still marked as pinned")
155	}
156	if runtime.GetPinCounter(addr) != nil {
157		t.Fatal("pin counter was not deleted")
158	}
159}
160
161func TestPinnerPinZerosizeObj(t *testing.T) {
162	var pinner runtime.Pinner
163	defer pinner.Unpin()
164	p := new(struct{})
165	pinner.Pin(p)
166	if !runtime.IsPinned(unsafe.Pointer(p)) {
167		t.Fatal("not marked as pinned")
168	}
169}
170
171func TestPinnerPinGlobalPtr(t *testing.T) {
172	var pinner runtime.Pinner
173	defer pinner.Unpin()
174	pinner.Pin(globalPtrToObj)
175	pinner.Pin(globalPtrToObjWithPtr)
176	pinner.Pin(globalPtrToRuntimeObj)
177	pinner.Pin(globalPtrToRuntimeObjWithPtr)
178}
179
180func TestPinnerPinTinyObj(t *testing.T) {
181	var pinner runtime.Pinner
182	const N = 64
183	var addr [N]unsafe.Pointer
184	for i := 0; i < N; i++ {
185		p := new(bool)
186		addr[i] = unsafe.Pointer(p)
187		pinner.Pin(p)
188		pinner.Pin(p)
189		if !runtime.IsPinned(addr[i]) {
190			t.Fatalf("not marked as pinned: %d", i)
191		}
192		if cnt := runtime.GetPinCounter(addr[i]); cnt == nil || *cnt == 0 {
193			t.Fatalf("pin counter incorrect: %d, %d", *cnt, i)
194		}
195	}
196	pinner.Unpin()
197	for i := 0; i < N; i++ {
198		if runtime.IsPinned(addr[i]) {
199			t.Fatal("still marked as pinned")
200		}
201		if runtime.GetPinCounter(addr[i]) != nil {
202			t.Fatal("pin counter should not exist")
203		}
204	}
205}
206
207func TestPinnerInterface(t *testing.T) {
208	var pinner runtime.Pinner
209	o := new(obj)
210	ifc := any(o)
211	pinner.Pin(&ifc)
212	if !runtime.IsPinned(unsafe.Pointer(&ifc)) {
213		t.Fatal("not marked as pinned")
214	}
215	if runtime.IsPinned(unsafe.Pointer(o)) {
216		t.Fatal("marked as pinned")
217	}
218	pinner.Unpin()
219	pinner.Pin(ifc)
220	if !runtime.IsPinned(unsafe.Pointer(o)) {
221		t.Fatal("not marked as pinned")
222	}
223	if runtime.IsPinned(unsafe.Pointer(&ifc)) {
224		t.Fatal("marked as pinned")
225	}
226	pinner.Unpin()
227}
228
229func TestPinnerPinNonPtrPanics(t *testing.T) {
230	var pinner runtime.Pinner
231	defer pinner.Unpin()
232	var i int
233	defer assertDidPanic(t)
234	pinner.Pin(i)
235}
236
237func TestPinnerReuse(t *testing.T) {
238	var pinner runtime.Pinner
239	p := new(obj)
240	p2 := &p
241	assertCgoCheckPanics(t, p2)
242	pinner.Pin(p)
243	runtime.CgoCheckPointer(p2, true)
244	pinner.Unpin()
245	assertCgoCheckPanics(t, p2)
246	pinner.Pin(p)
247	runtime.CgoCheckPointer(p2, true)
248	pinner.Unpin()
249}
250
251func TestPinnerEmptyUnpin(t *testing.T) {
252	var pinner runtime.Pinner
253	pinner.Unpin()
254	pinner.Unpin()
255}
256
257func TestPinnerLeakPanics(t *testing.T) {
258	old := runtime.GetPinnerLeakPanic()
259	func() {
260		defer assertDidPanic(t)
261		old()
262	}()
263	done := make(chan struct{})
264	runtime.SetPinnerLeakPanic(func() {
265		done <- struct{}{}
266	})
267	func() {
268		var pinner runtime.Pinner
269		p := new(obj)
270		pinner.Pin(p)
271	}()
272	runtime.GC()
273	runtime.GC()
274	select {
275	case <-done:
276		break
277	case <-time.After(time.Second):
278		t.Fatal("leak didn't make GC to panic")
279	}
280	runtime.SetPinnerLeakPanic(old)
281}
282
283func TestPinnerCgoCheckPtr2Ptr(t *testing.T) {
284	var pinner runtime.Pinner
285	defer pinner.Unpin()
286	p := new(obj)
287	p2 := &objWith[*obj]{o: p}
288	assertCgoCheckPanics(t, p2)
289	pinner.Pin(p)
290	runtime.CgoCheckPointer(p2, true)
291}
292
293func TestPinnerCgoCheckPtr2UnsafePtr(t *testing.T) {
294	var pinner runtime.Pinner
295	defer pinner.Unpin()
296	p := unsafe.Pointer(new(obj))
297	p2 := &objWith[unsafe.Pointer]{o: p}
298	assertCgoCheckPanics(t, p2)
299	pinner.Pin(p)
300	runtime.CgoCheckPointer(p2, true)
301}
302
303func TestPinnerCgoCheckPtr2UnknownPtr(t *testing.T) {
304	var pinner runtime.Pinner
305	defer pinner.Unpin()
306	p := unsafe.Pointer(new(obj))
307	p2 := &p
308	func() {
309		defer assertDidPanic(t)
310		runtime.CgoCheckPointer(p2, nil)
311	}()
312	pinner.Pin(p)
313	runtime.CgoCheckPointer(p2, nil)
314}
315
316func TestPinnerCgoCheckInterface(t *testing.T) {
317	var pinner runtime.Pinner
318	defer pinner.Unpin()
319	var ifc any
320	var o obj
321	ifc = &o
322	p := &ifc
323	assertCgoCheckPanics(t, p)
324	pinner.Pin(&o)
325	runtime.CgoCheckPointer(p, true)
326}
327
328func TestPinnerCgoCheckSlice(t *testing.T) {
329	var pinner runtime.Pinner
330	defer pinner.Unpin()
331	sl := []int{1, 2, 3}
332	assertCgoCheckPanics(t, &sl)
333	pinner.Pin(&sl[0])
334	runtime.CgoCheckPointer(&sl, true)
335}
336
337func TestPinnerCgoCheckString(t *testing.T) {
338	var pinner runtime.Pinner
339	defer pinner.Unpin()
340	b := []byte("foobar")
341	str := unsafe.String(&b[0], 6)
342	assertCgoCheckPanics(t, &str)
343	pinner.Pin(&b[0])
344	runtime.CgoCheckPointer(&str, true)
345}
346
347func TestPinnerCgoCheckPinned2UnpinnedPanics(t *testing.T) {
348	var pinner runtime.Pinner
349	defer pinner.Unpin()
350	p := new(obj)
351	p2 := &objWith[*obj]{o: p}
352	assertCgoCheckPanics(t, p2)
353	pinner.Pin(p2)
354	assertCgoCheckPanics(t, p2)
355}
356
357func TestPinnerCgoCheckPtr2Pinned2Unpinned(t *testing.T) {
358	var pinner runtime.Pinner
359	defer pinner.Unpin()
360	p := new(obj)
361	p2 := &objWith[*obj]{o: p}
362	p3 := &objWith[*objWith[*obj]]{o: p2}
363	assertCgoCheckPanics(t, p2)
364	assertCgoCheckPanics(t, p3)
365	pinner.Pin(p2)
366	assertCgoCheckPanics(t, p2)
367	assertCgoCheckPanics(t, p3)
368	pinner.Pin(p)
369	runtime.CgoCheckPointer(p2, true)
370	runtime.CgoCheckPointer(p3, true)
371}
372
373func BenchmarkPinnerPinUnpinBatch(b *testing.B) {
374	const Batch = 1000
375	var data [Batch]*obj
376	for i := 0; i < Batch; i++ {
377		data[i] = new(obj)
378	}
379	b.ResetTimer()
380	for n := 0; n < b.N; n++ {
381		var pinner runtime.Pinner
382		for i := 0; i < Batch; i++ {
383			pinner.Pin(data[i])
384		}
385		pinner.Unpin()
386	}
387}
388
389func BenchmarkPinnerPinUnpinBatchDouble(b *testing.B) {
390	const Batch = 1000
391	var data [Batch]*obj
392	for i := 0; i < Batch; i++ {
393		data[i] = new(obj)
394	}
395	b.ResetTimer()
396	for n := 0; n < b.N; n++ {
397		var pinner runtime.Pinner
398		for i := 0; i < Batch; i++ {
399			pinner.Pin(data[i])
400			pinner.Pin(data[i])
401		}
402		pinner.Unpin()
403	}
404}
405
406func BenchmarkPinnerPinUnpinBatchTiny(b *testing.B) {
407	const Batch = 1000
408	var data [Batch]*bool
409	for i := 0; i < Batch; i++ {
410		data[i] = new(bool)
411	}
412	b.ResetTimer()
413	for n := 0; n < b.N; n++ {
414		var pinner runtime.Pinner
415		for i := 0; i < Batch; i++ {
416			pinner.Pin(data[i])
417		}
418		pinner.Unpin()
419	}
420}
421
422func BenchmarkPinnerPinUnpin(b *testing.B) {
423	p := new(obj)
424	for n := 0; n < b.N; n++ {
425		var pinner runtime.Pinner
426		pinner.Pin(p)
427		pinner.Unpin()
428	}
429}
430
431func BenchmarkPinnerPinUnpinTiny(b *testing.B) {
432	p := new(bool)
433	for n := 0; n < b.N; n++ {
434		var pinner runtime.Pinner
435		pinner.Pin(p)
436		pinner.Unpin()
437	}
438}
439
440func BenchmarkPinnerPinUnpinDouble(b *testing.B) {
441	p := new(obj)
442	for n := 0; n < b.N; n++ {
443		var pinner runtime.Pinner
444		pinner.Pin(p)
445		pinner.Pin(p)
446		pinner.Unpin()
447	}
448}
449
450func BenchmarkPinnerPinUnpinParallel(b *testing.B) {
451	b.RunParallel(func(pb *testing.PB) {
452		p := new(obj)
453		for pb.Next() {
454			var pinner runtime.Pinner
455			pinner.Pin(p)
456			pinner.Unpin()
457		}
458	})
459}
460
461func BenchmarkPinnerPinUnpinParallelTiny(b *testing.B) {
462	b.RunParallel(func(pb *testing.PB) {
463		p := new(bool)
464		for pb.Next() {
465			var pinner runtime.Pinner
466			pinner.Pin(p)
467			pinner.Unpin()
468		}
469	})
470}
471
472func BenchmarkPinnerPinUnpinParallelDouble(b *testing.B) {
473	b.RunParallel(func(pb *testing.PB) {
474		p := new(obj)
475		for pb.Next() {
476			var pinner runtime.Pinner
477			pinner.Pin(p)
478			pinner.Pin(p)
479			pinner.Unpin()
480		}
481	})
482}
483
484func BenchmarkPinnerIsPinnedOnPinned(b *testing.B) {
485	var pinner runtime.Pinner
486	ptr := new(obj)
487	pinner.Pin(ptr)
488	b.ResetTimer()
489	for n := 0; n < b.N; n++ {
490		runtime.IsPinned(unsafe.Pointer(ptr))
491	}
492	pinner.Unpin()
493}
494
495func BenchmarkPinnerIsPinnedOnUnpinned(b *testing.B) {
496	ptr := new(obj)
497	b.ResetTimer()
498	for n := 0; n < b.N; n++ {
499		runtime.IsPinned(unsafe.Pointer(ptr))
500	}
501}
502
503func BenchmarkPinnerIsPinnedOnPinnedParallel(b *testing.B) {
504	var pinner runtime.Pinner
505	ptr := new(obj)
506	pinner.Pin(ptr)
507	b.ResetTimer()
508	b.RunParallel(func(pb *testing.PB) {
509		for pb.Next() {
510			runtime.IsPinned(unsafe.Pointer(ptr))
511		}
512	})
513	pinner.Unpin()
514}
515
516func BenchmarkPinnerIsPinnedOnUnpinnedParallel(b *testing.B) {
517	ptr := new(obj)
518	b.ResetTimer()
519	b.RunParallel(func(pb *testing.PB) {
520		for pb.Next() {
521			runtime.IsPinned(unsafe.Pointer(ptr))
522		}
523	})
524}
525
526// const string data is not in span.
527func TestPinnerConstStringData(t *testing.T) {
528	var pinner runtime.Pinner
529	str := "test-const-string"
530	p := unsafe.StringData(str)
531	addr := unsafe.Pointer(p)
532	if !runtime.IsPinned(addr) {
533		t.Fatal("not marked as pinned")
534	}
535	pinner.Pin(p)
536	pinner.Unpin()
537	if !runtime.IsPinned(addr) {
538		t.Fatal("not marked as pinned")
539	}
540}
541