github.com/amarpal/go-tools@v0.0.0-20240422043104-40142f59f616/cmd/structlayout/main.go (about) 1 // structlayout displays the layout (field sizes and padding) of structs. 2 package main 3 4 import ( 5 "encoding/json" 6 "flag" 7 "fmt" 8 "go/build" 9 "go/types" 10 "log" 11 "os" 12 13 "github.com/amarpal/go-tools/go/gcsizes" 14 "github.com/amarpal/go-tools/lintcmd/version" 15 st "github.com/amarpal/go-tools/structlayout" 16 17 "golang.org/x/tools/go/packages" 18 ) 19 20 var ( 21 fJSON bool 22 fVersion bool 23 ) 24 25 func init() { 26 flag.BoolVar(&fJSON, "json", false, "Format data as JSON") 27 flag.BoolVar(&fVersion, "version", false, "Print version and exit") 28 } 29 30 func main() { 31 log.SetFlags(0) 32 flag.Parse() 33 34 if fVersion { 35 version.Print(version.Version, version.MachineVersion) 36 os.Exit(0) 37 } 38 39 if len(flag.Args()) != 2 { 40 flag.Usage() 41 os.Exit(1) 42 } 43 44 cfg := &packages.Config{ 45 Mode: packages.NeedImports | packages.NeedExportFile | packages.NeedTypes | packages.NeedSyntax, 46 Tests: true, 47 } 48 pkgs, err := packages.Load(cfg, flag.Args()[0]) 49 if err != nil { 50 log.Fatal(err) 51 } 52 53 for _, pkg := range pkgs { 54 typName := flag.Args()[1] 55 56 var typ types.Type 57 obj := pkg.Types.Scope().Lookup(typName) 58 if obj == nil { 59 continue 60 } 61 typ = obj.Type() 62 63 st, ok := typ.Underlying().(*types.Struct) 64 if !ok { 65 log.Fatal("identifier is not a struct type") 66 } 67 68 fields := sizes(st, typ.(*types.Named).Obj().Name(), 0, nil) 69 if fJSON { 70 emitJSON(fields) 71 } else { 72 emitText(fields) 73 } 74 return 75 } 76 77 log.Fatal("couldn't find type") 78 } 79 80 func emitJSON(fields []st.Field) { 81 if fields == nil { 82 fields = []st.Field{} 83 } 84 json.NewEncoder(os.Stdout).Encode(fields) 85 } 86 87 func emitText(fields []st.Field) { 88 for _, field := range fields { 89 fmt.Println(field) 90 } 91 } 92 func sizes(typ *types.Struct, prefix string, base int64, out []st.Field) []st.Field { 93 s := gcsizes.ForArch(build.Default.GOARCH) 94 n := typ.NumFields() 95 var fields []*types.Var 96 for i := 0; i < n; i++ { 97 fields = append(fields, typ.Field(i)) 98 } 99 offsets := s.Offsetsof(fields) 100 for i := range offsets { 101 offsets[i] += base 102 } 103 104 pos := base 105 for i, field := range fields { 106 if offsets[i] > pos { 107 padding := offsets[i] - pos 108 out = append(out, st.Field{ 109 IsPadding: true, 110 Start: pos, 111 End: pos + padding, 112 Size: padding, 113 }) 114 pos += padding 115 } 116 size := s.Sizeof(field.Type()) 117 if typ2, ok := field.Type().Underlying().(*types.Struct); ok && typ2.NumFields() != 0 { 118 out = sizes(typ2, prefix+"."+field.Name(), pos, out) 119 } else { 120 out = append(out, st.Field{ 121 Name: prefix + "." + field.Name(), 122 Type: field.Type().String(), 123 Start: offsets[i], 124 End: offsets[i] + size, 125 Size: size, 126 Align: s.Alignof(field.Type()), 127 }) 128 } 129 pos += size 130 } 131 132 if len(out) == 0 { 133 return out 134 } 135 field := &out[len(out)-1] 136 if field.Size == 0 { 137 field.Size = 1 138 field.End++ 139 } 140 pad := s.Sizeof(typ) - field.End 141 if pad > 0 { 142 out = append(out, st.Field{ 143 IsPadding: true, 144 Start: field.End, 145 End: field.End + pad, 146 Size: pad, 147 }) 148 } 149 150 return out 151 }