modernc.org/gc@v1.0.1-0.20240304020402-f0dba7c97c2b/testdata/errchk/test/nosplit.go (about)

     1  // +build !nacl
     2  // run
     3  
     4  // Copyright 2014 The Go Authors. All rights reserved.
     5  // Use of this source code is governed by a BSD-style
     6  // license that can be found in the LICENSE file.
     7  
     8  package main
     9  
    10  import (
    11  	"bytes"
    12  	"fmt"
    13  	"io/ioutil"
    14  	"log"
    15  	"os"
    16  	"os/exec"
    17  	"path/filepath"
    18  	"regexp"
    19  	"runtime"
    20  	"strconv"
    21  	"strings"
    22  )
    23  
    24  var tests = `
    25  # These are test cases for the linker analysis that detects chains of
    26  # nosplit functions that would cause a stack overflow.
    27  #
    28  # Lines beginning with # are comments.
    29  #
    30  # Each test case describes a sequence of functions, one per line.
    31  # Each function definition is the function name, then the frame size,
    32  # then optionally the keyword 'nosplit', then the body of the function.
    33  # The body is assembly code, with some shorthands.
    34  # The shorthand 'call x' stands for CALL x(SB).
    35  # The shorthand 'callind' stands for 'CALL R0', where R0 is a register.
    36  # Each test case must define a function named main, and it must be first.
    37  # That is, a line beginning "main " indicates the start of a new test case.
    38  # Within a stanza, ; can be used instead of \n to separate lines.
    39  #
    40  # After the function definition, the test case ends with an optional
    41  # REJECT line, specifying the architectures on which the case should
    42  # be rejected. "REJECT" without any architectures means reject on all architectures.
    43  # The linker should accept the test case on systems not explicitly rejected.
    44  #
    45  # 64-bit systems do not attempt to execute test cases with frame sizes
    46  # that are only 32-bit aligned.
    47  
    48  # Ordinary function should work
    49  main 0
    50  
    51  # Large frame marked nosplit is always wrong.
    52  main 10000 nosplit
    53  REJECT
    54  
    55  # Calling a large frame is okay.
    56  main 0 call big
    57  big 10000
    58  
    59  # But not if the frame is nosplit.
    60  main 0 call big
    61  big 10000 nosplit
    62  REJECT
    63  
    64  # Recursion is okay.
    65  main 0 call main
    66  
    67  # Recursive nosplit runs out of space.
    68  main 0 nosplit call main
    69  REJECT
    70  
    71  # Chains of ordinary functions okay.
    72  main 0 call f1
    73  f1 80 call f2
    74  f2 80
    75  
    76  # Chains of nosplit must fit in the stack limit, 128 bytes.
    77  main 0 call f1
    78  f1 80 nosplit call f2
    79  f2 80 nosplit
    80  REJECT
    81  
    82  # Larger chains.
    83  main 0 call f1
    84  f1 16 call f2
    85  f2 16 call f3
    86  f3 16 call f4
    87  f4 16 call f5
    88  f5 16 call f6
    89  f6 16 call f7
    90  f7 16 call f8
    91  f8 16 call end
    92  end 1000
    93  
    94  main 0 call f1
    95  f1 16 nosplit call f2
    96  f2 16 nosplit call f3
    97  f3 16 nosplit call f4
    98  f4 16 nosplit call f5
    99  f5 16 nosplit call f6
   100  f6 16 nosplit call f7
   101  f7 16 nosplit call f8
   102  f8 16 nosplit call end
   103  end 1000
   104  REJECT
   105  
   106  # Test cases near the 128-byte limit.
   107  
   108  # Ordinary stack split frame is always okay.
   109  main 112
   110  main 116
   111  main 120
   112  main 124
   113  main 128
   114  main 132
   115  main 136
   116  
   117  # A nosplit leaf can use the whole 128-CallSize bytes available on entry.
   118  # (CallSize is 32 on ppc64, 8 on amd64 for frame pointer.)
   119  main 96 nosplit
   120  main 100 nosplit; REJECT ppc64 ppc64le
   121  main 104 nosplit; REJECT ppc64 ppc64le
   122  main 108 nosplit; REJECT ppc64 ppc64le
   123  main 112 nosplit; REJECT ppc64 ppc64le
   124  main 116 nosplit; REJECT ppc64 ppc64le
   125  main 120 nosplit; REJECT ppc64 ppc64le amd64
   126  main 124 nosplit; REJECT ppc64 ppc64le amd64
   127  main 128 nosplit; REJECT
   128  main 132 nosplit; REJECT
   129  main 136 nosplit; REJECT
   130  
   131  # Calling a nosplit function from a nosplit function requires
   132  # having room for the saved caller PC and the called frame.
   133  # Because ARM doesn't save LR in the leaf, it gets an extra 4 bytes.
   134  # Because arm64 doesn't save LR in the leaf, it gets an extra 8 bytes.
   135  # ppc64 doesn't save LR in the leaf, but CallSize is 32, so it gets 24 bytes.
   136  # Because AMD64 uses frame pointer, it has 8 fewer bytes.
   137  main 96 nosplit call f; f 0 nosplit
   138  main 100 nosplit call f; f 0 nosplit; REJECT ppc64 ppc64le
   139  main 104 nosplit call f; f 0 nosplit; REJECT ppc64 ppc64le
   140  main 108 nosplit call f; f 0 nosplit; REJECT ppc64 ppc64le
   141  main 112 nosplit call f; f 0 nosplit; REJECT ppc64 ppc64le amd64
   142  main 116 nosplit call f; f 0 nosplit; REJECT ppc64 ppc64le amd64
   143  main 120 nosplit call f; f 0 nosplit; REJECT ppc64 ppc64le amd64
   144  main 124 nosplit call f; f 0 nosplit; REJECT ppc64 ppc64le amd64 386
   145  main 128 nosplit call f; f 0 nosplit; REJECT
   146  main 132 nosplit call f; f 0 nosplit; REJECT
   147  main 136 nosplit call f; f 0 nosplit; REJECT
   148  
   149  # Calling a splitting function from a nosplit function requires
   150  # having room for the saved caller PC of the call but also the
   151  # saved caller PC for the call to morestack.
   152  # Architectures differ in the same way as before.
   153  main 96 nosplit call f; f 0 call f
   154  main 100 nosplit call f; f 0 call f; REJECT ppc64 ppc64le
   155  main 104 nosplit call f; f 0 call f; REJECT ppc64 ppc64le amd64
   156  main 108 nosplit call f; f 0 call f; REJECT ppc64 ppc64le amd64
   157  main 112 nosplit call f; f 0 call f; REJECT ppc64 ppc64le amd64
   158  main 116 nosplit call f; f 0 call f; REJECT ppc64 ppc64le amd64
   159  main 120 nosplit call f; f 0 call f; REJECT ppc64 ppc64le amd64 386
   160  main 124 nosplit call f; f 0 call f; REJECT ppc64 ppc64le amd64 386
   161  main 128 nosplit call f; f 0 call f; REJECT
   162  main 132 nosplit call f; f 0 call f; REJECT
   163  main 136 nosplit call f; f 0 call f; REJECT
   164  
   165  # Indirect calls are assumed to be splitting functions.
   166  main 96 nosplit callind
   167  main 100 nosplit callind; REJECT ppc64 ppc64le
   168  main 104 nosplit callind; REJECT ppc64 ppc64le amd64
   169  main 108 nosplit callind; REJECT ppc64 ppc64le amd64
   170  main 112 nosplit callind; REJECT ppc64 ppc64le amd64
   171  main 116 nosplit callind; REJECT ppc64 ppc64le amd64
   172  main 120 nosplit callind; REJECT ppc64 ppc64le amd64 386
   173  main 124 nosplit callind; REJECT ppc64 ppc64le amd64 386
   174  main 128 nosplit callind; REJECT
   175  main 132 nosplit callind; REJECT
   176  main 136 nosplit callind; REJECT
   177  
   178  # Issue 7623
   179  main 0 call f; f 112
   180  main 0 call f; f 116
   181  main 0 call f; f 120
   182  main 0 call f; f 124
   183  main 0 call f; f 128
   184  main 0 call f; f 132
   185  main 0 call f; f 136
   186  `
   187  
   188  var (
   189  	commentRE = regexp.MustCompile(`(?m)^#.*`)
   190  	rejectRE  = regexp.MustCompile(`(?s)\A(.+?)((\n|; *)REJECT(.*))?\z`)
   191  	lineRE    = regexp.MustCompile(`(\w+) (\d+)( nosplit)?(.*)`)
   192  	callRE    = regexp.MustCompile(`\bcall (\w+)\b`)
   193  	callindRE = regexp.MustCompile(`\bcallind\b`)
   194  )
   195  
   196  func main() {
   197  	goarch := os.Getenv("GOARCH")
   198  	if goarch == "" {
   199  		goarch = runtime.GOARCH
   200  	}
   201  
   202  	version, err := exec.Command("go", "tool", "compile", "-V").Output()
   203  	if err != nil {
   204  		bug()
   205  		fmt.Printf("running go tool compile -V: %v\n", err)
   206  		return
   207  	}
   208  	if s := string(version); goarch == "amd64" && strings.Contains(s, "X:") && !strings.Contains(s, "framepointer") {
   209  		// Skip this test if framepointer is NOT enabled on AMD64
   210  		return
   211  	}
   212  
   213  	dir, err := ioutil.TempDir("", "go-test-nosplit")
   214  	if err != nil {
   215  		bug()
   216  		fmt.Printf("creating temp dir: %v\n", err)
   217  		return
   218  	}
   219  	defer os.RemoveAll(dir)
   220  
   221  	tests = strings.Replace(tests, "\t", " ", -1)
   222  	tests = commentRE.ReplaceAllString(tests, "")
   223  
   224  	nok := 0
   225  	nfail := 0
   226  TestCases:
   227  	for len(tests) > 0 {
   228  		var stanza string
   229  		i := strings.Index(tests, "\nmain ")
   230  		if i < 0 {
   231  			stanza, tests = tests, ""
   232  		} else {
   233  			stanza, tests = tests[:i], tests[i+1:]
   234  		}
   235  
   236  		m := rejectRE.FindStringSubmatch(stanza)
   237  		if m == nil {
   238  			bug()
   239  			fmt.Printf("invalid stanza:\n\t%s\n", indent(stanza))
   240  			continue
   241  		}
   242  		lines := strings.TrimSpace(m[1])
   243  		reject := false
   244  		if m[2] != "" {
   245  			if strings.TrimSpace(m[4]) == "" {
   246  				reject = true
   247  			} else {
   248  				for _, rej := range strings.Fields(m[4]) {
   249  					if rej == goarch {
   250  						reject = true
   251  					}
   252  				}
   253  			}
   254  		}
   255  		if lines == "" && !reject {
   256  			continue
   257  		}
   258  
   259  		var gobuf bytes.Buffer
   260  		fmt.Fprintf(&gobuf, "package main\n")
   261  
   262  		var buf bytes.Buffer
   263  		ptrSize := 4
   264  		switch goarch {
   265  		case "mips", "mipsle":
   266  			fmt.Fprintf(&buf, "#define REGISTER (R0)\n")
   267  		case "mips64", "mips64le":
   268  			ptrSize = 8
   269  			fmt.Fprintf(&buf, "#define REGISTER (R0)\n")
   270  		case "ppc64", "ppc64le":
   271  			ptrSize = 8
   272  			fmt.Fprintf(&buf, "#define REGISTER (CTR)\n")
   273  		case "arm":
   274  			fmt.Fprintf(&buf, "#define REGISTER (R0)\n")
   275  		case "arm64":
   276  			ptrSize = 8
   277  			fmt.Fprintf(&buf, "#define REGISTER (R0)\n")
   278  		case "amd64":
   279  			ptrSize = 8
   280  			fmt.Fprintf(&buf, "#define REGISTER AX\n")
   281  		case "s390x":
   282  			ptrSize = 8
   283  			fmt.Fprintf(&buf, "#define REGISTER R10\n")
   284  		default:
   285  			fmt.Fprintf(&buf, "#define REGISTER AX\n")
   286  		}
   287  
   288  		for _, line := range strings.Split(lines, "\n") {
   289  			line = strings.TrimSpace(line)
   290  			if line == "" {
   291  				continue
   292  			}
   293  			for i, subline := range strings.Split(line, ";") {
   294  				subline = strings.TrimSpace(subline)
   295  				if subline == "" {
   296  					continue
   297  				}
   298  				m := lineRE.FindStringSubmatch(subline)
   299  				if m == nil {
   300  					bug()
   301  					fmt.Printf("invalid function line: %s\n", subline)
   302  					continue TestCases
   303  				}
   304  				name := m[1]
   305  				size, _ := strconv.Atoi(m[2])
   306  
   307  				// The limit was originally 128 but is now 752 (880-128).
   308  				// Instead of rewriting the test cases above, adjust
   309  				// the first stack frame to use up the extra bytes.
   310  				if i == 0 {
   311  					size += (880 - 128) - 128
   312  					// Noopt builds have a larger stackguard.
   313  					// See ../src/cmd/dist/buildruntime.go:stackGuardMultiplier
   314  					// This increase is included in objabi.StackGuard
   315  					for _, s := range strings.Split(os.Getenv("GO_GCFLAGS"), " ") {
   316  						if s == "-N" {
   317  							size += 880
   318  						}
   319  					}
   320  				}
   321  
   322  				if size%ptrSize == 4 || goarch == "arm64" && size != 0 && (size+8)%16 != 0 {
   323  					continue TestCases
   324  				}
   325  				nosplit := m[3]
   326  				body := m[4]
   327  
   328  				if nosplit != "" {
   329  					nosplit = ",7"
   330  				} else {
   331  					nosplit = ",0"
   332  				}
   333  				body = callRE.ReplaceAllString(body, "CALL ·$1(SB);")
   334  				body = callindRE.ReplaceAllString(body, "CALL REGISTER;")
   335  
   336  				fmt.Fprintf(&gobuf, "func %s()\n", name)
   337  				fmt.Fprintf(&buf, "TEXT ·%s(SB)%s,$%d-0\n\t%s\n\tRET\n\n", name, nosplit, size, body)
   338  			}
   339  		}
   340  
   341  		if err := ioutil.WriteFile(filepath.Join(dir, "asm.s"), buf.Bytes(), 0666); err != nil {
   342  			log.Fatal(err)
   343  		}
   344  		if err := ioutil.WriteFile(filepath.Join(dir, "main.go"), gobuf.Bytes(), 0666); err != nil {
   345  			log.Fatal(err)
   346  		}
   347  
   348  		cmd := exec.Command("go", "build")
   349  		cmd.Dir = dir
   350  		output, err := cmd.CombinedOutput()
   351  		if err == nil {
   352  			nok++
   353  			if reject {
   354  				bug()
   355  				fmt.Printf("accepted incorrectly:\n\t%s\n", indent(strings.TrimSpace(stanza)))
   356  			}
   357  		} else {
   358  			nfail++
   359  			if !reject {
   360  				bug()
   361  				fmt.Printf("rejected incorrectly:\n\t%s\n", indent(strings.TrimSpace(stanza)))
   362  				fmt.Printf("\n\tlinker output:\n\t%s\n", indent(string(output)))
   363  			}
   364  		}
   365  	}
   366  
   367  	if !bugged && (nok == 0 || nfail == 0) {
   368  		bug()
   369  		fmt.Printf("not enough test cases run\n")
   370  	}
   371  }
   372  
   373  func indent(s string) string {
   374  	return strings.Replace(s, "\n", "\n\t", -1)
   375  }
   376  
   377  var bugged = false
   378  
   379  func bug() {
   380  	if !bugged {
   381  		bugged = true
   382  		fmt.Printf("BUG\n")
   383  	}
   384  }