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  }