diff --git a/main.go b/main.go index a1ba761..fe6872b 100644 --- a/main.go +++ b/main.go @@ -912,17 +912,16 @@ func transformCompile(args []string) ([]string, error) { // We could potentially avoid this by saving the type info we need in the cache, // although in general that wouldn't help much, since it's rare for Go's cache // to miss on a package and for our cache to hit. - if err := tf.typecheck(files); err != nil { + if tf.pkg, tf.info, err = typecheck(files); err != nil { return nil, err } - // NOTE: curPkgCache.EmbeddedAliasFields is already filled by typecheck above. - // That's needed if loadPkgCache is a miss, as we need to save the map. - // If loadPkgCache is a hit, then it's still fine, as the map entries are the same. - if err := tf.loadPkgCache(files); err != nil { + if err := loadPkgCache(tf.pkg, files, tf.info); err != nil { return nil, err } + // These maps are not kept in pkgCache, since they are only needed to obfuscate curPkg. + tf.fieldToStruct = computeFieldToStruct(tf.info) if err := tf.prefillObjectMaps(files); err != nil { return nil, err } @@ -1318,7 +1317,7 @@ func openCache() (*cache.Cache, error) { return cache.Open(dir) } -func (tf *transformer) loadPkgCache(files []*ast.File) error { +func loadPkgCache(pkg *types.Package, files []*ast.File, info *types.Info) error { fsCache, err := openCache() if err != nil { return err @@ -1382,10 +1381,30 @@ func (tf *transformer) loadPkgCache(files []*ast.File) error { } log.Printf("%d cached output files loaded in %s", loaded, debugSince(startTime)) - ssaProg := ssa.NewProgram(fset, 0) + // Fill EmbeddedAliasFields from the type info. + for name, obj := range info.Uses { + obj, ok := obj.(*types.TypeName) + if !ok || !obj.IsAlias() { + continue + } + vr, _ := info.Defs[name].(*types.Var) + if vr == nil || !vr.Embedded() { + continue + } + vrStr := recordedObjectString(vr) + if vrStr == "" { + continue + } + aliasTypeName := typeName{ + PkgPath: obj.Pkg().Path(), + Name: obj.Name(), + } + curPkgCache.EmbeddedAliasFields[vrStr] = aliasTypeName + } - // Create SSA packages for all imports. - // Order is not significant. + // Fill the reflect info from SSA, which builds on top of the syntax tree and type info. + // Create SSA packages for all imports. Order is not significant. + ssaProg := ssa.NewProgram(fset, 0) created := make(map[*types.Package]bool) var createAll func(pkgs []*types.Package) createAll = func(pkgs []*types.Package) { @@ -1397,13 +1416,13 @@ func (tf *transformer) loadPkgCache(files []*ast.File) error { } } } - createAll(tf.pkg.Imports()) + createAll(pkg.Imports()) - ssaPkg := ssaProg.CreatePackage(tf.pkg, files, tf.info, false) + ssaPkg := ssaProg.CreatePackage(pkg, files, info, false) ssaPkg.Build() inspector := reflectInspector{ - pkg: tf.pkg, + pkg: pkg, checkedAPIs: make(map[string]bool), result: curPkgCache, // append the results } @@ -1487,17 +1506,13 @@ type transformer struct { // as well as the strings the user wants to inject them with. linkerVariableStrings map[*types.Var]string - // recordTypeDone helps avoid type cycles in recordType. - // We only need to track named types, as all cycles must use them. - recordTypeDone map[*types.Named]bool - // fieldToStruct helps locate struct types from any of their field // objects. Useful when obfuscating field names. fieldToStruct map[*types.Var]*types.Struct } -func (tf *transformer) typecheck(files []*ast.File) error { - tf.info = &types.Info{ +func typecheck(files []*ast.File) (*types.Package, *types.Info, error) { + info := &types.Info{ Types: make(map[ast.Expr]types.TypeAndValue), Defs: make(map[*ast.Ident]types.Object), Uses: make(map[*ast.Ident]types.Object), @@ -1506,57 +1521,41 @@ func (tf *transformer) typecheck(files []*ast.File) error { Selections: make(map[*ast.SelectorExpr]*types.Selection), Instances: make(map[*ast.Ident]types.Instance), } - tf.recordTypeDone = make(map[*types.Named]bool) - tf.fieldToStruct = make(map[*types.Var]*types.Struct) origTypesConfig := types.Config{Importer: origImporter} - pkg, err := origTypesConfig.Check(curPkg.ImportPath, fset, files, tf.info) + pkg, err := origTypesConfig.Check(curPkg.ImportPath, fset, files, info) if err != nil { - return fmt.Errorf("typecheck error: %v", err) + return nil, nil, fmt.Errorf("typecheck error: %v", err) } - tf.pkg = pkg + return pkg, info, err +} + +func computeFieldToStruct(info *types.Info) map[*types.Var]*types.Struct { + done := make(map[*types.Named]bool) + fieldToStruct := make(map[*types.Var]*types.Struct) // Run recordType on all types reachable via types.Info. // A bit hacky, but I could not find an easier way to do this. - for _, obj := range tf.info.Defs { + for _, obj := range info.Uses { if obj != nil { - tf.recordType(obj.Type(), nil) + recordType(obj.Type(), nil, done, fieldToStruct) } } - for name, obj := range tf.info.Uses { - if obj == nil { - continue - } - tf.recordType(obj.Type(), nil) - - // Record into EmbeddedAliasFields. - obj, ok := obj.(*types.TypeName) - if !ok || !obj.IsAlias() { - continue - } - vr, _ := tf.info.Defs[name].(*types.Var) - if vr == nil || !vr.Embedded() { - continue - } - vrStr := recordedObjectString(vr) - if vrStr == "" { - continue - } - aliasTypeName := typeName{ - PkgPath: obj.Pkg().Path(), - Name: obj.Name(), + for _, obj := range info.Defs { + if obj != nil { + recordType(obj.Type(), nil, done, fieldToStruct) } - curPkgCache.EmbeddedAliasFields[vrStr] = aliasTypeName } - for _, tv := range tf.info.Types { - tf.recordType(tv.Type, nil) + for _, tv := range info.Types { + recordType(tv.Type, nil, done, fieldToStruct) } - return nil + return fieldToStruct } // recordType visits every reachable type after typechecking a package. -// Right now, all it does is fill the fieldToStruct field. +// Right now, all it does is fill the fieldToStruct map. // Since types can be recursive, we need a map to avoid cycles. -func (tf *transformer) recordType(used, origin types.Type) { +// We only need to track named types as done, as all cycles must use them. +func recordType(used, origin types.Type, done map[*types.Named]bool, fieldToStruct map[*types.Var]*types.Struct) { if origin == nil { origin = used } @@ -1568,13 +1567,13 @@ func (tf *transformer) recordType(used, origin types.Type) { // We can edit this code in the future if we find an example, // because we panic if a field is not in fieldToStruct. if origin, ok := origin.(Container); ok { - tf.recordType(used.Elem(), origin.Elem()) + recordType(used.Elem(), origin.Elem(), done, fieldToStruct) } case *types.Named: - if tf.recordTypeDone[used] { + if done[used] { return } - tf.recordTypeDone[used] = true + done[used] = true // If we have a generic struct like // // type Foo[T any] struct { Bar T } @@ -1583,15 +1582,15 @@ func (tf *transformer) recordType(used, origin types.Type) { // because otherwise different instances like "Bar int" and "Bar bool" // will result in different hashes and the field names will break. // Ensure we record the original generic struct, if there is one. - tf.recordType(used.Underlying(), used.Origin().Underlying()) + recordType(used.Underlying(), used.Origin().Underlying(), done, fieldToStruct) case *types.Struct: origin := origin.(*types.Struct) for i := 0; i < used.NumFields(); i++ { field := used.Field(i) - tf.fieldToStruct[field] = origin + fieldToStruct[field] = origin if field.Embedded() { - tf.recordType(field.Type(), origin.Field(i).Type()) + recordType(field.Type(), origin.Field(i).Type(), done, fieldToStruct) } } } @@ -1849,7 +1848,7 @@ func (tf *transformer) transformGoFile(file *ast.File) *ast.File { // packages result in different obfuscated names. strct := tf.fieldToStruct[obj] if strct == nil { - panic("could not find for " + name) + panic("could not find struct for field " + name) } node.Name = hashWithStruct(strct, name) if flagDebug { // TODO(mvdan): remove once https://go.dev/issue/53465 if fixed diff --git a/reverse.go b/reverse.go index c575de0..d2ef874 100644 --- a/reverse.go +++ b/reverse.go @@ -93,10 +93,11 @@ One can reverse a captured panic stack trace as follows: } files = append(files, file) } - tf := &transformer{} - if err := tf.typecheck(files); err != nil { + _, info, err := typecheck(files) + if err != nil { return err } + fieldToStruct := computeFieldToStruct(info) for i, file := range files { goFile := lpkg.CompiledGoFiles[i] ast.Inspect(file, func(node ast.Node) bool { @@ -110,13 +111,13 @@ One can reverse a captured panic stack trace as follows: addHashedWithPackage(node.Name.Name) case *ast.Field: for _, name := range node.Names { - obj, _ := tf.info.ObjectOf(name).(*types.Var) + obj, _ := info.ObjectOf(name).(*types.Var) if obj == nil || !obj.IsField() { continue } - strct := tf.fieldToStruct[obj] + strct := fieldToStruct[obj] if strct == nil { - panic("could not find for " + name.Name) + panic("could not find struct for field " + name.Name) } replaces = append(replaces, hashWithStruct(strct, name.Name), name.Name) }