diff options
author | Ayke van Laethem <[email protected]> | 2019-11-22 11:32:57 +0100 |
---|---|---|
committer | Ron Evans <[email protected]> | 2019-11-25 09:32:03 +0100 |
commit | 10e1420237164d16f64aae691dac12f692d80aed (patch) | |
tree | 946d28ec975d93f1ffd958a2c3b576570cb00c10 | |
parent | 6a1bb134f9dfddf915421d15c0bb6f8a5fcbfc25 (diff) | |
download | tinygo-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.go | 98 | ||||
-rw-r--r-- | cgo/cgo_test.go | 2 | ||||
-rw-r--r-- | cgo/libclang.go | 25 | ||||
-rw-r--r-- | cgo/security.go | 301 | ||||
-rw-r--r-- | cgo/security_test.go | 260 | ||||
-rw-r--r-- | cgo/testdata/flags.go | 26 | ||||
-rw-r--r-- | cgo/testdata/flags.out.go | 32 | ||||
-rw-r--r-- | go.mod | 1 | ||||
-rw-r--r-- | go.sum | 2 |
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 @@ -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 @@ -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= |