// Line directives typically appear in machine-generated code, so that compilers and debuggers
// 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
-//go:name and must start at the beginning of a line, indicating that the directive is defined
-by the Go toolchain.
+The line directive is a historical special case; all other directives are of the form
+//go:name, indicating that they are defined 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
-The //go:noescape directive specifies that the next declaration in the file, which
-must be a func without a body (meaning that it has an implementation not written
-in Go) does not allow any of the pointers passed as arguments to escape into the
-heap or into the values returned from the function. This information can be used
-during the compiler's escape analysis of Go code calling the function.
+The //go:noescape directive must be followed by a function declaration without
+a body (meaning that the function has an implementation not written in Go).
+It specifies that the function does not allow any of the pointers passed as
+arguments to escape into the heap or into the values returned from 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
-The //go:nosplit directive specifies that the next function declared in the file must
-not include a stack overflow check. This is most commonly used by low-level
-runtime sources invoked at times when it is unsafe for the calling goroutine to be
-preempted.
+The //go:nosplit directive must be followed by a function declaration.
+It specifies that the function must omit its usual stack overflow check.
+This is most commonly used by low-level runtime code invoked
+at times when it is unsafe for the calling goroutine to be preempted.
//go:linkname localname [importpath.name]
-The //go:linkname directive instructs the compiler to use ``importpath.name'' as the
-object file symbol name for the variable or function declared as ``localname'' in the
-source code.
+This special directive does not apply to the Go code that follows it.
+Instead, the //go:linkname directive instructs the compiler to use ``importpath.name''
+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
symbol's default object file symbol name and only has the effect of making
the symbol accessible to other packages.
return len(s) >= 2 && s[0] == '"' && s[len(s)-1] == '"'
}
+type PragmaFlag int16
+
const (
// Func pragmas.
- Nointerface syntax.Pragma = 1 << iota
- Noescape // func parameters don't escape
- Norace // func must not have race detector annotations
- Nosplit // func should not execute on separate stack
- Noinline // func should not be inlined
- NoCheckPtr // func should not be instrumented by checkptr
- CgoUnsafeArgs // treat a pointer to one arg as a pointer to them all
- UintptrEscapes // pointers converted to uintptr escape
+ Nointerface PragmaFlag = 1 << iota
+ Noescape // func parameters don't escape
+ Norace // func must not have race detector annotations
+ Nosplit // func should not execute on separate stack
+ Noinline // func should not be inlined
+ NoCheckPtr // func should not be instrumented by checkptr
+ CgoUnsafeArgs // treat a pointer to one arg as a pointer to them all
+ UintptrEscapes // pointers converted to uintptr escape
// Runtime-only func pragmas.
// See ../../../../runtime/README.md for detailed descriptions.
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 {
case "go:nointerface":
if objabi.Fieldtrack_enabled != 0 {
p.setlineno(p.file.PkgName)
mkpackage(p.file.PkgName.Value)
+ if pragma, ok := p.file.Pragma.(*Pragma); ok {
+ p.checkUnused(pragma)
+ }
+
xtop = append(xtop, p.decls(p.file.DeclList)...)
for _, n := range p.linknames {
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)
ipkg := importfile(&val)
exprs = p.exprList(decl.Values)
}
+ if pragma, ok := decl.Pragma.(*Pragma); ok {
+ p.checkUnused(pragma)
+ }
+
p.setlineno(decl)
return variter(names, typ, exprs)
}
}
}
+ if pragma, ok := decl.Pragma.(*Pragma); ok {
+ p.checkUnused(pragma)
+ }
+
names := p.declNames(decl.NameList)
typ := p.typeExprOrNil(decl.Type)
param := n.Name.Param
param.Ntype = typ
- param.Pragma = decl.Pragma
param.Alias = decl.Alias
- if param.Alias && param.Pragma != 0 {
- yyerror("cannot specify directive with type alias")
- param.Pragma = 0
+ if pragma, ok := decl.Pragma.(*Pragma); ok {
+ if !decl.Alias {
+ param.Pragma = pragma.Flag & TypePragmas
+ pragma.Flag &^= TypePragmas
+ }
+ p.checkUnused(pragma)
}
nod := p.nod(decl, ODCLTYPE, n, nil)
f.Func.Nname.Name.Defn = f
f.Func.Nname.Name.Param.Ntype = t
- pragma := fun.Pragma
- f.Func.Pragma = fun.Pragma
- if pragma&Systemstack != 0 && pragma&Nosplit != 0 {
- yyerrorl(f.Pos, "go:nosplit and go:systemstack cannot be combined")
+ if pragma, ok := fun.Pragma.(*Pragma); ok {
+ f.Func.Pragma = pragma.Flag & FuncPragmas
+ if pragma.Flag&Systemstack != 0 && pragma.Flag&Nosplit != 0 {
+ yyerrorl(f.Pos, "go:nosplit and go:systemstack cannot be combined")
+ }
+ pragma.Flag &^= FuncPragmas
+ p.checkUnused(pragma)
}
if fun.Recv == nil {
"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.
-func (p *noder) pragma(pos syntax.Pos, text string) syntax.Pragma {
- switch {
- case strings.HasPrefix(text, "line "):
+func (p *noder) pragma(pos syntax.Pos, blankLine bool, text string, old syntax.Pragma) syntax.Pragma {
+ pragma, _ := old.(*Pragma)
+ 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
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 "):
f := strings.Fields(text)
if !(2 <= len(f) && len(f) <= 3) {
p.error(syntax.Error{Pos: pos, Msg: fmt.Sprintf("invalid library name %q in cgo_import_dynamic directive", lib)})
}
p.pragcgo(pos, text)
- return pragmaValue("go:cgo_import_dynamic")
+ pragma.Flag |= pragmaFlag("go:cgo_import_dynamic")
+ break
}
fallthrough
case strings.HasPrefix(text, "go:cgo_"):
if i := strings.Index(text, " "); i >= 0 {
verb = verb[:i]
}
- prag := pragmaValue(verb)
+ flag := pragmaFlag(verb)
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)})
}
- 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)})
}
- 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
import (
"cmd/compile/internal/ssa"
- "cmd/compile/internal/syntax"
"cmd/compile/internal/types"
"cmd/internal/obj"
"cmd/internal/objabi"
// OTYPE
//
// 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)
}
Endlineno src.XPos
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
numDefers int // number of defer calls in the function
// package PkgName; DeclList[0], DeclList[1], ...
type File struct {
+ Pragma Pragma
PkgName *Name
DeclList []Decl
Lines uint
// Path
// LocalPkgName Path
ImportDecl struct {
+ Group *Group // nil means not part of a group
+ Pragma Pragma
LocalPkgName *Name // including "."; nil means no rename present
Path *BasicLit
- Group *Group // nil means not part of a group
decl
}
// NameList = Values
// NameList Type = Values
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
+ Pragma Pragma
+ NameList []*Name
+ Type Expr // nil means no type
+ Values Expr // nil means no values
decl
}
// Name Type
TypeDecl struct {
+ Group *Group // nil means not part of a group
+ Pragma Pragma
Name *Name
Alias bool
Type Expr
- Group *Group // nil means not part of a group
- Pragma Pragma
decl
}
// NameList Type = Values
// NameList = Values
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
+ Pragma Pragma
+ NameList []*Name
+ Type Expr // nil means no type
+ Values Expr // nil means no values
decl
}
// func Receiver Name Type { Body }
// func Receiver Name Type
FuncDecl struct {
- Attr map[string]bool // go:attr map
- Recv *Field // nil means regular function
+ Pragma Pragma
+ Recv *Field // nil means regular function
Name *Name
Type *FuncType
Body *BlockStmt // nil means no body (forward declaration)
- Pragma Pragma // TODO(mdempsky): Cleaner solution.
decl
}
)
const trace = false
type parser struct {
- file *PosBase
- errh ErrorHandler
- mode Mode
+ file *PosBase
+ errh ErrorHandler
+ mode Mode
+ pragh PragmaHandler
scanner
base *PosBase // current position base
first error // first error encountered
errcnt int // number of errors encountered
- pragma Pragma // pragma flags
+ pragma Pragma // pragmas
fnest int // function nesting level (for error handling)
xnest int // expression nesting level (for complit ambiguity resolution)
p.file = file
p.errh = errh
p.mode = mode
+ p.pragh = pragh
p.scanner.init(
r,
// Error and directive handler for scanner.
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)
- if strings.HasPrefix(text, "line ") {
+ if (col == colbase || msg[1] == '*') && strings.HasPrefix(text, "line ") {
var pos Pos // position immediately following the comment
if msg[1] == '/' {
// line comment (newline is part of the comment)
// go: directive (but be conservative and test)
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,
p.base = file
p.first = nil
p.errcnt = 0
- p.pragma = 0
+ p.pragma = nil
p.fnest = 0
p.xnest = 0
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.
// The base's filename, line, and column values are extracted from text
// which is positioned at (tline, tcol) (only needed for error messages).
p.syntaxError("package statement must be first")
return nil
}
+ f.Pragma = p.takePragma()
f.PkgName = p.name()
p.want(_Semi)
// Reset p.pragma BEFORE advancing to the next token (consuming ';')
// since comments before may set pragmas for the next function decl.
- p.pragma = 0
+ p.clearPragma()
if p.tok != _EOF && !p.got(_Semi) {
p.syntaxError("after top level declaration")
}
// p.tok == _EOF
+ p.clearPragma()
f.Lines = p.line
return f
func (p *parser) appendGroup(list []Decl, f func(*Group) Decl) []Decl {
if p.tok == _Lparen {
g := new(Group)
+ p.clearPragma()
p.list(_Lparen, _Semi, _Rparen, func() bool {
list = append(list, f(g))
return false
d := new(ImportDecl)
d.pos = p.pos()
+ d.Group = group
+ d.Pragma = p.takePragma()
switch p.tok {
case _Name:
p.advance(_Semi, _Rparen)
return nil
}
- d.Group = group
return d
}
d := new(ConstDecl)
d.pos = p.pos()
+ d.Group = group
+ d.Pragma = p.takePragma()
d.NameList = p.nameList(p.name())
if p.tok != _EOF && p.tok != _Semi && p.tok != _Rparen {
d.Values = p.exprList()
}
}
- d.Group = group
return d
}
d := new(TypeDecl)
d.pos = p.pos()
+ d.Group = group
+ d.Pragma = p.takePragma()
d.Name = p.name()
d.Alias = p.gotAssign()
p.syntaxError("in type declaration")
p.advance(_Semi, _Rparen)
}
- d.Group = group
- d.Pragma = p.pragma
return d
}
d := new(VarDecl)
d.pos = p.pos()
+ d.Group = group
+ d.Pragma = p.takePragma()
d.NameList = p.nameList(p.name())
if p.gotAssign() {
d.Values = p.exprList()
}
}
- d.Group = group
return d
}
f := new(FuncDecl)
f.pos = p.pos()
+ f.Pragma = p.takePragma()
if p.tok == _Lparen {
rcvr := p.paramList()
if p.tok == _Lbrace {
f.Body = p.funcBody()
}
- f.Pragma = p.pragma
return f
}
// Most statements (assignments) start with an identifier;
// look for it first before doing anything more expensive.
if p.tok == _Name {
+ p.clearPragma()
lhs := p.exprList()
if label, ok := lhs.(*Name); ok && p.tok == _Colon {
return p.labeledStmtOrNil(label)
}
switch p.tok {
- case _Lbrace:
- return p.blockStmt("")
-
case _Var:
return p.declStmt(p.varDecl)
case _Type:
return p.declStmt(p.typeDecl)
+ }
+
+ p.clearPragma()
+
+ switch p.tok {
+ case _Lbrace:
+ return p.blockStmt("")
case _Operator, _Star:
switch p.op {
for p.tok != _EOF && p.tok != _Rbrace && p.tok != _Case && p.tok != _Default {
s := p.stmtOrNil()
+ p.clearPragma()
if s == nil {
break
}
// current token, valid after calling next()
line, col uint
+ blank bool // line is blank up to col
tok token
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
//
// If the scanner mode includes the directives (but not the comments)
// flag, only comments containing a //line, /*line, or //go: directive
-// are reported, in the same way as regular comments. Directives in
-// //-style comments are only recognized if they are at the beginning
-// of a line.
-//
+// are reported, in the same way as regular comments.
func (s *scanner) next() {
nlsemi := s.nlsemi
s.nlsemi = false
redo:
// skip white space
s.stop()
+ startLine, startCol := s.pos()
for s.ch == ' ' || s.ch == '\t' || s.ch == '\n' && !nlsemi || s.ch == '\r' {
s.nextch()
}
// token start
s.line, s.col = s.pos()
+ s.blank = s.line > startLine || startCol == colbase
s.start()
if isLetter(s.ch) || s.ch >= utf8.RuneSelf && s.atIdentChar(true) {
s.nextch()
return
}
- // directives must start at the beginning of the line (s.col == colbase)
- if s.mode&directives == 0 || s.col != colbase || (s.ch != 'g' && s.ch != 'l') {
+ // are we saving directives? or is this definitely not a directive?
+ if s.mode&directives == 0 || (s.ch != 'g' && s.ch != 'l') {
s.stop()
s.skipLine()
return
// An ErrorHandler is called for each error encountered reading a .go file.
type ErrorHandler func(err error)
-// A Pragma value is a set of flags that augment a function or
-// type declaration. Callers may assign meaning to the flags as
-// appropriate.
-type Pragma uint16
+// A Pragma value augments a package, import, const, func, type, or var declaration.
+// Its meaning is entirely up to the PragmaHandler,
+// except that nil is used to mean “no pragma seen.”
+type Pragma interface{}
-// A PragmaHandler is used to process //go: directives as
-// they're scanned. The returned Pragma value will be unioned into the
-// next FuncDecl node.
-type PragmaHandler func(pos Pos, text string) Pragma
+// A PragmaHandler is used to process //go: directives while scanning.
+// It is passed the current pragma value, which starts out being nil,
+// and it returns an updated pragma value.
+// 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
// syntax tree. If there are errors, Parse will return the first error found,
testTestDir(t, filepath.Join(runtime.GOROOT(), "test"),
"cmplxdivide.go", // also needs file cmplxdivide1.go - ignore
+ "directive.go", // tests compiler rejection of bad directive placement - ignore
)
}
--- /dev/null
+// 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"