// 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")
}
