Home | History | Annotate | Download | only in test
      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)
    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
    126 main 124 nosplit; REJECT ppc64 ppc64le
    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 fewer bytes than amd64.
    136 main 96 nosplit call f; f 0 nosplit
    137 main 100 nosplit call f; f 0 nosplit; REJECT ppc64 ppc64le
    138 main 104 nosplit call f; f 0 nosplit; REJECT ppc64 ppc64le
    139 main 108 nosplit call f; f 0 nosplit; REJECT ppc64 ppc64le
    140 main 112 nosplit call f; f 0 nosplit; REJECT ppc64 ppc64le
    141 main 116 nosplit call f; f 0 nosplit; REJECT ppc64 ppc64le
    142 main 120 nosplit call f; f 0 nosplit; REJECT ppc64 ppc64le amd64
    143 main 124 nosplit call f; f 0 nosplit; REJECT ppc64 ppc64le amd64 386
    144 main 128 nosplit call f; f 0 nosplit; REJECT
    145 main 132 nosplit call f; f 0 nosplit; REJECT
    146 main 136 nosplit call f; f 0 nosplit; REJECT
    147 
    148 # Calling a splitting function from a nosplit function requires
    149 # having room for the saved caller PC of the call but also the
    150 # saved caller PC for the call to morestack.
    151 # RISC architectures differ in the same way as before.
    152 main 96 nosplit call f; f 0 call f
    153 main 100 nosplit call f; f 0 call f; REJECT ppc64 ppc64le
    154 main 104 nosplit call f; f 0 call f; REJECT ppc64 ppc64le
    155 main 108 nosplit call f; f 0 call f; REJECT ppc64 ppc64le
    156 main 112 nosplit call f; f 0 call f; REJECT ppc64 ppc64le amd64
    157 main 116 nosplit call f; f 0 call f; REJECT ppc64 ppc64le amd64
    158 main 120 nosplit call f; f 0 call f; REJECT ppc64 ppc64le amd64 386
    159 main 124 nosplit call f; f 0 call f; REJECT ppc64 ppc64le amd64 386
    160 main 128 nosplit call f; f 0 call f; REJECT
    161 main 132 nosplit call f; f 0 call f; REJECT
    162 main 136 nosplit call f; f 0 call f; REJECT
    163 
    164 # Indirect calls are assumed to be splitting functions.
    165 main 96 nosplit callind
    166 main 100 nosplit callind; REJECT ppc64 ppc64le
    167 main 104 nosplit callind; REJECT ppc64 ppc64le
    168 main 108 nosplit callind; REJECT ppc64 ppc64le
    169 main 112 nosplit callind; REJECT ppc64 ppc64le amd64
    170 main 116 nosplit callind; REJECT ppc64 ppc64le amd64
    171 main 120 nosplit callind; REJECT ppc64 ppc64le amd64 386
    172 main 124 nosplit callind; REJECT ppc64 ppc64le amd64 386
    173 main 128 nosplit callind; REJECT
    174 main 132 nosplit callind; REJECT
    175 main 136 nosplit callind; REJECT
    176 
    177 # Issue 7623
    178 main 0 call f; f 112
    179 main 0 call f; f 116
    180 main 0 call f; f 120
    181 main 0 call f; f 124
    182 main 0 call f; f 128
    183 main 0 call f; f 132
    184 main 0 call f; f 136
    185 `
    186 
    187 var (
    188 	commentRE = regexp.MustCompile(`(?m)^#.*`)
    189 	rejectRE  = regexp.MustCompile(`(?s)\A(.+?)((\n|; *)REJECT(.*))?\z`)
    190 	lineRE    = regexp.MustCompile(`(\w+) (\d+)( nosplit)?(.*)`)
    191 	callRE    = regexp.MustCompile(`\bcall (\w+)\b`)
    192 	callindRE = regexp.MustCompile(`\bcallind\b`)
    193 )
    194 
    195 func main() {
    196 	goarch := os.Getenv("GOARCH")
    197 	if goarch == "" {
    198 		goarch = runtime.GOARCH
    199 	}
    200 
    201 	version, err := exec.Command("go", "tool", "compile", "-V").Output()
    202 	if err != nil {
    203 		bug()
    204 		fmt.Printf("running go tool compile -V: %v\n", err)
    205 		return
    206 	}
    207 	if strings.Contains(string(version), "framepointer") {
    208 		// Skip this test if GOEXPERIMENT=framepointer
    209 		return
    210 	}
    211 
    212 	dir, err := ioutil.TempDir("", "go-test-nosplit")
    213 	if err != nil {
    214 		bug()
    215 		fmt.Printf("creating temp dir: %v\n", err)
    216 		return
    217 	}
    218 	defer os.RemoveAll(dir)
    219 
    220 	tests = strings.Replace(tests, "\t", " ", -1)
    221 	tests = commentRE.ReplaceAllString(tests, "")
    222 
    223 	nok := 0
    224 	nfail := 0
    225 TestCases:
    226 	for len(tests) > 0 {
    227 		var stanza string
    228 		i := strings.Index(tests, "\nmain ")
    229 		if i < 0 {
    230 			stanza, tests = tests, ""
    231 		} else {
    232 			stanza, tests = tests[:i], tests[i+1:]
    233 		}
    234 
    235 		m := rejectRE.FindStringSubmatch(stanza)
    236 		if m == nil {
    237 			bug()
    238 			fmt.Printf("invalid stanza:\n\t%s\n", indent(stanza))
    239 			continue
    240 		}
    241 		lines := strings.TrimSpace(m[1])
    242 		reject := false
    243 		if m[2] != "" {
    244 			if strings.TrimSpace(m[4]) == "" {
    245 				reject = true
    246 			} else {
    247 				for _, rej := range strings.Fields(m[4]) {
    248 					if rej == goarch {
    249 						reject = true
    250 					}
    251 				}
    252 			}
    253 		}
    254 		if lines == "" && !reject {
    255 			continue
    256 		}
    257 
    258 		var gobuf bytes.Buffer
    259 		fmt.Fprintf(&gobuf, "package main\n")
    260 
    261 		var buf bytes.Buffer
    262 		ptrSize := 4
    263 		switch goarch {
    264 		case "mips", "mipsle":
    265 			fmt.Fprintf(&buf, "#define CALL JAL\n#define REGISTER (R0)\n")
    266 		case "mips64", "mips64le":
    267 			ptrSize = 8
    268 			fmt.Fprintf(&buf, "#define CALL JAL\n#define REGISTER (R0)\n")
    269 		case "ppc64", "ppc64le":
    270 			ptrSize = 8
    271 			fmt.Fprintf(&buf, "#define CALL BL\n#define REGISTER (CTR)\n")
    272 		case "arm":
    273 			fmt.Fprintf(&buf, "#define CALL BL\n#define REGISTER (R0)\n")
    274 		case "arm64":
    275 			ptrSize = 8
    276 			fmt.Fprintf(&buf, "#define CALL BL\n#define REGISTER (R0)\n")
    277 		case "amd64":
    278 			ptrSize = 8
    279 			fmt.Fprintf(&buf, "#define REGISTER AX\n")
    280 		case "s390x":
    281 			ptrSize = 8
    282 			fmt.Fprintf(&buf, "#define REGISTER R10\n")
    283 		default:
    284 			fmt.Fprintf(&buf, "#define REGISTER AX\n")
    285 		}
    286 
    287 		for _, line := range strings.Split(lines, "\n") {
    288 			line = strings.TrimSpace(line)
    289 			if line == "" {
    290 				continue
    291 			}
    292 			for i, subline := range strings.Split(line, ";") {
    293 				subline = strings.TrimSpace(subline)
    294 				if subline == "" {
    295 					continue
    296 				}
    297 				m := lineRE.FindStringSubmatch(subline)
    298 				if m == nil {
    299 					bug()
    300 					fmt.Printf("invalid function line: %s\n", subline)
    301 					continue TestCases
    302 				}
    303 				name := m[1]
    304 				size, _ := strconv.Atoi(m[2])
    305 
    306 				// The limit was originally 128 but is now 592.
    307 				// Instead of rewriting the test cases above, adjust
    308 				// the first stack frame to use up the extra bytes.
    309 				if i == 0 {
    310 					size += (880 - 128) - 128
    311 					// Noopt builds have a larger stackguard.
    312 					// See ../src/cmd/dist/buildruntime.go:stackGuardMultiplier
    313 					// This increase is included in obj.StackGuard
    314 					for _, s := range strings.Split(os.Getenv("GO_GCFLAGS"), " ") {
    315 						if s == "-N" {
    316 							size += 880
    317 						}
    318 					}
    319 				}
    320 
    321 				if size%ptrSize == 4 || goarch == "arm64" && size != 0 && (size+8)%16 != 0 {
    322 					continue TestCases
    323 				}
    324 				nosplit := m[3]
    325 				body := m[4]
    326 
    327 				if nosplit != "" {
    328 					nosplit = ",7"
    329 				} else {
    330 					nosplit = ",0"
    331 				}
    332 				body = callRE.ReplaceAllString(body, "CALL $1(SB);")
    333 				body = callindRE.ReplaceAllString(body, "CALL REGISTER;")
    334 
    335 				fmt.Fprintf(&gobuf, "func %s()\n", name)
    336 				fmt.Fprintf(&buf, "TEXT %s(SB)%s,$%d-0\n\t%s\n\tRET\n\n", name, nosplit, size, body)
    337 			}
    338 		}
    339 
    340 		if err := ioutil.WriteFile(filepath.Join(dir, "asm.s"), buf.Bytes(), 0666); err != nil {
    341 			log.Fatal(err)
    342 		}
    343 		if err := ioutil.WriteFile(filepath.Join(dir, "main.go"), gobuf.Bytes(), 0666); err != nil {
    344 			log.Fatal(err)
    345 		}
    346 
    347 		cmd := exec.Command("go", "build")
    348 		cmd.Dir = dir
    349 		output, err := cmd.CombinedOutput()
    350 		if err == nil {
    351 			nok++
    352 			if reject {
    353 				bug()
    354 				fmt.Printf("accepted incorrectly:\n\t%s\n", indent(strings.TrimSpace(stanza)))
    355 			}
    356 		} else {
    357 			nfail++
    358 			if !reject {
    359 				bug()
    360 				fmt.Printf("rejected incorrectly:\n\t%s\n", indent(strings.TrimSpace(stanza)))
    361 				fmt.Printf("\n\tlinker output:\n\t%s\n", indent(string(output)))
    362 			}
    363 		}
    364 	}
    365 
    366 	if !bugged && (nok == 0 || nfail == 0) {
    367 		bug()
    368 		fmt.Printf("not enough test cases run\n")
    369 	}
    370 }
    371 
    372 func indent(s string) string {
    373 	return strings.Replace(s, "\n", "\n\t", -1)
    374 }
    375 
    376 var bugged = false
    377 
    378 func bug() {
    379 	if !bugged {
    380 		bugged = true
    381 		fmt.Printf("BUG\n")
    382 	}
    383 }
    384