| // Copyright 2024 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 | |
| import ( | |
| "internal/asan" | |
| "internal/race" | |
| "runtime" | |
| "runtime/debug" | |
| "unsafe" | |
| ) | |
| func init() { | |
| register("DetectFinalizerAndCleanupLeaks", DetectFinalizerAndCleanupLeaks) | |
| } | |
| type tiny uint8 | |
| var tinySink *tiny | |
| // Intended to be run only with `GODEBUG=checkfinalizers=1`. | |
| func DetectFinalizerAndCleanupLeaks() { | |
| type T *int | |
| defer debug.SetGCPercent(debug.SetGCPercent(-1)) | |
| // Leak a cleanup. | |
| cLeak := new(T) | |
| // Use an extra closure to avoid the simple | |
| // checking done by AddCleanup. | |
| var closeOverCLeak func(int) | |
| closeOverCLeak = func(x int) { | |
| // Use recursion to avoid inlining. | |
| if x <= 0 { | |
| **cLeak = x | |
| } else { | |
| closeOverCLeak(x - 1) | |
| } | |
| } | |
| runtime.AddCleanup(cLeak, func(x int) { | |
| closeOverCLeak(x) | |
| }, int(0)) | |
| // Have a regular cleanup to make sure it doesn't trip the detector. | |
| cNoLeak := new(T) | |
| runtime.AddCleanup(cNoLeak, func(_ int) {}, int(0)) | |
| // Like closeOverCLeak. | |
| var closeOverCNoLeak func(int) | |
| closeOverCNoLeak = func(x int) { | |
| if x <= 0 { | |
| **cNoLeak = x | |
| } else { | |
| closeOverCNoLeak(x - 1) | |
| } | |
| } | |
| // Add a cleanup that only temporarily leaks cNoLeak. | |
| runtime.AddCleanup(cNoLeak, func(x int) { | |
| closeOverCNoLeak(x) | |
| }, int(0)).Stop() | |
| if !asan.Enabled && !race.Enabled { | |
| // Ensure we create an allocation into a tiny block that shares space among several values. | |
| // | |
| // Don't do this with ASAN and in race mode, where the tiny allocator is disabled. | |
| // We might just loop forever here in that case. | |
| var ctLeak *tiny | |
| for { | |
| tinySink = ctLeak | |
| ctLeak = new(tiny) | |
| *ctLeak = tiny(55) | |
| // Make sure the address is an odd value. This is sufficient to | |
| // be certain that we're sharing a block with another value and | |
| // trip the detector. | |
| if uintptr(unsafe.Pointer(ctLeak))%2 != 0 { | |
| break | |
| } | |
| } | |
| runtime.AddCleanup(ctLeak, func(_ struct{}) {}, struct{}{}) | |
| } | |
| // Leak a finalizer. | |
| fLeak := new(T) | |
| runtime.SetFinalizer(fLeak, func(_ *T) { | |
| **fLeak = 12 | |
| }) | |
| // Have a regular finalizer to make sure it doesn't trip the detector. | |
| fNoLeak := new(T) | |
| runtime.SetFinalizer(fNoLeak, func(x *T) { | |
| **x = 51 | |
| }) | |
| // runtime.GC here should crash. | |
| runtime.GC() | |
| println("OK") | |
| } | |