Browse Source

cgo: add tests for errors

This commit adds tests for CGo preprocessing. There are various errors
that can be reported while preprocessing, and they should integrate well
with the compiler (including accurate source location tracking).

Also allow CGo preprocessing to continue after Clang encountered an
error, for a better view of what happened.
pull/754/head
Ayke van Laethem 5 years ago
committed by Ron Evans
parent
commit
6a1bb134f9
  1. 40
      cgo/cgo_test.go
  2. 1
      cgo/libclang.go
  3. 33
      cgo/testdata/errors.go
  4. 43
      cgo/testdata/errors.out.go

40
cgo/cgo_test.go

@ -11,6 +11,7 @@ import (
"go/types" "go/types"
"io/ioutil" "io/ioutil"
"path/filepath" "path/filepath"
"runtime"
"strings" "strings"
"testing" "testing"
) )
@ -21,7 +22,7 @@ var flagUpdate = flag.Bool("update", false, "Update images based on test output.
func TestCGo(t *testing.T) { func TestCGo(t *testing.T) {
var cflags = []string{"--target=armv6m-none-eabi"} var cflags = []string{"--target=armv6m-none-eabi"}
for _, name := range []string{"basic", "types"} { for _, name := range []string{"basic", "errors", "types"} {
name := name // avoid a race condition name := name // avoid a race condition
t.Run(name, func(t *testing.T) { t.Run(name, func(t *testing.T) {
t.Parallel() t.Parallel()
@ -35,23 +36,19 @@ func TestCGo(t *testing.T) {
} }
// Process the AST with CGo. // Process the AST with CGo.
cgoAST, errs := Process([]*ast.File{f}, "testdata", fset, cflags) cgoAST, cgoErrors := Process([]*ast.File{f}, "testdata", fset, cflags)
for _, err := range errs {
t.Errorf("error during CGo processing: %v", err)
}
// Check the AST for type errors. // Check the AST for type errors.
hasTypeError := false var typecheckErrors []error
config := types.Config{ config := types.Config{
Error: func(err error) { Error: func(err error) {
t.Error("typecheck error:", err) typecheckErrors = append(typecheckErrors, err)
hasTypeError = true
}, },
Importer: simpleImporter{}, Importer: simpleImporter{},
Sizes: types.SizesFor("gccgo", "arm"), Sizes: types.SizesFor("gccgo", "arm"),
} }
_, err = config.Check("", fset, []*ast.File{f, cgoAST}, nil) _, err = config.Check("", fset, []*ast.File{f, cgoAST}, nil)
if err != nil && !hasTypeError { if err != nil && len(typecheckErrors) == 0 {
// Only report errors when no type errors are found (an // Only report errors when no type errors are found (an
// unexpected condition). // unexpected condition).
t.Error(err) t.Error(err)
@ -61,6 +58,20 @@ func TestCGo(t *testing.T) {
// becomes easier to read (and will hopefully change less with CGo // becomes easier to read (and will hopefully change less with CGo
// changes). // changes).
buf := &bytes.Buffer{} buf := &bytes.Buffer{}
if len(cgoErrors) != 0 {
buf.WriteString("// CGo errors:\n")
for _, err := range cgoErrors {
buf.WriteString(formatDiagnostic(err))
}
buf.WriteString("\n")
}
if len(typecheckErrors) != 0 {
buf.WriteString("// Type checking errors after CGo processing:\n")
for _, err := range typecheckErrors {
buf.WriteString(formatDiagnostic(err))
}
buf.WriteString("\n")
}
err = format.Node(buf, fset, cgoAST) err = format.Node(buf, fset, cgoAST)
if err != nil { if err != nil {
t.Errorf("could not write out CGo AST: %v", err) t.Errorf("could not write out CGo AST: %v", err)
@ -107,3 +118,14 @@ func (i simpleImporter) Import(path string) (*types.Package, error) {
return nil, fmt.Errorf("importer not implemented for package %s", path) return nil, fmt.Errorf("importer not implemented for package %s", path)
} }
} }
// formatDiagnostics formats the error message to be an indented comment. It
// also fixes Windows path name issues (backward slashes).
func formatDiagnostic(err error) string {
msg := err.Error()
if runtime.GOOS == "windows" {
// Fix Windows path slashes.
msg = strings.Replace(msg, "testdata\\", "testdata/", -1)
}
return "// " + msg + "\n"
}

1
cgo/libclang.go

@ -132,7 +132,6 @@ func (p *cgoPackage) parseFragment(fragment string, cflags []string, posFilename
addDiagnostic(C.clang_getDiagnosticInSet(diagnostics, C.uint(j))) addDiagnostic(C.clang_getDiagnosticInSet(diagnostics, C.uint(j)))
} }
} }
return
} }
ref := storedRefs.Put(p) ref := storedRefs.Put(p)

33
cgo/testdata/errors.go

@ -0,0 +1,33 @@
package main
/*
#warning some warning
typedef struct {
int x;
int y;
} point_t;
typedef someType noType; // undefined type
#define SOME_CONST_1 5) // invalid const syntax
#define SOME_CONST_2 6) // const not used (so no error)
#define SOME_CONST_3 1234 // const too large for byte
*/
import "C"
// Make sure that errors for the following lines won't change with future
// additions to the CGo preamble.
//line errors.go:100
var (
// constant too large
_ C.uint8_t = 2 << 10
// z member does not exist
_ C.point_t = C.point_t{z: 3}
// constant has syntax error
_ = C.SOME_CONST_1
_ byte = C.SOME_CONST_3
)

43
cgo/testdata/errors.out.go

@ -0,0 +1,43 @@
// CGo errors:
// testdata/errors.go:4:2: warning: some warning
// testdata/errors.go:11:9: error: unknown type name 'someType'
// testdata/errors.go:13:23: unexpected token )
// Type checking errors after CGo processing:
// testdata/errors.go:102: 2 << 10 (untyped int constant 2048) overflows uint8
// testdata/errors.go:105: unknown field z in struct literal
// testdata/errors.go:108: undeclared name: C.SOME_CONST_1
// testdata/errors.go:110: C.SOME_CONST_3 (untyped int constant 1234) overflows byte
package main
import "unsafe"
var _ unsafe.Pointer
const C.SOME_CONST_3 = 1234
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
type C.point_t = struct {
x C.int
y C.int
}
Loading…
Cancel
Save