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 main 112 nosplit
    119 main 116 nosplit
    120 main 120 nosplit
    121 main 124 nosplit
    122 main 128 nosplit; REJECT
    123 main 132 nosplit; REJECT
    124 main 136 nosplit; REJECT
    125 
    126 # Calling a nosplit function from a nosplit function requires
    127 # having room for the saved caller PC and the called frame.
    128 # Because ARM doesn't save LR in the leaf, it gets an extra 4 bytes.
    129 # Because ppc64 doesn't save LR in the leaf, it gets an extra 8 bytes.
    130 main 112 nosplit call f; f 0 nosplit
    131 main 116 nosplit call f; f 0 nosplit
    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.
    141 # Again the ARM and ppc64 work 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 	version, err := exec.Command("go", "tool", "compile", "-V").Output()
    188 	if err != nil {
    189 		bug()
    190 		fmt.Printf("running go tool compile -V: %v\n", err)
    191 		return
    192 	}
    193 	if strings.Contains(string(version), "framepointer") {
    194 		// Skip this test if GOEXPERIMENT=framepointer
    195 		return
    196 	}
    197 
    198 	dir, err := ioutil.TempDir("", "go-test-nosplit")
    199 	if err != nil {
    200 		bug()
    201 		fmt.Printf("creating temp dir: %v\n", err)
    202 		return
    203 	}
    204 	defer os.RemoveAll(dir)
    205 
    206 	tests = strings.Replace(tests, "\t", " ", -1)
    207 	tests = commentRE.ReplaceAllString(tests, "")
    208 
    209 	nok := 0
    210 	nfail := 0
    211 TestCases:
    212 	for len(tests) > 0 {
    213 		var stanza string
    214 		i := strings.Index(tests, "\nmain ")
    215 		if i < 0 {
    216 			stanza, tests = tests, ""
    217 		} else {
    218 			stanza, tests = tests[:i], tests[i+1:]
    219 		}
    220 
    221 		m := rejectRE.FindStringSubmatch(stanza)
    222 		if m == nil {
    223 			bug()
    224 			fmt.Printf("invalid stanza:\n\t%s\n", indent(stanza))
    225 			continue
    226 		}
    227 		lines := strings.TrimSpace(m[1])
    228 		reject := false
    229 		if m[2] != "" {
    230 			if strings.TrimSpace(m[4]) == "" {
    231 				reject = true
    232 			} else {
    233 				for _, rej := range strings.Fields(m[4]) {
    234 					if rej == goarch {
    235 						reject = true
    236 					}
    237 				}
    238 			}
    239 		}
    240 		if lines == "" && !reject {
    241 			continue
    242 		}
    243 
    244 		var gobuf bytes.Buffer
    245 		fmt.Fprintf(&gobuf, "package main\n")
    246 
    247 		var buf bytes.Buffer
    248 		ptrSize := 4
    249 		switch goarch {
    250 		case "ppc64", "ppc64le":
    251 			ptrSize = 8
    252 			fmt.Fprintf(&buf, "#define CALL BL\n#define REGISTER (CTR)\n")
    253 		case "arm":
    254 			fmt.Fprintf(&buf, "#define CALL BL\n#define REGISTER (R0)\n")
    255 		case "arm64":
    256 			ptrSize = 8
    257 			fmt.Fprintf(&buf, "#define CALL BL\n#define REGISTER (R0)\n")
    258 		case "amd64":
    259 			ptrSize = 8
    260 			fmt.Fprintf(&buf, "#define REGISTER AX\n")
    261 		default:
    262 			fmt.Fprintf(&buf, "#define REGISTER AX\n")
    263 		}
    264 
    265 		for _, line := range strings.Split(lines, "\n") {
    266 			line = strings.TrimSpace(line)
    267 			if line == "" {
    268 				continue
    269 			}
    270 			for i, subline := range strings.Split(line, ";") {
    271 				subline = strings.TrimSpace(subline)
    272 				if subline == "" {
    273 					continue
    274 				}
    275 				m := lineRE.FindStringSubmatch(subline)
    276 				if m == nil {
    277 					bug()
    278 					fmt.Printf("invalid function line: %s\n", subline)
    279 					continue TestCases
    280 				}
    281 				name := m[1]
    282 				size, _ := strconv.Atoi(m[2])
    283 
    284 				// The limit was originally 128 but is now 512.
    285 				// Instead of rewriting the test cases above, adjust
    286 				// the first stack frame to use up the extra bytes.
    287 				if i == 0 {
    288 					size += 512 - 128
    289 					// Noopt builds have a larger stackguard.
    290 					// See ../cmd/dist/buildruntime.go:stackGuardMultiplier
    291 					for _, s := range strings.Split(os.Getenv("GO_GCFLAGS"), " ") {
    292 						if s == "-N" {
    293 							size += 640
    294 						}
    295 					}
    296 				}
    297 
    298 				if size%ptrSize == 4 || goarch == "arm64" && size != 0 && (size+8)%16 != 0 {
    299 					continue TestCases
    300 				}
    301 				nosplit := m[3]
    302 				body := m[4]
    303 
    304 				if nosplit != "" {
    305 					nosplit = ",7"
    306 				} else {
    307 					nosplit = ",0"
    308 				}
    309 				body = callRE.ReplaceAllString(body, "CALL $1(SB);")
    310 				body = callindRE.ReplaceAllString(body, "CALL REGISTER;")
    311 
    312 				fmt.Fprintf(&gobuf, "func %s()\n", name)
    313 				fmt.Fprintf(&buf, "TEXT %s(SB)%s,$%d-0\n\t%s\n\tRET\n\n", name, nosplit, size, body)
    314 			}
    315 		}
    316 
    317 		if err := ioutil.WriteFile(filepath.Join(dir, "asm.s"), buf.Bytes(), 0666); err != nil {
    318 			log.Fatal(err)
    319 		}
    320 		if err := ioutil.WriteFile(filepath.Join(dir, "main.go"), gobuf.Bytes(), 0666); err != nil {
    321 			log.Fatal(err)
    322 		}
    323 
    324 		cmd := exec.Command("go", "build")
    325 		cmd.Dir = dir
    326 		output, err := cmd.CombinedOutput()
    327 		if err == nil {
    328 			nok++
    329 			if reject {
    330 				bug()
    331 				fmt.Printf("accepted incorrectly:\n\t%s\n", indent(strings.TrimSpace(stanza)))
    332 			}
    333 		} else {
    334 			nfail++
    335 			if !reject {
    336 				bug()
    337 				fmt.Printf("rejected incorrectly:\n\t%s\n", indent(strings.TrimSpace(stanza)))
    338 				fmt.Printf("\n\tlinker output:\n\t%s\n", indent(string(output)))
    339 			}
    340 		}
    341 	}
    342 
    343 	if !bugged && (nok == 0 || nfail == 0) {
    344 		bug()
    345 		fmt.Printf("not enough test cases run\n")
    346 	}
    347 }
    348 
    349 func indent(s string) string {
    350 	return strings.Replace(s, "\n", "\n\t", -1)
    351 }
    352 
    353 var bugged = false
    354 
    355 func bug() {
    356 	if !bugged {
    357 		bugged = true
    358 		fmt.Printf("BUG\n")
    359 	}
    360 }
    361