@@ -18,6 +18,7 @@ import (
1818 "go/scanner"
1919 "go/token"
2020 "path/filepath"
21+ "sort"
2122 "strconv"
2223 "strings"
2324
@@ -42,6 +43,7 @@ type cgoPackage struct {
4243 fset * token.FileSet
4344 tokenFiles map [string ]* token.File
4445 definedGlobally map [string ]ast.Node
46+ noescapingFuncs map [string ]* noescapingFunc // #cgo noescape lines
4547 anonDecls map [interface {}]string
4648 cflags []string // CFlags from #cgo lines
4749 ldflags []string // LDFlags from #cgo lines
@@ -80,6 +82,13 @@ type bitfieldInfo struct {
8082 endBit int64 // may be 0 meaning "until the end of the field"
8183}
8284
85+ // Information about a #cgo noescape line in the source code.
86+ type noescapingFunc struct {
87+ name string
88+ pos token.Pos
89+ used bool // true if used somewhere in the source (for proper error reporting)
90+ }
91+
8392// cgoAliases list type aliases between Go and C, for types that are equivalent
8493// in both languages. See addTypeAliases.
8594var cgoAliases = map [string ]string {
@@ -255,6 +264,7 @@ func Process(files []*ast.File, dir, importPath string, fset *token.FileSet, cfl
255264 fset : fset ,
256265 tokenFiles : map [string ]* token.File {},
257266 definedGlobally : map [string ]ast.Node {},
267+ noescapingFuncs : map [string ]* noescapingFunc {},
258268 anonDecls : map [interface {}]string {},
259269 visitedFiles : map [string ][]byte {},
260270 }
@@ -418,6 +428,22 @@ func Process(files []*ast.File, dir, importPath string, fset *token.FileSet, cfl
418428 })
419429 }
420430
431+ // Show an error when a #cgo noescape line isn't used in practice.
432+ // This matches upstream Go. I think the goal is to avoid issues with
433+ // misspelled function names, which seems very useful.
434+ var unusedNoescapeLines []* noescapingFunc
435+ for _ , value := range p .noescapingFuncs {
436+ if ! value .used {
437+ unusedNoescapeLines = append (unusedNoescapeLines , value )
438+ }
439+ }
440+ sort .SliceStable (unusedNoescapeLines , func (i , j int ) bool {
441+ return unusedNoescapeLines [i ].pos < unusedNoescapeLines [j ].pos
442+ })
443+ for _ , value := range unusedNoescapeLines {
444+ p .addError (value .pos , fmt .Sprintf ("function %#v in #cgo noescape line is not used" , value .name ))
445+ }
446+
421447 // Print the newly generated in-memory AST, for debugging.
422448 //ast.Print(fset, p.generated)
423449
@@ -483,6 +509,33 @@ func (p *cgoPackage) parseCGoPreprocessorLines(text string, pos token.Pos) strin
483509 }
484510 text = text [:lineStart ] + string (spaces ) + text [lineEnd :]
485511
512+ allFields := strings .Fields (line [4 :])
513+ switch allFields [0 ] {
514+ case "noescape" :
515+ // The code indicates that pointer parameters will not be captured
516+ // by the called C function.
517+ if len (allFields ) < 2 {
518+ p .addErrorAfter (pos , text [:lineStart ], "missing function name in #cgo noescape line" )
519+ continue
520+ }
521+ if len (allFields ) > 2 {
522+ p .addErrorAfter (pos , text [:lineStart ], "multiple function names in #cgo noescape line" )
523+ continue
524+ }
525+ name := allFields [1 ]
526+ p .noescapingFuncs [name ] = & noescapingFunc {
527+ name : name ,
528+ pos : pos ,
529+ used : false ,
530+ }
531+ continue
532+ case "nocallback" :
533+ // We don't do anything special when calling a C function, so there
534+ // appears to be no optimization that we can do here.
535+ // Accept, but ignore the parameter for compatibility.
536+ continue
537+ }
538+
486539 // Get the text before the colon in the #cgo directive.
487540 colon := strings .IndexByte (line , ':' )
488541 if colon < 0 {
0 commit comments