cmd/compile: detect and diagnose invalid //go: directive placement

Thie CL changes cmd/compile/internal/syntax to give the gc half of
the compiler more control over pragma handling, so that it can prepare
better errors, diagnose misuse, and so on. Before, the API between
the two was hard-coded as a uint16. Now it is an interface{}.
This should set us up better for future directives.

In addition to the split, this CL emits a "misplaced compiler directive"
error for any directive that is in a place where it has no effect.
I've certainly been confused in the past by adding comments
that were doing nothing and not realizing it. This should help
avoid that kind of confusion.

The rule, now applied consistently, is that a //go: directive
must appear on a line by itself immediately before the declaration
specifier it means to apply to. See cmd/compile/doc.go for
precise text and test/directive.go for examples.

This may cause some code to stop compiling, but that code
was broken. For example, this code formerly applied the
//go:noinline to f (not c) but now will fail to compile:

	//go:noinline
	const c = 1

	func f() {}

Change-Id: Ieba9b8d90a27cfab25de79d2790a895cefe5296f
Reviewed-on: https://go-review.googlesource.com/c/go/+/228578
Run-TryBot: Russ Cox <rsc@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Robert Griesemer <gri@golang.org>
This commit is contained in:
Russ Cox 2020-04-01 15:51:08 -04:00
parent 4f27e1d7aa
commit 768201729d
10 changed files with 345 additions and 88 deletions

View file

@ -195,30 +195,58 @@ directive can skip over a directive like any other comment.
// Line directives typically appear in machine-generated code, so that compilers and debuggers // Line directives typically appear in machine-generated code, so that compilers and debuggers
// will report positions in the original input to the generator. // will report positions in the original input to the generator.
/* /*
The line directive is an historical special case; all other directives are of the form The line directive is a historical special case; all other directives are of the form
//go:name and must start at the beginning of a line, indicating that the directive is defined //go:name, indicating that they are defined by the Go toolchain.
by the Go toolchain. Each directive must be placed its own line, with only leading spaces and tabs
allowed before the comment.
Each directive applies to the Go code that immediately follows it,
which typically must be a declaration.
//go:noescape //go:noescape
The //go:noescape directive specifies that the next declaration in the file, which The //go:noescape directive must be followed by a function declaration without
must be a func without a body (meaning that it has an implementation not written a body (meaning that the function has an implementation not written in Go).
in Go) does not allow any of the pointers passed as arguments to escape into the It specifies that the function does not allow any of the pointers passed as
heap or into the values returned from the function. This information can be used arguments to escape into the heap or into the values returned from the function.
during the compiler's escape analysis of Go code calling the function. This information can be used during the compiler's escape analysis of Go code
calling the function.
//go:uintptrescapes
The //go:uintptrescapes directive must be followed by a function declaration.
It specifies that the function's uintptr arguments may be pointer values
that have been converted to uintptr and must be treated as such by the
garbage collector. The conversion from pointer to uintptr must appear in
the argument list of any call to this function. This directive is necessary
for some low-level system call implementations and should be avoided otherwise.
//go:noinline
The //go:noinline directive must be followed by a function declaration.
It specifies that calls to the function should not be inlined, overriding
the compiler's usual optimization rules. This is typically only needed
for special runtime functions or when debugging the compiler.
//go:norace
The //go:norace directive must be followed by a function declaration.
It specifies that the function's memory accesses must be ignored by the
race detector. This is most commonly used in low-level code invoked
at times when it is unsafe to call into the race detector runtime.
//go:nosplit //go:nosplit
The //go:nosplit directive specifies that the next function declared in the file must The //go:nosplit directive must be followed by a function declaration.
not include a stack overflow check. This is most commonly used by low-level It specifies that the function must omit its usual stack overflow check.
runtime sources invoked at times when it is unsafe for the calling goroutine to be This is most commonly used by low-level runtime code invoked
preempted. at times when it is unsafe for the calling goroutine to be preempted.
//go:linkname localname [importpath.name] //go:linkname localname [importpath.name]
The //go:linkname directive instructs the compiler to use ``importpath.name'' as the This special directive does not apply to the Go code that follows it.
object file symbol name for the variable or function declared as ``localname'' in the Instead, the //go:linkname directive instructs the compiler to use ``importpath.name''
source code. as the object file symbol name for the variable or function declared as ``localname''
in the source code.
If the ``importpath.name'' argument is omitted, the directive uses the If the ``importpath.name'' argument is omitted, the directive uses the
symbol's default object file symbol name and only has the effect of making symbol's default object file symbol name and only has the effect of making
the symbol accessible to other packages. the symbol accessible to other packages.

View file

@ -28,16 +28,18 @@ func isQuoted(s string) bool {
return len(s) >= 2 && s[0] == '"' && s[len(s)-1] == '"' return len(s) >= 2 && s[0] == '"' && s[len(s)-1] == '"'
} }
type PragmaFlag int16
const ( const (
// Func pragmas. // Func pragmas.
Nointerface syntax.Pragma = 1 << iota Nointerface PragmaFlag = 1 << iota
Noescape // func parameters don't escape Noescape // func parameters don't escape
Norace // func must not have race detector annotations Norace // func must not have race detector annotations
Nosplit // func should not execute on separate stack Nosplit // func should not execute on separate stack
Noinline // func should not be inlined Noinline // func should not be inlined
NoCheckPtr // func should not be instrumented by checkptr NoCheckPtr // func should not be instrumented by checkptr
CgoUnsafeArgs // treat a pointer to one arg as a pointer to them all CgoUnsafeArgs // treat a pointer to one arg as a pointer to them all
UintptrEscapes // pointers converted to uintptr escape UintptrEscapes // pointers converted to uintptr escape
// Runtime-only func pragmas. // Runtime-only func pragmas.
// See ../../../../runtime/README.md for detailed descriptions. // See ../../../../runtime/README.md for detailed descriptions.
@ -50,7 +52,24 @@ const (
NotInHeap // values of this type must not be heap allocated NotInHeap // values of this type must not be heap allocated
) )
func pragmaValue(verb string) syntax.Pragma { const (
FuncPragmas = Nointerface |
Noescape |
Norace |
Nosplit |
Noinline |
NoCheckPtr |
CgoUnsafeArgs |
UintptrEscapes |
Systemstack |
Nowritebarrier |
Nowritebarrierrec |
Yeswritebarrierrec
TypePragmas = NotInHeap
)
func pragmaFlag(verb string) PragmaFlag {
switch verb { switch verb {
case "go:nointerface": case "go:nointerface":
if objabi.Fieldtrack_enabled != 0 { if objabi.Fieldtrack_enabled != 0 {

View file

@ -241,6 +241,10 @@ func (p *noder) node() {
p.setlineno(p.file.PkgName) p.setlineno(p.file.PkgName)
mkpackage(p.file.PkgName.Value) mkpackage(p.file.PkgName.Value)
if pragma, ok := p.file.Pragma.(*Pragma); ok {
p.checkUnused(pragma)
}
xtop = append(xtop, p.decls(p.file.DeclList)...) xtop = append(xtop, p.decls(p.file.DeclList)...)
for _, n := range p.linknames { for _, n := range p.linknames {
@ -313,6 +317,10 @@ func (p *noder) importDecl(imp *syntax.ImportDecl) {
return // avoid follow-on errors if there was a syntax error return // avoid follow-on errors if there was a syntax error
} }
if pragma, ok := imp.Pragma.(*Pragma); ok {
p.checkUnused(pragma)
}
val := p.basicLit(imp.Path) val := p.basicLit(imp.Path)
ipkg := importfile(&val) ipkg := importfile(&val)
@ -363,6 +371,10 @@ func (p *noder) varDecl(decl *syntax.VarDecl) []*Node {
exprs = p.exprList(decl.Values) exprs = p.exprList(decl.Values)
} }
if pragma, ok := decl.Pragma.(*Pragma); ok {
p.checkUnused(pragma)
}
p.setlineno(decl) p.setlineno(decl)
return variter(names, typ, exprs) return variter(names, typ, exprs)
} }
@ -384,6 +396,10 @@ func (p *noder) constDecl(decl *syntax.ConstDecl, cs *constState) []*Node {
} }
} }
if pragma, ok := decl.Pragma.(*Pragma); ok {
p.checkUnused(pragma)
}
names := p.declNames(decl.NameList) names := p.declNames(decl.NameList)
typ := p.typeExprOrNil(decl.Type) typ := p.typeExprOrNil(decl.Type)
@ -438,11 +454,13 @@ func (p *noder) typeDecl(decl *syntax.TypeDecl) *Node {
param := n.Name.Param param := n.Name.Param
param.Ntype = typ param.Ntype = typ
param.Pragma = decl.Pragma
param.Alias = decl.Alias param.Alias = decl.Alias
if param.Alias && param.Pragma != 0 { if pragma, ok := decl.Pragma.(*Pragma); ok {
yyerror("cannot specify directive with type alias") if !decl.Alias {
param.Pragma = 0 param.Pragma = pragma.Flag & TypePragmas
pragma.Flag &^= TypePragmas
}
p.checkUnused(pragma)
} }
nod := p.nod(decl, ODCLTYPE, n, nil) nod := p.nod(decl, ODCLTYPE, n, nil)
@ -493,10 +511,13 @@ func (p *noder) funcDecl(fun *syntax.FuncDecl) *Node {
f.Func.Nname.Name.Defn = f f.Func.Nname.Name.Defn = f
f.Func.Nname.Name.Param.Ntype = t f.Func.Nname.Name.Param.Ntype = t
pragma := fun.Pragma if pragma, ok := fun.Pragma.(*Pragma); ok {
f.Func.Pragma = fun.Pragma f.Func.Pragma = pragma.Flag & FuncPragmas
if pragma&Systemstack != 0 && pragma&Nosplit != 0 { if pragma.Flag&Systemstack != 0 && pragma.Flag&Nosplit != 0 {
yyerrorl(f.Pos, "go:nosplit and go:systemstack cannot be combined") yyerrorl(f.Pos, "go:nosplit and go:systemstack cannot be combined")
}
pragma.Flag &^= FuncPragmas
p.checkUnused(pragma)
} }
if fun.Recv == nil { if fun.Recv == nil {
@ -1479,13 +1500,58 @@ var allowedStdPragmas = map[string]bool{
"go:generate": true, "go:generate": true,
} }
// *Pragma is the value stored in a syntax.Pragma during parsing.
type Pragma struct {
Flag PragmaFlag // collected bits
Pos []PragmaPos // position of each individual flag
}
type PragmaPos struct {
Flag PragmaFlag
Pos syntax.Pos
}
func (p *noder) checkUnused(pragma *Pragma) {
for _, pos := range pragma.Pos {
if pos.Flag&pragma.Flag != 0 {
p.yyerrorpos(pos.Pos, "misplaced compiler directive")
}
}
}
func (p *noder) checkUnusedDuringParse(pragma *Pragma) {
for _, pos := range pragma.Pos {
if pos.Flag&pragma.Flag != 0 {
p.error(syntax.Error{Pos: pos.Pos, Msg: "misplaced compiler directive"})
}
}
}
// pragma is called concurrently if files are parsed concurrently. // pragma is called concurrently if files are parsed concurrently.
func (p *noder) pragma(pos syntax.Pos, text string) syntax.Pragma { func (p *noder) pragma(pos syntax.Pos, blankLine bool, text string, old syntax.Pragma) syntax.Pragma {
switch { pragma, _ := old.(*Pragma)
case strings.HasPrefix(text, "line "): if pragma == nil {
pragma = new(Pragma)
}
if text == "" {
// unused pragma; only called with old != nil.
p.checkUnusedDuringParse(pragma)
return nil
}
if strings.HasPrefix(text, "line ") {
// line directives are handled by syntax package // line directives are handled by syntax package
panic("unreachable") panic("unreachable")
}
if !blankLine {
// directive must be on line by itself
p.error(syntax.Error{Pos: pos, Msg: "misplaced compiler directive"})
return pragma
}
switch {
case strings.HasPrefix(text, "go:linkname "): case strings.HasPrefix(text, "go:linkname "):
f := strings.Fields(text) f := strings.Fields(text)
if !(2 <= len(f) && len(f) <= 3) { if !(2 <= len(f) && len(f) <= 3) {
@ -1513,7 +1579,8 @@ func (p *noder) pragma(pos syntax.Pos, text string) syntax.Pragma {
p.error(syntax.Error{Pos: pos, Msg: fmt.Sprintf("invalid library name %q in cgo_import_dynamic directive", lib)}) p.error(syntax.Error{Pos: pos, Msg: fmt.Sprintf("invalid library name %q in cgo_import_dynamic directive", lib)})
} }
p.pragcgo(pos, text) p.pragcgo(pos, text)
return pragmaValue("go:cgo_import_dynamic") pragma.Flag |= pragmaFlag("go:cgo_import_dynamic")
break
} }
fallthrough fallthrough
case strings.HasPrefix(text, "go:cgo_"): case strings.HasPrefix(text, "go:cgo_"):
@ -1530,18 +1597,19 @@ func (p *noder) pragma(pos syntax.Pos, text string) syntax.Pragma {
if i := strings.Index(text, " "); i >= 0 { if i := strings.Index(text, " "); i >= 0 {
verb = verb[:i] verb = verb[:i]
} }
prag := pragmaValue(verb) flag := pragmaFlag(verb)
const runtimePragmas = Systemstack | Nowritebarrier | Nowritebarrierrec | Yeswritebarrierrec const runtimePragmas = Systemstack | Nowritebarrier | Nowritebarrierrec | Yeswritebarrierrec
if !compiling_runtime && prag&runtimePragmas != 0 { if !compiling_runtime && flag&runtimePragmas != 0 {
p.error(syntax.Error{Pos: pos, Msg: fmt.Sprintf("//%s only allowed in runtime", verb)}) p.error(syntax.Error{Pos: pos, Msg: fmt.Sprintf("//%s only allowed in runtime", verb)})
} }
if prag == 0 && !allowedStdPragmas[verb] && compiling_std { if flag == 0 && !allowedStdPragmas[verb] && compiling_std {
p.error(syntax.Error{Pos: pos, Msg: fmt.Sprintf("//%s is not allowed in the standard library", verb)}) p.error(syntax.Error{Pos: pos, Msg: fmt.Sprintf("//%s is not allowed in the standard library", verb)})
} }
return prag pragma.Flag |= flag
pragma.Pos = append(pragma.Pos, PragmaPos{flag, pos})
} }
return 0 return pragma
} }
// isCgoGeneratedFile reports whether pos is in a file // isCgoGeneratedFile reports whether pos is in a file

View file

@ -8,7 +8,6 @@ package gc
import ( import (
"cmd/compile/internal/ssa" "cmd/compile/internal/ssa"
"cmd/compile/internal/syntax"
"cmd/compile/internal/types" "cmd/compile/internal/types"
"cmd/internal/obj" "cmd/internal/obj"
"cmd/internal/objabi" "cmd/internal/objabi"
@ -483,7 +482,7 @@ type Param struct {
// OTYPE // OTYPE
// //
// TODO: Should Func pragmas also be stored on the Name? // TODO: Should Func pragmas also be stored on the Name?
Pragma syntax.Pragma Pragma PragmaFlag
Alias bool // node is alias for Ntype (only used when type-checking ODCLTYPE) Alias bool // node is alias for Ntype (only used when type-checking ODCLTYPE)
} }
@ -565,7 +564,7 @@ type Func struct {
Endlineno src.XPos Endlineno src.XPos
WBPos src.XPos // position of first write barrier; see SetWBPos WBPos src.XPos // position of first write barrier; see SetWBPos
Pragma syntax.Pragma // go:xxx function annotations Pragma PragmaFlag // go:xxx function annotations
flags bitset16 flags bitset16
numDefers int // number of defer calls in the function numDefers int // number of defer calls in the function

View file

@ -34,6 +34,7 @@ func (*node) aNode() {}
// package PkgName; DeclList[0], DeclList[1], ... // package PkgName; DeclList[0], DeclList[1], ...
type File struct { type File struct {
Pragma Pragma
PkgName *Name PkgName *Name
DeclList []Decl DeclList []Decl
Lines uint Lines uint
@ -52,9 +53,10 @@ type (
// Path // Path
// LocalPkgName Path // LocalPkgName Path
ImportDecl struct { ImportDecl struct {
Group *Group // nil means not part of a group
Pragma Pragma
LocalPkgName *Name // including "."; nil means no rename present LocalPkgName *Name // including "."; nil means no rename present
Path *BasicLit Path *BasicLit
Group *Group // nil means not part of a group
decl decl
} }
@ -62,20 +64,21 @@ type (
// NameList = Values // NameList = Values
// NameList Type = Values // NameList Type = Values
ConstDecl struct { ConstDecl struct {
NameList []*Name
Type Expr // nil means no type
Values Expr // nil means no values
Group *Group // nil means not part of a group Group *Group // nil means not part of a group
Pragma Pragma
NameList []*Name
Type Expr // nil means no type
Values Expr // nil means no values
decl decl
} }
// Name Type // Name Type
TypeDecl struct { TypeDecl struct {
Group *Group // nil means not part of a group
Pragma Pragma
Name *Name Name *Name
Alias bool Alias bool
Type Expr Type Expr
Group *Group // nil means not part of a group
Pragma Pragma
decl decl
} }
@ -83,10 +86,11 @@ type (
// NameList Type = Values // NameList Type = Values
// NameList = Values // NameList = Values
VarDecl struct { VarDecl struct {
NameList []*Name
Type Expr // nil means no type
Values Expr // nil means no values
Group *Group // nil means not part of a group Group *Group // nil means not part of a group
Pragma Pragma
NameList []*Name
Type Expr // nil means no type
Values Expr // nil means no values
decl decl
} }
@ -95,12 +99,11 @@ type (
// func Receiver Name Type { Body } // func Receiver Name Type { Body }
// func Receiver Name Type // func Receiver Name Type
FuncDecl struct { FuncDecl struct {
Attr map[string]bool // go:attr map Pragma Pragma
Recv *Field // nil means regular function Recv *Field // nil means regular function
Name *Name Name *Name
Type *FuncType Type *FuncType
Body *BlockStmt // nil means no body (forward declaration) Body *BlockStmt // nil means no body (forward declaration)
Pragma Pragma // TODO(mdempsky): Cleaner solution.
decl decl
} }
) )

View file

@ -15,15 +15,16 @@ const debug = false
const trace = false const trace = false
type parser struct { type parser struct {
file *PosBase file *PosBase
errh ErrorHandler errh ErrorHandler
mode Mode mode Mode
pragh PragmaHandler
scanner scanner
base *PosBase // current position base base *PosBase // current position base
first error // first error encountered first error // first error encountered
errcnt int // number of errors encountered errcnt int // number of errors encountered
pragma Pragma // pragma flags pragma Pragma // pragmas
fnest int // function nesting level (for error handling) fnest int // function nesting level (for error handling)
xnest int // expression nesting level (for complit ambiguity resolution) xnest int // expression nesting level (for complit ambiguity resolution)
@ -34,6 +35,7 @@ func (p *parser) init(file *PosBase, r io.Reader, errh ErrorHandler, pragh Pragm
p.file = file p.file = file
p.errh = errh p.errh = errh
p.mode = mode p.mode = mode
p.pragh = pragh
p.scanner.init( p.scanner.init(
r, r,
// Error and directive handler for scanner. // Error and directive handler for scanner.
@ -47,9 +49,11 @@ func (p *parser) init(file *PosBase, r io.Reader, errh ErrorHandler, pragh Pragm
return return
} }
// otherwise it must be a comment containing a line or go: directive // otherwise it must be a comment containing a line or go: directive.
// //line directives must be at the start of the line (column colbase).
// /*line*/ directives can be anywhere in the line.
text := commentText(msg) text := commentText(msg)
if strings.HasPrefix(text, "line ") { if (col == colbase || msg[1] == '*') && strings.HasPrefix(text, "line ") {
var pos Pos // position immediately following the comment var pos Pos // position immediately following the comment
if msg[1] == '/' { if msg[1] == '/' {
// line comment (newline is part of the comment) // line comment (newline is part of the comment)
@ -67,7 +71,7 @@ func (p *parser) init(file *PosBase, r io.Reader, errh ErrorHandler, pragh Pragm
// go: directive (but be conservative and test) // go: directive (but be conservative and test)
if pragh != nil && strings.HasPrefix(text, "go:") { if pragh != nil && strings.HasPrefix(text, "go:") {
p.pragma |= pragh(p.posAt(line, col+2), text) // +2 to skip over // or /* p.pragma = pragh(p.posAt(line, col+2), p.scanner.blank, text, p.pragma) // +2 to skip over // or /*
} }
}, },
directives, directives,
@ -76,13 +80,32 @@ func (p *parser) init(file *PosBase, r io.Reader, errh ErrorHandler, pragh Pragm
p.base = file p.base = file
p.first = nil p.first = nil
p.errcnt = 0 p.errcnt = 0
p.pragma = 0 p.pragma = nil
p.fnest = 0 p.fnest = 0
p.xnest = 0 p.xnest = 0
p.indent = nil p.indent = nil
} }
// takePragma returns the current parsed pragmas
// and clears them from the parser state.
func (p *parser) takePragma() Pragma {
prag := p.pragma
p.pragma = nil
return prag
}
// clearPragma is called at the end of a statement or
// other Go form that does NOT accept a pragma.
// It sends the pragma back to the pragma handler
// to be reported as unused.
func (p *parser) clearPragma() {
if p.pragma != nil {
p.pragh(p.pos(), p.scanner.blank, "", p.pragma)
p.pragma = nil
}
}
// updateBase sets the current position base to a new line base at pos. // updateBase sets the current position base to a new line base at pos.
// The base's filename, line, and column values are extracted from text // The base's filename, line, and column values are extracted from text
// which is positioned at (tline, tcol) (only needed for error messages). // which is positioned at (tline, tcol) (only needed for error messages).
@ -362,6 +385,7 @@ func (p *parser) fileOrNil() *File {
p.syntaxError("package statement must be first") p.syntaxError("package statement must be first")
return nil return nil
} }
f.Pragma = p.takePragma()
f.PkgName = p.name() f.PkgName = p.name()
p.want(_Semi) p.want(_Semi)
@ -410,7 +434,7 @@ func (p *parser) fileOrNil() *File {
// Reset p.pragma BEFORE advancing to the next token (consuming ';') // Reset p.pragma BEFORE advancing to the next token (consuming ';')
// since comments before may set pragmas for the next function decl. // since comments before may set pragmas for the next function decl.
p.pragma = 0 p.clearPragma()
if p.tok != _EOF && !p.got(_Semi) { if p.tok != _EOF && !p.got(_Semi) {
p.syntaxError("after top level declaration") p.syntaxError("after top level declaration")
@ -419,6 +443,7 @@ func (p *parser) fileOrNil() *File {
} }
// p.tok == _EOF // p.tok == _EOF
p.clearPragma()
f.Lines = p.line f.Lines = p.line
return f return f
@ -469,6 +494,7 @@ func (p *parser) list(open, sep, close token, f func() bool) Pos {
func (p *parser) appendGroup(list []Decl, f func(*Group) Decl) []Decl { func (p *parser) appendGroup(list []Decl, f func(*Group) Decl) []Decl {
if p.tok == _Lparen { if p.tok == _Lparen {
g := new(Group) g := new(Group)
p.clearPragma()
p.list(_Lparen, _Semi, _Rparen, func() bool { p.list(_Lparen, _Semi, _Rparen, func() bool {
list = append(list, f(g)) list = append(list, f(g))
return false return false
@ -497,6 +523,8 @@ func (p *parser) importDecl(group *Group) Decl {
d := new(ImportDecl) d := new(ImportDecl)
d.pos = p.pos() d.pos = p.pos()
d.Group = group
d.Pragma = p.takePragma()
switch p.tok { switch p.tok {
case _Name: case _Name:
@ -511,7 +539,6 @@ func (p *parser) importDecl(group *Group) Decl {
p.advance(_Semi, _Rparen) p.advance(_Semi, _Rparen)
return nil return nil
} }
d.Group = group
return d return d
} }
@ -524,6 +551,8 @@ func (p *parser) constDecl(group *Group) Decl {
d := new(ConstDecl) d := new(ConstDecl)
d.pos = p.pos() d.pos = p.pos()
d.Group = group
d.Pragma = p.takePragma()
d.NameList = p.nameList(p.name()) d.NameList = p.nameList(p.name())
if p.tok != _EOF && p.tok != _Semi && p.tok != _Rparen { if p.tok != _EOF && p.tok != _Semi && p.tok != _Rparen {
@ -532,7 +561,6 @@ func (p *parser) constDecl(group *Group) Decl {
d.Values = p.exprList() d.Values = p.exprList()
} }
} }
d.Group = group
return d return d
} }
@ -545,6 +573,8 @@ func (p *parser) typeDecl(group *Group) Decl {
d := new(TypeDecl) d := new(TypeDecl)
d.pos = p.pos() d.pos = p.pos()
d.Group = group
d.Pragma = p.takePragma()
d.Name = p.name() d.Name = p.name()
d.Alias = p.gotAssign() d.Alias = p.gotAssign()
@ -554,8 +584,6 @@ func (p *parser) typeDecl(group *Group) Decl {
p.syntaxError("in type declaration") p.syntaxError("in type declaration")
p.advance(_Semi, _Rparen) p.advance(_Semi, _Rparen)
} }
d.Group = group
d.Pragma = p.pragma
return d return d
} }
@ -568,6 +596,8 @@ func (p *parser) varDecl(group *Group) Decl {
d := new(VarDecl) d := new(VarDecl)
d.pos = p.pos() d.pos = p.pos()
d.Group = group
d.Pragma = p.takePragma()
d.NameList = p.nameList(p.name()) d.NameList = p.nameList(p.name())
if p.gotAssign() { if p.gotAssign() {
@ -578,7 +608,6 @@ func (p *parser) varDecl(group *Group) Decl {
d.Values = p.exprList() d.Values = p.exprList()
} }
} }
d.Group = group
return d return d
} }
@ -595,6 +624,7 @@ func (p *parser) funcDeclOrNil() *FuncDecl {
f := new(FuncDecl) f := new(FuncDecl)
f.pos = p.pos() f.pos = p.pos()
f.Pragma = p.takePragma()
if p.tok == _Lparen { if p.tok == _Lparen {
rcvr := p.paramList() rcvr := p.paramList()
@ -620,7 +650,6 @@ func (p *parser) funcDeclOrNil() *FuncDecl {
if p.tok == _Lbrace { if p.tok == _Lbrace {
f.Body = p.funcBody() f.Body = p.funcBody()
} }
f.Pragma = p.pragma
return f return f
} }
@ -2054,6 +2083,7 @@ func (p *parser) stmtOrNil() Stmt {
// Most statements (assignments) start with an identifier; // Most statements (assignments) start with an identifier;
// look for it first before doing anything more expensive. // look for it first before doing anything more expensive.
if p.tok == _Name { if p.tok == _Name {
p.clearPragma()
lhs := p.exprList() lhs := p.exprList()
if label, ok := lhs.(*Name); ok && p.tok == _Colon { if label, ok := lhs.(*Name); ok && p.tok == _Colon {
return p.labeledStmtOrNil(label) return p.labeledStmtOrNil(label)
@ -2062,9 +2092,6 @@ func (p *parser) stmtOrNil() Stmt {
} }
switch p.tok { switch p.tok {
case _Lbrace:
return p.blockStmt("")
case _Var: case _Var:
return p.declStmt(p.varDecl) return p.declStmt(p.varDecl)
@ -2073,6 +2100,13 @@ func (p *parser) stmtOrNil() Stmt {
case _Type: case _Type:
return p.declStmt(p.typeDecl) return p.declStmt(p.typeDecl)
}
p.clearPragma()
switch p.tok {
case _Lbrace:
return p.blockStmt("")
case _Operator, _Star: case _Operator, _Star:
switch p.op { switch p.op {
@ -2151,6 +2185,7 @@ func (p *parser) stmtList() (l []Stmt) {
for p.tok != _EOF && p.tok != _Rbrace && p.tok != _Case && p.tok != _Default { for p.tok != _EOF && p.tok != _Rbrace && p.tok != _Case && p.tok != _Default {
s := p.stmtOrNil() s := p.stmtOrNil()
p.clearPragma()
if s == nil { if s == nil {
break break
} }

View file

@ -34,6 +34,7 @@ type scanner struct {
// current token, valid after calling next() // current token, valid after calling next()
line, col uint line, col uint
blank bool // line is blank up to col
tok token tok token
lit string // valid if tok is _Name, _Literal, or _Semi ("semicolon", "newline", or "EOF"); may be malformed if bad is true lit string // valid if tok is _Name, _Literal, or _Semi ("semicolon", "newline", or "EOF"); may be malformed if bad is true
bad bool // valid if tok is _Literal, true if a syntax error occurred, lit may be malformed bad bool // valid if tok is _Literal, true if a syntax error occurred, lit may be malformed
@ -83,10 +84,7 @@ func (s *scanner) setLit(kind LitKind, ok bool) {
// //
// If the scanner mode includes the directives (but not the comments) // If the scanner mode includes the directives (but not the comments)
// flag, only comments containing a //line, /*line, or //go: directive // flag, only comments containing a //line, /*line, or //go: directive
// are reported, in the same way as regular comments. Directives in // are reported, in the same way as regular comments.
// //-style comments are only recognized if they are at the beginning
// of a line.
//
func (s *scanner) next() { func (s *scanner) next() {
nlsemi := s.nlsemi nlsemi := s.nlsemi
s.nlsemi = false s.nlsemi = false
@ -94,12 +92,14 @@ func (s *scanner) next() {
redo: redo:
// skip white space // skip white space
s.stop() s.stop()
startLine, startCol := s.pos()
for s.ch == ' ' || s.ch == '\t' || s.ch == '\n' && !nlsemi || s.ch == '\r' { for s.ch == ' ' || s.ch == '\t' || s.ch == '\n' && !nlsemi || s.ch == '\r' {
s.nextch() s.nextch()
} }
// token start // token start
s.line, s.col = s.pos() s.line, s.col = s.pos()
s.blank = s.line > startLine || startCol == colbase
s.start() s.start()
if isLetter(s.ch) || s.ch >= utf8.RuneSelf && s.atIdentChar(true) { if isLetter(s.ch) || s.ch >= utf8.RuneSelf && s.atIdentChar(true) {
s.nextch() s.nextch()
@ -741,8 +741,8 @@ func (s *scanner) lineComment() {
return return
} }
// directives must start at the beginning of the line (s.col == colbase) // are we saving directives? or is this definitely not a directive?
if s.mode&directives == 0 || s.col != colbase || (s.ch != 'g' && s.ch != 'l') { if s.mode&directives == 0 || (s.ch != 'g' && s.ch != 'l') {
s.stop() s.stop()
s.skipLine() s.skipLine()
return return

View file

@ -33,15 +33,24 @@ var _ error = Error{} // verify that Error implements error
// An ErrorHandler is called for each error encountered reading a .go file. // An ErrorHandler is called for each error encountered reading a .go file.
type ErrorHandler func(err error) type ErrorHandler func(err error)
// A Pragma value is a set of flags that augment a function or // A Pragma value augments a package, import, const, func, type, or var declaration.
// type declaration. Callers may assign meaning to the flags as // Its meaning is entirely up to the PragmaHandler,
// appropriate. // except that nil is used to mean “no pragma seen.”
type Pragma uint16 type Pragma interface{}
// A PragmaHandler is used to process //go: directives as // A PragmaHandler is used to process //go: directives while scanning.
// they're scanned. The returned Pragma value will be unioned into the // It is passed the current pragma value, which starts out being nil,
// next FuncDecl node. // and it returns an updated pragma value.
type PragmaHandler func(pos Pos, text string) Pragma // The text is the directive, with the "//" prefix stripped.
// The current pragma is saved at each package, import, const, func, type, or var
// declaration, into the File, ImportDecl, ConstDecl, FuncDecl, TypeDecl, or VarDecl node.
//
// If text is the empty string, the pragma is being returned
// to the handler unused, meaning it appeared before a non-declaration.
// The handler may wish to report an error. In this case, pos is the
// current parser position, not the position of the pragma itself.
// Blank specifies whether the line is blank before the pragma.
type PragmaHandler func(pos Pos, blank bool, text string, current Pragma) Pragma
// Parse parses a single Go source file from src and returns the corresponding // Parse parses a single Go source file from src and returns the corresponding
// syntax tree. If there are errors, Parse will return the first error found, // syntax tree. If there are errors, Parse will return the first error found,

View file

@ -156,6 +156,7 @@ func TestStdTest(t *testing.T) {
testTestDir(t, filepath.Join(runtime.GOROOT(), "test"), testTestDir(t, filepath.Join(runtime.GOROOT(), "test"),
"cmplxdivide.go", // also needs file cmplxdivide1.go - ignore "cmplxdivide.go", // also needs file cmplxdivide1.go - ignore
"directive.go", // tests compiler rejection of bad directive placement - ignore
) )
} }

95
test/directive.go Normal file
View file

@ -0,0 +1,95 @@
// errorcheck
// Copyright 2020 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.
// Verify that misplaced directives are diagnosed.
//go:noinline // ERROR "misplaced compiler directive"
//go:noinline // ERROR "misplaced compiler directive"
package main
//go:nosplit
func f1() {}
//go:nosplit
//go:noinline
func f2() {}
//go:noinline // ERROR "misplaced compiler directive"
//go:noinline // ERROR "misplaced compiler directive"
var x int
//go:noinline // ERROR "misplaced compiler directive"
const c = 1
//go:noinline // ERROR "misplaced compiler directive"
type T int
// ok
//go:notinheap
type T1 int
//go:notinheap // ERROR "misplaced compiler directive"
type (
//go:notinheap
//go:noinline // ERROR "misplaced compiler directive"
T2 int //go:notinheap // ERROR "misplaced compiler directive"
T2b int
//go:notinheap
T2c int
//go:noinline // ERROR "misplaced compiler directive"
T3 int
)
//go:notinheap // ERROR "misplaced compiler directive"
type (
//go:notinheap
T4 int
)
//go:notinheap // ERROR "misplaced compiler directive"
type ()
type T5 int
func g() {} //go:noinline // ERROR "misplaced compiler directive"
// ok: attached to f (duplicated yes, but ok)
//go:noinline
//go:noinline
func f() {
//go:noinline // ERROR "misplaced compiler directive"
x := 1
//go:noinline // ERROR "misplaced compiler directive"
{
_ = x //go:noinline // ERROR "misplaced compiler directive"
}
//go:noinline // ERROR "misplaced compiler directive"
var y int //go:noinline // ERROR "misplaced compiler directive"
//go:noinline // ERROR "misplaced compiler directive"
_ = y
//go:noinline // ERROR "misplaced compiler directive"
const c = 1
//go:noinline // ERROR "misplaced compiler directive"
_ = func() {}
//go:noinline // ERROR "misplaced compiler directive"
// ok:
//go:notinheap
type T int
}
// someday there might be a directive that can apply to type aliases, but go:notinheap doesn't.
//go:notinheap // ERROR "misplaced compiler directive"
type T6 = int
// EOF
//go:noinline // ERROR "misplaced compiler directive"