Source file src/cmd/compile/internal/test/issue_81478_test.go

     1  // Copyright 2026 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  
     5  package test
     6  
     7  import (
     8  	"internal/platform"
     9  	"internal/testenv"
    10  	"os"
    11  	"path/filepath"
    12  	"runtime"
    13  	"strings"
    14  	"testing"
    15  )
    16  
    17  // Test that compiler-generated wrappers that end in a tail call still tell the
    18  // race detector that the wrapper has returned. This is a regression test for
    19  // #81478, where the missing racefuncexit call made every wrapper call leak an
    20  // entry in the race detector's shadow stack, which in turn made later
    21  // synchronization operations dramatically slower.
    22  func TestIssue81478(t *testing.T) {
    23  	if !platform.RaceDetectorSupported(runtime.GOOS, runtime.GOARCH) {
    24  		t.Skipf("race detector not supported on %s/%s", runtime.GOOS, runtime.GOARCH)
    25  	}
    26  	testenv.MustHaveGoBuild(t)
    27  
    28  	dir := t.TempDir()
    29  	src := filepath.Join(dir, "x.go")
    30  	if err := os.WriteFile(src, []byte(issue81478src), 0644); err != nil {
    31  		t.Fatalf("could not write file: %v", err)
    32  	}
    33  
    34  	cmd := testenv.Command(t, testenv.GoToolPath(t), "tool", "compile", "-race", "-p=main", "-S", "-o", filepath.Join(dir, "x.o"), src)
    35  	out, err := cmd.CombinedOutput()
    36  	if err != nil {
    37  		t.Fatalf("compile failed: %v\n%s", err, out)
    38  	}
    39  
    40  	// W.F tail calls through an interface, S.G tail calls a static function.
    41  	// Whether the tail call is actually emitted is architecture-dependent, but
    42  	// either way the enter/exit instrumentation must be balanced.
    43  	for _, wrapper := range []string{"main.(*W).F", "main.(*S).G"} {
    44  		body, ok := funcBody(string(out), wrapper)
    45  		if !ok {
    46  			t.Errorf("no assembly found for %s", wrapper)
    47  			continue
    48  		}
    49  		enter := strings.Count(body, "runtime.racefuncenter")
    50  		exit := strings.Count(body, "runtime.racefuncexit")
    51  		if enter == 0 {
    52  			t.Errorf("%s: not instrumented for the race detector\n%s", wrapper, body)
    53  		} else if enter != exit {
    54  			t.Errorf("%s: unbalanced race instrumentation: %d racefuncenter, %d racefuncexit\n%s", wrapper, enter, exit, body)
    55  		}
    56  	}
    57  }
    58  
    59  // funcBody returns the part of the -S output that belongs to the symbol fn:
    60  // its header line plus the indented instruction and relocation lines below it.
    61  func funcBody(out, fn string) (string, bool) {
    62  	lines := strings.Split(out, "\n")
    63  	for i, line := range lines {
    64  		if !strings.HasPrefix(line, fn+" STEXT") {
    65  			continue
    66  		}
    67  		end := i + 1
    68  		for end < len(lines) && strings.HasPrefix(lines[end], "\t") {
    69  			end++
    70  		}
    71  		return strings.Join(lines[i:end], "\n"), true
    72  	}
    73  	return "", false
    74  }
    75  
    76  var issue81478src = `
    77  package main
    78  
    79  type I interface{ F() }
    80  
    81  type T struct{ n int }
    82  
    83  func (t *T) F() { t.n++ }
    84  
    85  //go:noinline
    86  func (t *T) G() { t.n++ }
    87  
    88  // W.F is a wrapper around an embedded interface method.
    89  type W struct{ I }
    90  
    91  // S.G is a wrapper around an embedded pointer's method.
    92  type S struct{ *T }
    93  
    94  var _ I = &W{}
    95  var _ = (&S{}).G
    96  
    97  func main() {}
    98  `
    99  

View as plain text