aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorAyke van Laethem <[email protected]>2019-11-22 11:32:57 +0100
committerRon Evans <[email protected]>2019-11-25 09:32:03 +0100
commit10e1420237164d16f64aae691dac12f692d80aed (patch)
tree946d28ec975d93f1ffd958a2c3b576570cb00c10
parent6a1bb134f9dfddf915421d15c0bb6f8a5fcbfc25 (diff)
downloadtinygo-10e1420237164d16f64aae691dac12f692d80aed.tar.gz
tinygo-10e1420237164d16f64aae691dac12f692d80aed.zip
cgo: implement #cgo CFLAGS
This implementation is still very limited but provides a base to build upon. Limitations: * CGO_CFLAGS etc is not taken into account. * These CFLAGS are not used in C files compiled with the package. * Other flags (CPPFLAGS, LDFAGS, ...) are not yet implemented.
-rw-r--r--cgo/cgo.go98
-rw-r--r--cgo/cgo_test.go2
-rw-r--r--cgo/libclang.go25
-rw-r--r--cgo/security.go301
-rw-r--r--cgo/security_test.go260
-rw-r--r--cgo/testdata/flags.go26
-rw-r--r--cgo/testdata/flags.out.go32
-rw-r--r--go.mod1
-rw-r--r--go.sum2
9 files changed, 738 insertions, 9 deletions
diff --git a/cgo/cgo.go b/cgo/cgo.go
index f20ea0d58..5799b4fa5 100644
--- a/cgo/cgo.go
+++ b/cgo/cgo.go
@@ -19,6 +19,7 @@ import (
"strconv"
"strings"
+ "github.com/google/shlex"
"golang.org/x/tools/go/ast/astutil"
)
@@ -235,6 +236,7 @@ func Process(files []*ast.File, dir string, fset *token.FileSet, cflags []string
}
// Find `import "C"` statements in the file.
+ var statements []*ast.GenDecl
for _, f := range files {
for i := 0; i < len(f.Decls); i++ {
decl := f.Decls[i]
@@ -258,14 +260,9 @@ func Process(files []*ast.File, dir string, fset *token.FileSet, cflags []string
if path != "C" {
continue
}
- cgoComment := genDecl.Doc.Text()
- pos := genDecl.Pos()
- if genDecl.Doc != nil {
- pos = genDecl.Doc.Pos()
- }
- position := fset.PositionFor(pos, true)
- p.parseFragment(cgoComment+cgoTypes, cflags, position.Filename, position.Line)
+ // Found a CGo statement.
+ statements = append(statements, genDecl)
// Remove this import declaration.
f.Decls = append(f.Decls[:i], f.Decls[i+1:]...)
@@ -276,6 +273,93 @@ func Process(files []*ast.File, dir string, fset *token.FileSet, cflags []string
//ast.Print(fset, f)
}
+ // Find all #cgo lines.
+ for _, genDecl := range statements {
+ if genDecl.Doc == nil {
+ continue
+ }
+ for _, comment := range genDecl.Doc.List {
+ for {
+ // Extract the #cgo line, and replace it with spaces.
+ // Replacing with spaces makes sure that error locations are
+ // still correct, while not interfering with parsing in any way.
+ lineStart := strings.Index(comment.Text, "#cgo ")
+ if lineStart < 0 {
+ break
+ }
+ lineLen := strings.IndexByte(comment.Text[lineStart:], '\n')
+ if lineLen < 0 {
+ lineLen = len(comment.Text) - lineStart
+ }
+ lineEnd := lineStart + lineLen
+ line := comment.Text[lineStart:lineEnd]
+ spaces := make([]byte, len(line))
+ for i := range spaces {
+ spaces[i] = ' '
+ }
+ lenBefore := len(comment.Text)
+ comment.Text = comment.Text[:lineStart] + string(spaces) + comment.Text[lineEnd:]
+ if len(comment.Text) != lenBefore {
+ println(lenBefore, len(comment.Text))
+ panic("length of preamble changed!")
+ }
+
+ // Get the text before the colon in the #cgo directive.
+ colon := strings.IndexByte(line, ':')
+ if colon < 0 {
+ p.addErrorAfter(comment.Slash, comment.Text[:lineStart], "missing colon in #cgo line")
+ continue
+ }
+
+ // Extract the fields before the colon. These fields are a list
+ // of build tags and the C environment variable.
+ fields := strings.Fields(line[4:colon])
+ if len(fields) == 0 {
+ p.addErrorAfter(comment.Slash, comment.Text[:lineStart+colon-1], "invalid #cgo line")
+ continue
+ }
+
+ if len(fields) > 1 {
+ p.addErrorAfter(comment.Slash, comment.Text[:lineStart+5], "not implemented: build constraints in #cgo line")
+ continue
+ }
+
+ name := fields[len(fields)-1]
+ value := line[colon+1:]
+ switch name {
+ case "CFLAGS":
+ flags, err := shlex.Split(value)
+ if err != nil {
+ // TODO: find the exact location where the error happened.
+ p.addErrorAfter(comment.Slash, comment.Text[:lineStart+colon+1], "failed to parse flags in #cgo line: "+err.Error())
+ continue
+ }
+ if err := checkCompilerFlags(name, flags); err != nil {
+ p.addErrorAfter(comment.Slash, comment.Text[:lineStart+colon+1], err.Error())
+ continue
+ }
+ cflags = append(cflags, flags...)
+ default:
+ startPos := strings.LastIndex(line[4:colon], name) + 4
+ p.addErrorAfter(comment.Slash, comment.Text[:lineStart+startPos], "invalid #cgo line: "+name)
+ continue
+ }
+ }
+ }
+ }
+
+ // Process all CGo imports.
+ for _, genDecl := range statements {
+ cgoComment := genDecl.Doc.Text()
+
+ pos := genDecl.Pos()
+ if genDecl.Doc != nil {
+ pos = genDecl.Doc.Pos()
+ }
+ position := fset.PositionFor(pos, true)
+ p.parseFragment(cgoComment+cgoTypes, cflags, position.Filename, position.Line)
+ }
+
// Declare functions found by libclang.
p.addFuncDecls()
diff --git a/cgo/cgo_test.go b/cgo/cgo_test.go
index a2dbbd52a..65ac14954 100644
--- a/cgo/cgo_test.go
+++ b/cgo/cgo_test.go
@@ -22,7 +22,7 @@ var flagUpdate = flag.Bool("update", false, "Update images based on test output.
func TestCGo(t *testing.T) {
var cflags = []string{"--target=armv6m-none-eabi"}
- for _, name := range []string{"basic", "errors", "types"} {
+ for _, name := range []string{"basic", "errors", "types", "flags"} {
name := name // avoid a race condition
t.Run(name, func(t *testing.T) {
t.Parallel()
diff --git a/cgo/libclang.go b/cgo/libclang.go
index 2e918a497..3aaded21e 100644
--- a/cgo/libclang.go
+++ b/cgo/libclang.go
@@ -325,9 +325,32 @@ func (p *cgoPackage) getClangLocationPosition(location C.CXSourceLocation, tu C.
return positionFile.Pos(int(offset))
}
-// addError is a utility function to add an error to the list of errors.
+// addError is a utility function to add an error to the list of errors. It will
+// convert the token position to a line/column position first, and call
+// addErrorAt.
func (p *cgoPackage) addError(pos token.Pos, msg string) {
+ p.addErrorAt(p.fset.PositionFor(pos, true), msg)
+}
+
+// addErrorAfter is like addError, but adds the text `after` to the source
+// location.
+func (p *cgoPackage) addErrorAfter(pos token.Pos, after, msg string) {
position := p.fset.PositionFor(pos, true)
+ lines := strings.Split(after, "\n")
+ if len(lines) != 1 {
+ // Adjust lines.
+ // For why we can't just do pos+token.Pos(len(after)), see:
+ // https://github.com/golang/go/issues/35803
+ position.Line += len(lines) - 1
+ position.Column = len(lines[len(lines)-1]) + 1
+ } else {
+ position.Column += len(after)
+ }
+ p.addErrorAt(position, msg)
+}
+
+// addErrorAt is a utility function to add an error to the list of errors.
+func (p *cgoPackage) addErrorAt(position token.Position, msg string) {
if filepath.IsAbs(position.Filename) {
// Relative paths for readability, like other Go parser errors.
relpath, err := filepath.Rel(p.dir, position.Filename)
diff --git a/cgo/security.go b/cgo/security.go
new file mode 100644
index 000000000..2fea40c8a
--- /dev/null
+++ b/cgo/security.go
@@ -0,0 +1,301 @@
+// Copyright 2018 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.
+
+// This file has been copied from the Go 1.13 release tree.
+
+// Checking of compiler and linker flags.
+// We must avoid flags like -fplugin=, which can allow
+// arbitrary code execution during the build.
+// Do not make changes here without carefully
+// considering the implications.
+// (That's why the code is isolated in a file named security.go.)
+//
+// Note that -Wl,foo means split foo on commas and pass to
+// the linker, so that -Wl,-foo,bar means pass -foo bar to
+// the linker. Similarly -Wa,foo for the assembler and so on.
+// If any of these are permitted, the wildcard portion must
+// disallow commas.
+//
+// Note also that GNU binutils accept any argument @foo
+// as meaning "read more flags from the file foo", so we must
+// guard against any command-line argument beginning with @,
+// even things like "-I @foo".
+// We use safeArg (which is even more conservative)
+// to reject these.
+//
+// Even worse, gcc -I@foo (one arg) turns into cc1 -I @foo (two args),
+// so although gcc doesn't expand the @foo, cc1 will.
+// So out of paranoia, we reject @ at the beginning of every
+// flag argument that might be split into its own argument.
+
+package cgo
+
+import (
+ "fmt"
+ "os"
+ "regexp"
+ "strings"
+ "unicode/utf8"
+)
+
+var re = regexp.MustCompile
+
+var validCompilerFlags = []*regexp.Regexp{
+ re(`-D([A-Za-z_].*)`),
+ re(`-F([^@\-].*)`),
+ re(`-I([^@\-].*)`),
+ re(`-O`),
+ re(`-O([^@\-].*)`),
+ re(`-W`),
+ re(`-W([^@,]+)`), // -Wall but not -Wa,-foo.
+ re(`-Wa,-mbig-obj`),
+ re(`-Wp,-D([A-Za-z_].*)`),
+ re(`-ansi`),
+ re(`-f(no-)?asynchronous-unwind-tables`),
+ re(`-f(no-)?blocks`),
+ re(`-f(no-)builtin-[a-zA-Z0-9_]*`),
+ re(`-f(no-)?common`),
+ re(`-f(no-)?constant-cfstrings`),
+ re(`-fdiagnostics-show-note-include-stack`),
+ re(`-f(no-)?eliminate-unused-debug-types`),
+ re(`-f(no-)?exceptions`),
+ re(`-f(no-)?fast-math`),
+ re(`-f(no-)?inline-functions`),
+ re(`-finput-charset=([^@\-].*)`),
+ re(`-f(no-)?fat-lto-objects`),
+ re(`-f(no-)?keep-inline-dllexport`),
+ re(`-f(no-)?lto`),
+ re(`-fmacro-backtrace-limit=(.+)`),
+ re(`-fmessage-length=(.+)`),
+ re(`-f(no-)?modules`),
+ re(`-f(no-)?objc-arc`),
+ re(`-f(no-)?objc-nonfragile-abi`),
+ re(`-f(no-)?objc-legacy-dispatch`),
+ re(`-f(no-)?omit-frame-pointer`),
+ re(`-f(no-)?openmp(-simd)?`),
+ re(`-f(no-)?permissive`),
+ re(`-f(no-)?(pic|PIC|pie|PIE)`),
+ re(`-f(no-)?plt`),
+ re(`-f(no-)?rtti`),
+ re(`-f(no-)?split-stack`),
+ re(`-f(no-)?stack-(.+)`),
+ re(`-f(no-)?strict-aliasing`),
+ re(`-f(un)signed-char`),
+ re(`-f(no-)?use-linker-plugin`), // safe if -B is not used; we don't permit -B
+ re(`-f(no-)?visibility-inlines-hidden`),
+ re(`-fsanitize=(.+)`),
+ re(`-ftemplate-depth-(.+)`),
+ re(`-fvisibility=(.+)`),
+ re(`-g([^@\-].*)?`),
+ re(`-m32`),
+ re(`-m64`),
+ re(`-m(abi|arch|cpu|fpu|tune)=([^@\-].*)`),
+ re(`-m(no-)?v?aes`),
+ re(`-marm`),
+ re(`-m(no-)?avx[0-9a-z]*`),
+ re(`-mfloat-abi=([^@\-].*)`),
+ re(`-mfpmath=[0-9a-z,+]*`),
+ re(`-m(no-)?avx[0-9a-z.]*`),
+ re(`-m(no-)?ms-bitfields`),
+ re(`-m(no-)?stack-(.+)`),
+ re(`-mmacosx-(.+)`),
+ re(`-mios-simulator-version-min=(.+)`),
+ re(`-miphoneos-version-min=(.+)`),
+ re(`-mtvos-simulator-version-min=(.+)`),
+ re(`-mtvos-version-min=(.+)`),
+ re(`-mwatchos-simulator-version-min=(.+)`),
+ re(`-mwatchos-version-min=(.+)`),
+ re(`-mnop-fun-dllimport`),
+ re(`-m(no-)?sse[0-9.]*`),
+ re(`-m(no-)?ssse3`),
+ re(`-mthumb(-interwork)?`),
+ re(`-mthreads`),
+ re(`-mwindows`),
+ re(`--param=ssp-buffer-size=[0-9]*`),
+ re(`-pedantic(-errors)?`),
+ re(`-pipe`),
+ re(`-pthread`),
+ re(`-?-std=([^@\-].*)`),
+ re(`-?-stdlib=([^@\-].*)`),
+ re(`--sysroot=([^@\-].*)`),
+ re(`-w`),
+ re(`-x([^@\-].*)`),
+ re(`-v`),
+}
+
+var validCompilerFlagsWithNextArg = []string{
+ "-arch",
+ "-D",
+ "-I",
+ "-framework",
+ "-isysroot",
+ "-isystem",
+ "--sysroot",
+ "-target",
+ "-x",
+}
+
+var validLinkerFlags = []*regexp.Regexp{
+ re(`-F([^@\-].*)`),
+ re(`-l([^@\-].*)`),
+ re(`-L([^@\-].*)`),
+ re(`-O`),
+ re(`-O([^@\-].*)`),
+ re(`-f(no-)?(pic|PIC|pie|PIE)`),
+ re(`-f(no-)?openmp(-simd)?`),
+ re(`-fsanitize=([^@\-].*)`),
+ re(`-flat_namespace`),
+ re(`-g([^@\-].*)?`),
+ re(`-headerpad_max_install_names`),
+ re(`-m(abi|arch|cpu|fpu|tune)=([^@\-].*)`),
+ re(`-mfloat-abi=([^@\-].*)`),
+ re(`-mmacosx-(.+)`),
+ re(`-mios-simulator-version-min=(.+)`),
+ re(`-miphoneos-version-min=(.+)`),
+ re(`-mthreads`),
+ re(`-mwindows`),
+ re(`-(pic|PIC|pie|PIE)`),
+ re(`-pthread`),
+ re(`-rdynamic`),
+ re(`-shared`),
+ re(`-?-static([-a-z0-9+]*)`),
+ re(`-?-stdlib=([^@\-].*)`),
+ re(`-v`),
+
+ // Note that any wildcards in -Wl need to exclude comma,
+ // since -Wl splits its argument at commas and passes
+ // them all to the linker uninterpreted. Allowing comma
+ // in a wildcard would allow tunnelling arbitrary additional
+ // linker arguments through one of these.
+ re(`-Wl,--(no-)?allow-multiple-definition`),
+ re(`-Wl,--(no-)?allow-shlib-undefined`),
+ re(`-Wl,--(no-)?as-needed`),
+ re(`-Wl,-Bdynamic`),
+ re(`-Wl,-berok`),
+ re(`-Wl,-Bstatic`),
+ re(`-WL,-O([^@,\-][^,]*)?`),
+ re(`-Wl,-d[ny]`),
+ re(`-Wl,--disable-new-dtags`),
+ re(`-Wl,-e[=,][a-zA-Z0-9]*`),
+ re(`-Wl,--enable-new-dtags`),
+ re(`-Wl,--end-group`),
+ re(`-Wl,--(no-)?export-dynamic`),
+ re(`-Wl,-framework,[^,@\-][^,]+`),
+ re(`-Wl,-headerpad_max_install_names`),
+ re(`-Wl,--no-undefined`),
+ re(`-Wl,-R([^@\-][^,@]*$)`),
+ re(`-Wl,--just-symbols[=,]([^,@\-][^,@]+)`),
+ re(`-Wl,-rpath(-link)?[=,]([^,@\-][^,]+)`),
+ re(`-Wl,-s`),
+ re(`-Wl,-search_paths_first`),
+ re(`-Wl,-sectcreate,([^,@\-][^,]+),([^,@\-][^,]+),([^,@\-][^,]+)`),
+ re(`-Wl,--start-group`),
+ re(`-Wl,-?-static`),
+ re(`-Wl,-?-subsystem,(native|windows|console|posix|xbox)`),
+ re(`-Wl,-syslibroot[=,]([^,@\-][^,]+)`),
+ re(`-Wl,-undefined[=,]([^,@\-][^,]+)`),
+ re(`-Wl,-?-unresolved-symbols=[^,]+`),
+ re(`-Wl,--(no-)?warn-([^,]+)`),
+ re(`-Wl,-z,(no)?execstack`),
+ re(`-Wl,-z,relro`),
+
+ re(`[a-zA-Z0-9_/].*\.(a|o|obj|dll|dylib|so)`), // direct linker inputs: x.o or libfoo.so (but not -foo.o or @foo.o)
+ re(`\./.*\.(a|o|obj|dll|dylib|so)`),
+}
+
+var validLinkerFlagsWithNextArg = []string{
+ "-arch",
+ "-F",
+ "-l",
+ "-L",
+ "-framework",
+ "-isysroot",
+ "--sysroot",
+ "-target",
+ "-Wl,-framework",
+ "-Wl,-rpath",
+ "-Wl,-R",
+ "-Wl,--just-symbols",
+ "-Wl,-undefined",
+}
+
+func checkCompilerFlags(name string, list []string) error {
+ return checkFlags(name, list, validCompilerFlags, validCompilerFlagsWithNextArg)
+}
+
+func checkLinkerFlags(name string, list []string) error {
+ return checkFlags(name, list, validLinkerFlags, validLinkerFlagsWithNextArg)
+}
+
+func checkFlags(name string, list []string, valid []*regexp.Regexp, validNext []string) error {
+ // Let users override rules with $CGO_CFLAGS_ALLOW, $CGO_CFLAGS_DISALLOW, etc.
+ var (
+ allow *regexp.Regexp
+ disallow *regexp.Regexp
+ )
+ if env := os.Getenv("CGO_" + name + "_ALLOW"); env != "" {
+ r, err := regexp.Compile(env)
+ if err != nil {
+ return fmt.Errorf("parsing $CGO_%s_ALLOW: %v", name, err)
+ }
+ allow = r
+ }
+ if env := os.Getenv("CGO_" + name + "_DISALLOW"); env != "" {
+ r, err := regexp.Compile(env)
+ if err != nil {
+ return fmt.Errorf("parsing $CGO_%s_DISALLOW: %v", name, err)
+ }
+ disallow = r
+ }
+
+Args:
+ for i := 0; i < len(list); i++ {
+ arg := list[i]
+ if disallow != nil && disallow.FindString(arg) == arg {
+ goto Bad
+ }
+ if allow != nil && allow.FindString(arg) == arg {
+ continue Args
+ }
+ for _, re := range valid {
+ if re.FindString(arg) == arg { // must be complete match
+ continue Args
+ }
+ }
+ for _, x := range validNext {
+ if arg == x {
+ if i+1 < len(list) && safeArg(list[i+1]) {
+ i++
+ continue Args
+ }
+
+ // Permit -Wl,-framework -Wl,name.
+ if i+1 < len(list) &&
+ strings.HasPrefix(arg, "-Wl,") &&
+ strings.HasPrefix(list[i+1], "-Wl,") &&
+ safeArg(list[i+1][4:]) &&
+ !strings.Contains(list[i+1][4:], ",") {
+ i++
+ continue Args
+ }
+
+ if i+1 < len(list) {
+ return fmt.Errorf("invalid flag: %s %s (see https://golang.org/s/invalidflag)", arg, list[i+1])
+ }
+ return fmt.Errorf("invalid flag: %s without argument (see https://golang.org/s/invalidflag)", arg)
+ }
+ }
+ Bad:
+ return fmt.Errorf("invalid flag: %s", arg)
+ }
+ return nil
+}
+
+func safeArg(name string) bool {
+ if name == "" {
+ return false
+ }
+ c := name[0]
+ return '0' <= c && c <= '9' || 'A' <= c && c <= 'Z' || 'a' <= c && c <= 'z' || c == '.' || c == '_' || c == '/' || c >= utf8.RuneSelf
+}
diff --git a/cgo/security_test.go b/cgo/security_test.go
new file mode 100644
index 000000000..98fd25fb8
--- /dev/null
+++ b/cgo/security_test.go
@@ -0,0 +1,260 @@
+// Copyright 2018 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.
+
+// This file has been copied from the Go 1.13 release tree.
+
+package cgo
+
+import (
+ "os"
+ "testing"
+)
+
+var goodCompilerFlags = [][]string{
+ {"-DFOO"},
+ {"-Dfoo=bar"},
+ {"-F/Qt"},
+ {"-I/"},
+ {"-I/etc/passwd"},
+ {"-I."},
+ {"-O"},
+ {"-O2"},
+ {"-Osmall"},
+ {"-W"},
+ {"-Wall"},
+ {"-fobjc-arc"},
+ {"-fno-objc-arc"},
+ {"-fomit-frame-pointer"},
+ {"-fno-omit-frame-pointer"},
+ {"-fpic"},
+ {"-fno-pic"},
+ {"-fPIC"},
+ {"-fno-PIC"},
+ {"-fpie"},
+ {"-fno-pie"},
+ {"-fPIE"},
+ {"-fno-PIE"},
+ {"-fsplit-stack"},
+ {"-fno-split-stack"},
+ {"-fstack-xxx"},
+ {"-fno-stack-xxx"},
+ {"-fsanitize=hands"},
+ {"-g"},
+ {"-ggdb"},
+ {"-march=souza"},
+ {"-mcpu=123"},
+ {"-mfpu=123"},
+ {"-mtune=happybirthday"},
+ {"-mstack-overflow"},
+ {"-mno-stack-overflow"},
+ {"-mmacosx-version"},
+ {"-mnop-fun-dllimport"},
+ {"-pthread"},
+ {"-std=c99"},
+ {"-xc"},
+ {"-D", "FOO"},
+ {"-D", "foo=bar"},
+ {"-I", "."},
+ {"-I", "/etc/passwd"},
+ {"-I", "世界"},
+ {"-framework", "Chocolate"},
+ {"-x", "c"},
+ {"-v"},
+}
+
+var badCompilerFlags = [][]string{
+ {"-D@X"},
+ {"-D-X"},
+ {"-F@dir"},
+ {"-F-dir"},
+ {"-I@dir"},
+ {"-I-dir"},
+ {"-O@1"},
+ {"-Wa,-foo"},
+ {"-W@foo"},
+ {"-g@gdb"},
+ {"-g-gdb"},
+ {"-march=@dawn"},
+ {"-march=-dawn"},
+ {"-std=@c99"},
+ {"-std=-c99"},
+ {"-x@c"},
+ {"-x-c"},
+ {"-D", "@foo"},
+ {"-D", "-foo"},
+ {"-I", "@foo"},
+ {"-I", "-foo"},
+ {"-framework", "-Caffeine"},
+ {"-framework", "@Home"},
+ {"-x", "--c"},
+ {"-x", "@obj"},
+}
+
+func TestCheckCompilerFlags(t *testing.T) {
+ for _, f := range goodCompilerFlags {
+ if err := checkCompilerFlags("test", f); err != nil {
+ t.Errorf("unexpected error for %q: %v", f, err)
+ }
+ }
+ for _, f := range badCompilerFlags {
+ if err := checkCompilerFlags("test", f); err == nil {
+ t.Errorf("missing error for %q", f)
+ }
+ }
+}
+
+var goodLinkerFlags = [][]string{
+ {"-Fbar"},
+ {"-lbar"},
+ {"-Lbar"},
+ {"-fpic"},
+ {"-fno-pic"},
+ {"-fPIC"},
+ {"-fno-PIC"},
+ {"-fpie"},
+ {"-fno-pie"},
+ {"-fPIE"},
+ {"-fno-PIE"},
+ {"-fsanitize=hands"},
+ {"-g"},
+ {"-ggdb"},
+ {"-march=souza"},
+ {"-mcpu=123"},
+ {"-mfpu=123"},
+ {"-mtune=happybirthday"},
+ {"-pic"},
+ {"-pthread"},
+ {"-Wl,-rpath,foo"},
+ {"-Wl,-rpath,$ORIGIN/foo"},
+ {"-Wl,-R", "/foo"},
+ {"-Wl,-R", "foo"},
+ {"-Wl,-R,foo"},
+ {"-Wl,--just-symbols=foo"},
+ {"-Wl,--just-symbols,foo"},
+ {"-Wl,--warn-error"},
+ {"-Wl,--no-warn-error"},
+ {"foo.so"},
+ {"_世界.dll"},
+ {"./x.o"},
+ {"libcgosotest.dylib"},
+ {"-F", "framework"},
+ {"-l", "."},
+ {"-l", "/etc/passwd"},
+ {"-l", "世界"},
+ {"-L", "framework"},
+ {"-framework", "Chocolate"},
+ {"-v"},
+ {"-Wl,-framework", "-Wl,Chocolate"},
+ {"-Wl,-framework,Chocolate"},
+ {"-Wl,-unresolved-symbols=ignore-all"},
+}
+
+var badLinkerFlags = [][]string{
+ {"-DFOO"},
+ {"-Dfoo=bar"},
+ {"-W"},
+ {"-Wall"},
+ {"-fobjc-arc"},
+ {"-fno-objc-arc"},
+ {"-fomit-frame-pointer"},
+ {"-fno-omit-frame-pointer"},
+ {"-fsplit-stack"},
+ {"-fno-split-stack"},
+ {"-fstack-xxx"},
+ {"-fno-stack-xxx"},
+ {"-mstack-overflow"},
+ {"-mno-stack-overflow"},
+ {"-mnop-fun-dllimport"},
+ {"-std=c99"},
+ {"-xc"},
+ {"-D", "FOO"},
+ {"-D", "foo=bar"},
+ {"-I", "FOO"},
+ {"-L", "@foo"},
+ {"-L", "-foo"},
+ {"-x", "c"},
+ {"-D@X"},
+ {"-D-X"},
+ {"-I@dir"},
+ {"-I-dir"},
+ {"-O@1"},
+ {"-Wa,-foo"},
+ {"-W@foo"},
+ {"-g@gdb"},
+ {"-g-gdb"},
+ {"-march=@dawn"},
+ {"-march=-dawn"},
+ {"-std=@c99"},
+ {"-std=-c99"},
+ {"-x@c"},
+ {"-x-c"},
+ {"-D", "@foo"},
+ {"-D", "-foo"},
+ {"-I", "@foo"},
+ {"-I", "-foo"},
+ {"-l", "@foo"},
+ {"-l", "-foo"},
+ {"-framework", "-Caffeine"},
+ {"-framework", "@Home"},
+ {"-Wl,-framework,-Caffeine"},
+ {"-Wl,-framework", "-Wl,@Home"},
+ {"-Wl,-framework", "@Home"},
+ {"-Wl,-framework,Chocolate,@Home"},
+ {"-x", "--c"},
+ {"-x", "@obj"},
+ {"-Wl,-rpath,@foo"},
+ {"-Wl,-R,foo,bar"},
+ {"-Wl,-R,@foo"},
+ {"-Wl,--just-symbols,@foo"},
+ {"../x.o"},
+}
+
+func TestCheckLinkerFlags(t *testing.T) {
+ for _, f := range goodLinkerFlags {
+ if err := checkLinkerFlags("test", f); err != nil {
+ t.Errorf("unexpected error for %q: %v", f, err)
+ }
+ }
+ for _, f := range badLinkerFlags {
+ if err := checkLinkerFlags("test", f); err == nil {
+ t.Errorf("missing error for %q", f)
+ }
+ }
+}
+
+func TestCheckFlagAllowDisallow(t *testing.T) {
+ if err := checkCompilerFlags("TEST", []string{"-disallow"}); err == nil {
+ t.Fatalf("missing error for -disallow")
+ }
+ os.Setenv("CGO_TEST_ALLOW", "-disallo")
+ if err := checkCompilerFlags("TEST", []string{"-disallow"}); err == nil {
+ t.Fatalf("missing error for -disallow with CGO_TEST_ALLOW=-disallo")
+ }
+ os.Setenv("CGO_TEST_ALLOW", "-disallow")
+ if err := checkCompilerFlags("TEST", []string{"-disallow"}); err != nil {
+ t.Fatalf("unexpected error for -disallow with CGO_TEST_ALLOW=-disallow: %v", err)
+ }
+ os.Unsetenv("CGO_TEST_ALLOW")
+
+ if err := checkCompilerFlags("TEST", []string{"-Wall"}); err != nil {
+ t.Fatalf("unexpected error for -Wall: %v", err)
+ }
+ os.Setenv("CGO_TEST_DISALLOW", "-Wall")
+ if err := checkCompilerFlags("TEST", []string{"-Wall"}); err == nil {
+ t.Fatalf("missing error for -Wall with CGO_TEST_DISALLOW=-Wall")
+ }
+ os.Setenv("CGO_TEST_ALLOW", "-Wall") // disallow wins
+ if err := checkCompilerFlags("TEST", []string{"-Wall"}); err == nil {
+ t.Fatalf("missing error for -Wall with CGO_TEST_DISALLOW=-Wall and CGO_TEST_ALLOW=-Wall")
+ }
+
+ os.Setenv("CGO_TEST_ALLOW", "-fplugin.*")
+ os.Setenv("CGO_TEST_DISALLOW", "-fplugin=lint.so")
+ if err := checkCompilerFlags("TEST", []string{"-fplugin=faster.so"}); err != nil {
+ t.Fatalf("unexpected error for -fplugin=faster.so: %v", err)
+ }
+ if err := checkCompilerFlags("TEST", []string{"-fplugin=lint.so"}); err == nil {
+ t.Fatalf("missing error for -fplugin=lint.so: %v", err)
+ }
+}
diff --git a/cgo/testdata/flags.go b/cgo/testdata/flags.go
new file mode 100644
index 000000000..bb518e554
--- /dev/null
+++ b/cgo/testdata/flags.go
@@ -0,0 +1,26 @@
+package main
+
+/*
+// this name doesn't exist
+#cgo NOFLAGS: -foo
+
+// unknown flag
+#cgo CFLAGS: -fdoes-not-exist -DNOTDEFINED
+
+#cgo CFLAGS: -DFOO
+
+#if defined(FOO)
+#define BAR 3
+#else
+#define BAR 5
+#endif
+
+#if defined(NOTDEFINED)
+#warning flag must not be defined
+#endif
+*/
+import "C"
+
+var (
+ _ = C.BAR
+)
diff --git a/cgo/testdata/flags.out.go b/cgo/testdata/flags.out.go
new file mode 100644
index 000000000..dba5d1fbe
--- /dev/null
+++ b/cgo/testdata/flags.out.go
@@ -0,0 +1,32 @@
+// CGo errors:
+// testdata/flags.go:5:7: invalid #cgo line: NOFLAGS
+// testdata/flags.go:8:13: invalid flag: -fdoes-not-exist
+
+package main
+
+import "unsafe"
+
+var _ unsafe.Pointer
+
+const C.BAR = 3
+
+type C.int16_t = int16
+type C.int32_t = int32
+type C.int64_t = int64
+type C.int8_t = int8
+type C.uint16_t = uint16
+type C.uint32_t = uint32
+type C.uint64_t = uint64
+type C.uint8_t = uint8
+type C.uintptr_t = uintptr
+type C.char uint8
+type C.int int32
+type C.long int32
+type C.longlong int64
+type C.schar int8
+type C.short int16
+type C.uchar uint8
+type C.uint uint32
+type C.ulong uint32
+type C.ulonglong uint64
+type C.ushort uint16
diff --git a/go.mod b/go.mod
index 61c2b6f70..25a228921 100644
--- a/go.mod
+++ b/go.mod
@@ -5,6 +5,7 @@ go 1.11
require (
github.com/blakesmith/ar v0.0.0-20150311145944-8bd4349a67f2
github.com/creack/goselect v0.1.0 // indirect
+ github.com/google/shlex v0.0.0-20181106134648-c34317bd91bf
github.com/marcinbor85/gohex v0.0.0-20180128172054-7a43cd876e46
go.bug.st/serial.v1 v0.0.0-20180827123349-5f7892a7bb45
golang.org/x/sys v0.0.0-20191010194322-b09406accb47 // indirect
diff --git a/go.sum b/go.sum
index a31f3cf0a..1cd24e10d 100644
--- a/go.sum
+++ b/go.sum
@@ -3,6 +3,8 @@ github.com/blakesmith/ar v0.0.0-20150311145944-8bd4349a67f2/go.mod h1:PkYb9DJNAw
github.com/creack/goselect v0.1.0 h1:4QiXIhcpSQF50XGaBsFzesjwX/1qOY5bOveQPmN9CXY=
github.com/creack/goselect v0.1.0/go.mod h1:gHrIcH/9UZDn2qgeTUeW5K9eZsVYCH6/60J/FHysWyE=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/google/shlex v0.0.0-20181106134648-c34317bd91bf h1:7+FW5aGwISbqUtkfmIpZJGRgNFg2ioYPvFaUxdqpDsg=
+github.com/google/shlex v0.0.0-20181106134648-c34317bd91bf/go.mod h1:RpwtwJQFrIEPstU94h88MWPXP2ektJZ8cZ0YntAmXiE=
github.com/marcinbor85/gohex v0.0.0-20180128172054-7a43cd876e46 h1:wXG2bA8fO7Vv7lLk2PihFMTqmbT173Tje39oKzQ50Mo=
github.com/marcinbor85/gohex v0.0.0-20180128172054-7a43cd876e46/go.mod h1:Pb6XcsXyropB9LNHhnqaknG/vEwYztLkQzVCHv8sQ3M=
go.bug.st/serial.v1 v0.0.0-20180827123349-5f7892a7bb45 h1:mACY1anK6HNCZtm/DK2Rf2ZPHggVqeB0+7rY9Gl6wyI=