Commit 9d6427d8 authored by Russ Cox's avatar Russ Cox

cmd/asm: reject foo(SB)(AX) instead of silently treating as foo(SB)

Add test for assembly errors, to verify fix.
Make sure invalid instruction errors are printed just once
(was printing them once per span iteration, so typically twice).

Fixes #13282.

Change-Id: Id5f66f80a80b3bc4832e00084b0a91f1afec7f8f
Reviewed-on: https://go-review.googlesource.com/18858Reviewed-by: default avatarRob Pike <r@golang.org>
parent 544f28a2
...@@ -10,6 +10,7 @@ import ( ...@@ -10,6 +10,7 @@ import (
"io/ioutil" "io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
"regexp"
"sort" "sort"
"strconv" "strconv"
"strings" "strings"
...@@ -35,11 +36,10 @@ func testEndToEnd(t *testing.T, goarch, file string) { ...@@ -35,11 +36,10 @@ func testEndToEnd(t *testing.T, goarch, file string) {
ctxt.Bso = obj.Binitw(os.Stdout) ctxt.Bso = obj.Binitw(os.Stdout)
defer ctxt.Bso.Flush() defer ctxt.Bso.Flush()
failed := false failed := false
ctxt.Diag = func(format string, args ...interface{}) { ctxt.DiagFunc = func(format string, args ...interface{}) {
failed = true failed = true
t.Errorf(format, args...) t.Errorf(format, args...)
} }
obj.Binitw(ioutil.Discard)
pList.Firstpc, ok = parser.Parse() pList.Firstpc, ok = parser.Parse()
if !ok || failed { if !ok || failed {
t.Errorf("asm: %s assembly failed", goarch) t.Errorf("asm: %s assembly failed", goarch)
...@@ -175,7 +175,7 @@ Diff: ...@@ -175,7 +175,7 @@ Diff:
top := pList.Firstpc top := pList.Firstpc
var text *obj.LSym var text *obj.LSym
ok = true ok = true
ctxt.Diag = func(format string, args ...interface{}) { ctxt.DiagFunc = func(format string, args ...interface{}) {
t.Errorf(format, args...) t.Errorf(format, args...)
ok = false ok = false
} }
...@@ -250,8 +250,110 @@ func isHexes(s string) bool { ...@@ -250,8 +250,110 @@ func isHexes(s string) bool {
return true return true
} }
// It would be nice if the error messages began with
// the standard file:line: prefix,
// but that's not where we are today.
// It might be at the beginning but it might be in the middle of the printed instruction.
var fileLineRE = regexp.MustCompile(`(?:^|\()(testdata[/\\][0-9a-z]+\.s:[0-9]+)(?:$|\))`)
// Same as in test/run.go
var (
errRE = regexp.MustCompile(`// ERROR ?(.*)`)
errQuotesRE = regexp.MustCompile(`"([^"]*)"`)
)
func testErrors(t *testing.T, goarch, file string) {
lex.InitHist()
input := filepath.Join("testdata", file+".s")
architecture, ctxt := setArch(goarch)
lexer := lex.NewLexer(input, ctxt)
parser := NewParser(ctxt, architecture, lexer)
pList := obj.Linknewplist(ctxt)
var ok bool
testOut = new(bytes.Buffer) // The assembler writes test output to this buffer.
ctxt.Bso = obj.Binitw(os.Stdout)
defer ctxt.Bso.Flush()
failed := false
var errBuf bytes.Buffer
ctxt.DiagFunc = func(format string, args ...interface{}) {
failed = true
s := fmt.Sprintf(format, args...)
if !strings.HasSuffix(s, "\n") {
s += "\n"
}
errBuf.WriteString(s)
}
pList.Firstpc, ok = parser.Parse()
obj.Flushplist(ctxt)
if ok && !failed {
t.Errorf("asm: %s had no errors", goarch)
}
errors := map[string]string{}
for _, line := range strings.Split(errBuf.String(), "\n") {
if line == "" || strings.HasPrefix(line, "\t") {
continue
}
m := fileLineRE.FindStringSubmatch(line)
if m == nil {
t.Errorf("unexpected error: %v", line)
continue
}
fileline := m[1]
if errors[fileline] != "" {
t.Errorf("multiple errors on %s:\n\t%s\n\t%s", fileline, errors[fileline], line)
continue
}
errors[fileline] = line
}
// Reconstruct expected errors by independently "parsing" the input.
data, err := ioutil.ReadFile(input)
if err != nil {
t.Error(err)
return
}
lineno := 0
lines := strings.Split(string(data), "\n")
for _, line := range lines {
lineno++
fileline := fmt.Sprintf("%s:%d", input, lineno)
if m := errRE.FindStringSubmatch(line); m != nil {
all := m[1]
mm := errQuotesRE.FindAllStringSubmatch(all, -1)
if len(mm) != 1 {
t.Errorf("%s: invalid errorcheck line:\n%s", fileline, line)
} else if err := errors[fileline]; err == "" {
t.Errorf("%s: missing error, want %s", fileline, all)
} else if !strings.Contains(err, mm[0][1]) {
t.Errorf("%s: wrong error for %s:\n%s", fileline, all, err)
}
} else {
if errors[fileline] != "" {
t.Errorf("unexpected error on %s: %v", fileline, errors[fileline])
}
}
delete(errors, fileline)
}
var extra []string
for key := range errors {
extra = append(extra, key)
}
sort.Strings(extra)
for _, fileline := range extra {
t.Errorf("unexpected error on %s: %v", fileline, errors[fileline])
}
}
func Test386EndToEnd(t *testing.T) { func Test386EndToEnd(t *testing.T) {
testEndToEnd(t, "386", "386") defer os.Setenv("GO386", os.Getenv("GO386"))
for _, go386 := range []string{"387", "sse"} {
os.Setenv("GO386", go386)
t.Logf("GO386=%v", os.Getenv("GO386"))
testEndToEnd(t, "386", "386")
}
} }
func TestARMEndToEnd(t *testing.T) { func TestARMEndToEnd(t *testing.T) {
...@@ -276,6 +378,10 @@ func TestAMD64Encoder(t *testing.T) { ...@@ -276,6 +378,10 @@ func TestAMD64Encoder(t *testing.T) {
testEndToEnd(t, "amd64", "amd64enc") testEndToEnd(t, "amd64", "amd64enc")
} }
func TestAMD64Errors(t *testing.T) {
testErrors(t, "amd64", "amd64error")
}
func TestMIPS64EndToEnd(t *testing.T) { func TestMIPS64EndToEnd(t *testing.T) {
testEndToEnd(t, "mips64", "mips64") testEndToEnd(t, "mips64", "mips64")
} }
......
// Copyright 2016 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
TEXT errors(SB),$0
MOVL foo<>(SB)(AX), AX // ERROR "invalid instruction"
RET
...@@ -54,7 +54,7 @@ func main() { ...@@ -54,7 +54,7 @@ func main() {
lexer := lex.NewLexer(flag.Arg(0), ctxt) lexer := lex.NewLexer(flag.Arg(0), ctxt)
parser := asm.NewParser(ctxt, architecture, lexer) parser := asm.NewParser(ctxt, architecture, lexer)
diag := false diag := false
ctxt.Diag = func(format string, args ...interface{}) { ctxt.DiagFunc = func(format string, args ...interface{}) {
diag = true diag = true
log.Printf(format, args...) log.Printf(format, args...)
} }
......
...@@ -105,7 +105,7 @@ func Main() { ...@@ -105,7 +105,7 @@ func Main() {
Thearch.Linkarchinit() Thearch.Linkarchinit()
Ctxt = obj.Linknew(Thearch.Thelinkarch) Ctxt = obj.Linknew(Thearch.Thelinkarch)
Ctxt.Diag = Yyerror Ctxt.DiagFunc = Yyerror
Ctxt.Bso = &bstdout Ctxt.Bso = &bstdout
bstdout = *obj.Binitw(os.Stdout) bstdout = *obj.Binitw(os.Stdout)
......
...@@ -604,12 +604,13 @@ type Link struct { ...@@ -604,12 +604,13 @@ type Link struct {
Autosize int32 Autosize int32
Armsize int32 Armsize int32
Pc int64 Pc int64
Diag func(string, ...interface{}) DiagFunc func(string, ...interface{})
Mode int Mode int
Cursym *LSym Cursym *LSym
Version int Version int
Textp *LSym Textp *LSym
Etextp *LSym Etextp *LSym
Errors int
// state for writing objects // state for writing objects
Text *LSym Text *LSym
...@@ -618,6 +619,11 @@ type Link struct { ...@@ -618,6 +619,11 @@ type Link struct {
Edata *LSym Edata *LSym
} }
func (ctxt *Link) Diag(format string, args ...interface{}) {
ctxt.Errors++
ctxt.DiagFunc(format, args...)
}
// The smallest possible offset from the hardware stack pointer to a local // The smallest possible offset from the hardware stack pointer to a local
// variable on the stack. Architectures that use a link register save its value // variable on the stack. Architectures that use a link register save its value
// on the stack in the function prologue and so always have a pointer between // on the stack in the function prologue and so always have a pointer between
......
...@@ -1856,6 +1856,7 @@ func span6(ctxt *obj.Link, s *obj.LSym) { ...@@ -1856,6 +1856,7 @@ func span6(ctxt *obj.Link, s *obj.LSym) {
var loop int32 var loop int32
var m int var m int
var p *obj.Prog var p *obj.Prog
errors := ctxt.Errors
for { for {
loop = 0 loop = 0
for i = 0; i < len(s.R); i++ { for i = 0; i < len(s.R); i++ {
...@@ -1968,6 +1969,9 @@ func span6(ctxt *obj.Link, s *obj.LSym) { ...@@ -1968,6 +1969,9 @@ func span6(ctxt *obj.Link, s *obj.LSym) {
if loop == 0 { if loop == 0 {
break break
} }
if ctxt.Errors > errors {
return
}
} }
if ctxt.Headtype == obj.Hnacl { if ctxt.Headtype == obj.Hnacl {
...@@ -2294,6 +2298,11 @@ func oclass(ctxt *obj.Link, p *obj.Prog, a *obj.Addr) int { ...@@ -2294,6 +2298,11 @@ func oclass(ctxt *obj.Link, p *obj.Prog, a *obj.Addr) int {
return Yxxx return Yxxx
case obj.TYPE_MEM: case obj.TYPE_MEM:
if a.Name != obj.NAME_NONE {
if ctxt.Asmode == 64 && (a.Reg != REG_NONE || a.Index != REG_NONE || a.Scale != 0) {
return Yxxx
}
}
return Ym return Ym
case obj.TYPE_ADDR: case obj.TYPE_ADDR:
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment