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