"go/token"
"go/types"
"sort"
+ "strconv"
"strings"
"unicode"
"unicode/utf8"
data []byte
path string
buf []byte // for reading strings
+ version int // export format version
// object lists
strList []string // in order of appearance
// BImportData imports a package from the serialized package data
// and returns the number of bytes consumed and a reference to the package.
-// If data is obviously malformed, an error is returned but in
-// general it is not recommended to call BImportData on untrusted data.
-func BImportData(imports map[string]*types.Package, data []byte, path string) (int, *types.Package, error) {
+// If the export data version is not recognized or the format is otherwise
+// compromised, an error is returned.
+func BImportData(imports map[string]*types.Package, data []byte, path string) (_ int, _ *types.Package, err error) {
+ // catch panics and return them as errors
+ defer func() {
+ if e := recover(); e != nil {
+ // The package (filename) causing the problem is added to this
+ // error by a wrapper in the caller (Import in gcimporter.go).
+ err = fmt.Errorf("cannot import, possibly version skew (%v) - reinstall package", e)
+ }
+ }()
+
p := importer{
imports: imports,
data: data,
path: path,
+ version: -1, // unknown version
strList: []string{""}, // empty string is mapped to 0
}
// read version info
+ var versionstr string
if b := p.rawByte(); b == 'c' || b == 'd' {
// Go1.7 encoding; first byte encodes low-level
// encoding format (compact vs debug).
}
p.trackAllTypes = p.rawByte() == 'a'
p.posInfoFormat = p.int() != 0
- const go17version = "v1"
- if s := p.string(); s != go17version {
- return p.read, nil, fmt.Errorf("importer: unknown export data format: %s (imported package compiled with old compiler?)", s)
+ versionstr = p.string()
+ if versionstr == "v1" {
+ p.version = 0
}
} else {
// Go1.8 extensible encoding
- const exportVersion = "version 1"
- if s := p.rawStringln(b); s != exportVersion {
- return p.read, nil, fmt.Errorf("importer: unknown export data format: %s (imported package compiled with old compiler?)", s)
+ // read version string and extract version number (ignore anything after the version number)
+ versionstr = p.rawStringln(b)
+ if s := strings.SplitN(versionstr, " ", 3); len(s) >= 2 && s[0] == "version" {
+ if v, err := strconv.Atoi(s[1]); err == nil && v > 0 {
+ p.version = v
+ }
}
+ }
+
+ // read version specific flags - extend as necessary
+ switch p.version {
+ // case 2:
+ // ...
+ // fallthrough
+ case 1:
p.debugFormat = p.rawStringln(p.rawByte()) == "debug"
p.trackAllTypes = p.int() != 0
p.posInfoFormat = p.int() != 0
+ case 0:
+ // Go1.7 encoding format - nothing to do here
+ default:
+ errorf("unknown export format version %d (%q)", p.version, versionstr)
}
// --- generic export data ---
// self-verification
if count := p.int(); count != objcount {
- panic(fmt.Sprintf("got %d objects; want %d", objcount, count))
+ errorf("got %d objects; want %d", objcount, count)
}
// ignore compiler-specific import data
return p.read, pkg, nil
}
+func errorf(format string, args ...interface{}) {
+ panic(fmt.Sprintf(format, args...))
+}
+
func (p *importer) pkg() *types.Package {
// if the package was seen before, i is its index (>= 0)
i := p.tagOrIndex()
// otherwise, i is the package tag (< 0)
if i != packageTag {
- panic(fmt.Sprintf("unexpected package tag %d", i))
+ errorf("unexpected package tag %d", i)
}
// read package data
// we should never see an empty package name
if name == "" {
- panic("empty package name in import")
+ errorf("empty package name in import")
}
// an empty path denotes the package we are currently importing;
// it must be the first package we see
if (path == "") != (len(p.pkgList) == 0) {
- panic(fmt.Sprintf("package path %q for pkg index %d", path, len(p.pkgList)))
+ errorf("package path %q for pkg index %d", path, len(p.pkgList))
}
// if the package was imported before, use that one; otherwise create a new one
pkg = types.NewPackage(path, name)
p.imports[path] = pkg
} else if pkg.Name() != name {
- panic(fmt.Sprintf("conflicting names %s and %s for package %q", pkg.Name(), name, path))
+ errorf("conflicting names %s and %s for package %q", pkg.Name(), name, path)
}
p.pkgList = append(p.pkgList, pkg)
// imported.
// (See also the comment in cmd/compile/internal/gc/bimport.go importer.obj,
// switch case importing functions).
- panic(fmt.Sprintf("inconsistent import:\n\t%v\npreviously imported as:\n\t%v\n", alt, obj))
+ errorf("inconsistent import:\n\t%v\npreviously imported as:\n\t%v\n", alt, obj)
}
}
p.declare(types.NewFunc(token.NoPos, pkg, name, sig))
default:
- panic(fmt.Sprintf("unexpected object tag %d", tag))
+ errorf("unexpected object tag %d", tag)
}
}
}
if _, ok := obj.(*types.TypeName); !ok {
- panic(fmt.Sprintf("pkg = %s, name = %s => %s", parent, name, obj))
+ errorf("pkg = %s, name = %s => %s", parent, name, obj)
}
// associate new named type with obj if it doesn't exist yet
// no embedded interfaces with gc compiler
if p.int() != 0 {
- panic("unexpected embedded interface")
+ errorf("unexpected embedded interface")
}
t := types.NewInterface(p.methodList(parent), nil)
case 3 /* Cboth */ :
dir = types.SendRecv
default:
- panic(fmt.Sprintf("unexpected channel dir %d", d))
+ errorf("unexpected channel dir %d", d)
}
val := p.typ(parent)
*t = *types.NewChan(dir, val)
return t
default:
- panic(fmt.Sprintf("unexpected type tag %d", i))
+ errorf("unexpected type tag %d", i)
+ panic("unreachable")
}
}
case *types.Named:
name = typ.Obj().Name()
default:
- panic("anonymous field expected")
+ errorf("anonymous field expected")
}
anonymous = true
}
// use the imported package instead
pkg = p.pkgList[0]
}
+ if p.version == 0 && name == "_" {
+ // version 0 didn't export a package for _ fields
+ return pkg, name
+ }
if name != "" && !exported(name) {
if name == "?" {
name = ""
if named {
name = p.string()
if name == "" {
- panic("expected named parameter")
+ errorf("expected named parameter")
}
if name != "_" {
pkg = p.pkg()
case stringTag:
return constant.MakeString(p.string())
default:
- panic(fmt.Sprintf("unexpected value tag %d", tag))
+ errorf("unexpected value tag %d", tag)
+ panic("unreachable")
}
}
func (p *importer) int() int {
x := p.int64()
if int64(int(x)) != x {
- panic("exported integer too large")
+ errorf("exported integer too large")
}
return int(x)
}
func (p *importer) marker(want byte) {
if got := p.rawByte(); got != want {
- panic(fmt.Sprintf("incorrect marker: got %c; want %c (pos = %d)", got, want, p.read))
+ errorf("incorrect marker: got %c; want %c (pos = %d)", got, want, p.read)
}
pos := p.read
if n := int(p.rawInt64()); n != pos {
- panic(fmt.Sprintf("incorrect position: got %d; want %d", n, pos))
+ errorf("incorrect position: got %d; want %d", n, pos)
}
}
func (p *importer) rawInt64() int64 {
i, err := binary.ReadVarint(p)
if err != nil {
- panic(fmt.Sprintf("read error: %v", err))
+ errorf("read error: %v", err)
}
return i
}
case '|':
// nothing to do
default:
- panic("unexpected escape sequence in export data")
+ errorf("unexpected escape sequence in export data")
}
}
p.data = p.data[r:]
package gcimporter
import (
+ "bytes"
"fmt"
"internal/testenv"
"io/ioutil"
}
}
+func TestVersionHandling(t *testing.T) {
+ skipSpecialPlatforms(t) // we really only need to exclude nacl platforms, but this is fine
+
+ // This package only handles gc export data.
+ if runtime.Compiler != "gc" {
+ t.Skipf("gc-built packages not available (compiler = %s)", runtime.Compiler)
+ return
+ }
+
+ const dir = "./testdata/versions"
+ list, err := ioutil.ReadDir(dir)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ for _, f := range list {
+ name := f.Name()
+ if !strings.HasSuffix(name, ".a") {
+ continue // not a package file
+ }
+ if strings.Contains(name, "corrupted") {
+ continue // don't process a leftover corrupted file
+ }
+ pkgpath := "./" + name[:len(name)-2]
+
+ // test that export data can be imported
+ _, err := Import(make(map[string]*types.Package), pkgpath, dir)
+ if err != nil {
+ t.Errorf("import %q failed: %v", pkgpath, err)
+ continue
+ }
+
+ // create file with corrupted export data
+ // 1) read file
+ data, err := ioutil.ReadFile(filepath.Join(dir, name))
+ if err != nil {
+ t.Fatal(err)
+ }
+ // 2) find export data
+ i := bytes.Index(data, []byte("\n$$B\n")) + 5
+ j := bytes.Index(data[i:], []byte("\n$$\n")) + i
+ if i < 0 || j < 0 || i > j {
+ t.Fatalf("export data section not found (i = %d, j = %d)", i, j)
+ }
+ // 3) corrupt the data (increment every 7th byte)
+ for k := j - 13; k >= i; k -= 7 {
+ data[k]++
+ }
+ // 4) write the file
+ pkgpath += "_corrupted"
+ filename := filepath.Join(dir, pkgpath) + ".a"
+ ioutil.WriteFile(filename, data, 0666)
+ defer os.Remove(filename)
+
+ // test that importing the corrupted file results in an error
+ _, err = Import(make(map[string]*types.Package), pkgpath, dir)
+ if err == nil {
+ t.Errorf("import corrupted %q succeeded", pkgpath)
+ } else if msg := err.Error(); !strings.Contains(msg, "version skew") {
+ t.Errorf("import %q error incorrect (%s)", pkgpath, msg)
+ }
+ }
+}
+
func TestImportStdLib(t *testing.T) {
skipSpecialPlatforms(t)