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 }