split typecheck and loadPkgCache from transformer

Neither of them has anything to do with transforming Go code;
they simply load or compute the information necessary for doing so.

Split typecheck into two functions as well.
The new typecheck function only does typechecking and nothing else.
A new comptueFieldToStruct func fills the fieldToStruct map,
which depends on typecheck, but is not needed when computing pkgCache.

This isolation also forces us to separate the code that fills pkgCache
from the code that fills the in-memory-only maps in transformer,
removing the need for the NOTE that we had left around as a reminder.
pull/756/head
Daniel Martí 1 year ago
parent 4b0b2acf6f
commit f85492a728

@ -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, // 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 // 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. // 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 return nil, err
} }
// NOTE: curPkgCache.EmbeddedAliasFields is already filled by typecheck above. if err := loadPkgCache(tf.pkg, files, tf.info); err != nil {
// 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 {
return nil, err 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 { if err := tf.prefillObjectMaps(files); err != nil {
return nil, err return nil, err
} }
@ -1318,7 +1317,7 @@ func openCache() (*cache.Cache, error) {
return cache.Open(dir) 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() fsCache, err := openCache()
if err != nil { if err != nil {
return err 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)) 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. // Fill the reflect info from SSA, which builds on top of the syntax tree and type info.
// Order is not significant. // Create SSA packages for all imports. Order is not significant.
ssaProg := ssa.NewProgram(fset, 0)
created := make(map[*types.Package]bool) created := make(map[*types.Package]bool)
var createAll func(pkgs []*types.Package) var createAll func(pkgs []*types.Package)
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() ssaPkg.Build()
inspector := reflectInspector{ inspector := reflectInspector{
pkg: tf.pkg, pkg: pkg,
checkedAPIs: make(map[string]bool), checkedAPIs: make(map[string]bool),
result: curPkgCache, // append the results result: curPkgCache, // append the results
} }
@ -1487,17 +1506,13 @@ type transformer struct {
// as well as the strings the user wants to inject them with. // as well as the strings the user wants to inject them with.
linkerVariableStrings map[*types.Var]string 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 // fieldToStruct helps locate struct types from any of their field
// objects. Useful when obfuscating field names. // objects. Useful when obfuscating field names.
fieldToStruct map[*types.Var]*types.Struct fieldToStruct map[*types.Var]*types.Struct
} }
func (tf *transformer) typecheck(files []*ast.File) error { func typecheck(files []*ast.File) (*types.Package, *types.Info, error) {
tf.info = &types.Info{ info := &types.Info{
Types: make(map[ast.Expr]types.TypeAndValue), Types: make(map[ast.Expr]types.TypeAndValue),
Defs: make(map[*ast.Ident]types.Object), Defs: make(map[*ast.Ident]types.Object),
Uses: 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), Selections: make(map[*ast.SelectorExpr]*types.Selection),
Instances: make(map[*ast.Ident]types.Instance), 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} 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 { 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. // Run recordType on all types reachable via types.Info.
// A bit hacky, but I could not find an easier way to do this. // 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 { if obj != nil {
tf.recordType(obj.Type(), nil) recordType(obj.Type(), nil, done, fieldToStruct)
} }
} }
for name, obj := range tf.info.Uses { for _, obj := range info.Defs {
if obj == nil { if obj != nil {
continue recordType(obj.Type(), nil, done, fieldToStruct)
}
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(),
} }
curPkgCache.EmbeddedAliasFields[vrStr] = aliasTypeName
} }
for _, tv := range tf.info.Types { for _, tv := range info.Types {
tf.recordType(tv.Type, nil) recordType(tv.Type, nil, done, fieldToStruct)
} }
return nil return fieldToStruct
} }
// recordType visits every reachable type after typechecking a package. // 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. // 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 { if origin == nil {
origin = used 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, // We can edit this code in the future if we find an example,
// because we panic if a field is not in fieldToStruct. // because we panic if a field is not in fieldToStruct.
if origin, ok := origin.(Container); ok { if origin, ok := origin.(Container); ok {
tf.recordType(used.Elem(), origin.Elem()) recordType(used.Elem(), origin.Elem(), done, fieldToStruct)
} }
case *types.Named: case *types.Named:
if tf.recordTypeDone[used] { if done[used] {
return return
} }
tf.recordTypeDone[used] = true done[used] = true
// If we have a generic struct like // If we have a generic struct like
// //
// type Foo[T any] struct { Bar T } // 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" // because otherwise different instances like "Bar int" and "Bar bool"
// will result in different hashes and the field names will break. // will result in different hashes and the field names will break.
// Ensure we record the original generic struct, if there is one. // 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: case *types.Struct:
origin := origin.(*types.Struct) origin := origin.(*types.Struct)
for i := 0; i < used.NumFields(); i++ { for i := 0; i < used.NumFields(); i++ {
field := used.Field(i) field := used.Field(i)
tf.fieldToStruct[field] = origin fieldToStruct[field] = origin
if field.Embedded() { 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. // packages result in different obfuscated names.
strct := tf.fieldToStruct[obj] strct := tf.fieldToStruct[obj]
if strct == nil { if strct == nil {
panic("could not find for " + name) panic("could not find struct for field " + name)
} }
node.Name = hashWithStruct(strct, name) node.Name = hashWithStruct(strct, name)
if flagDebug { // TODO(mvdan): remove once https://go.dev/issue/53465 if fixed if flagDebug { // TODO(mvdan): remove once https://go.dev/issue/53465 if fixed

@ -93,10 +93,11 @@ One can reverse a captured panic stack trace as follows:
} }
files = append(files, file) files = append(files, file)
} }
tf := &transformer{} _, info, err := typecheck(files)
if err := tf.typecheck(files); err != nil { if err != nil {
return err return err
} }
fieldToStruct := computeFieldToStruct(info)
for i, file := range files { for i, file := range files {
goFile := lpkg.CompiledGoFiles[i] goFile := lpkg.CompiledGoFiles[i]
ast.Inspect(file, func(node ast.Node) bool { 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) addHashedWithPackage(node.Name.Name)
case *ast.Field: case *ast.Field:
for _, name := range node.Names { for _, name := range node.Names {
obj, _ := tf.info.ObjectOf(name).(*types.Var) obj, _ := info.ObjectOf(name).(*types.Var)
if obj == nil || !obj.IsField() { if obj == nil || !obj.IsField() {
continue continue
} }
strct := tf.fieldToStruct[obj] strct := fieldToStruct[obj]
if strct == nil { 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) replaces = append(replaces, hashWithStruct(strct, name.Name), name.Name)
} }

Loading…
Cancel
Save