// 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) runtime.AddCleanup(cLeak, func(x int) { **cLeak = 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)) // Add a cleanup that only temporarily leaks cNoLeak. runtime.AddCleanup(cNoLeak, func(x int) { **cNoLeak = 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") }