gvisor.dev/gvisor@v0.0.0-20240520182842-f9d4d51c7e0f/tools/nogo/facts/facts.go (about)

     1  // Copyright 2019 The gVisor Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  // Package facts implements alternate fact types.
    16  package facts
    17  
    18  import (
    19  	"encoding/gob"
    20  	"fmt"
    21  	"go/types"
    22  	"io"
    23  	"reflect"
    24  	"sort"
    25  
    26  	"archive/zip"
    27  	"golang.org/x/tools/go/analysis"
    28  	"golang.org/x/tools/go/types/objectpath"
    29  )
    30  
    31  // Serializer is used for fact serialization.
    32  //
    33  // It generalizes over the Package and Bundle types.
    34  type Serializer interface {
    35  	Serialize(w io.Writer) error
    36  }
    37  
    38  // item is used for serialiation.
    39  type item struct {
    40  	Key   string
    41  	Value any
    42  }
    43  
    44  // writeItems is an implementation of Serialize.
    45  //
    46  // This will sort the list as a side effect.
    47  func writeItems(w io.Writer, is []item) error {
    48  	sort.Slice(is, func(i, j int) bool {
    49  		return is[i].Key < is[j].Key
    50  	})
    51  	enc := gob.NewEncoder(w)
    52  	return enc.Encode(is)
    53  }
    54  
    55  // readItems is an implementation of io.ReaderTo.ReadTo.
    56  func readItems(r io.Reader) (is []item, err error) {
    57  	dec := gob.NewDecoder(r)
    58  	err = dec.Decode(&is)
    59  	return
    60  }
    61  
    62  // Package is a set of facts about a single package.
    63  //
    64  // These use the types.Object as the key because this is canonical. Normally,
    65  // this is canonical only in the context of a single types.Package. However,
    66  // because all imports are shared across all packages, there is a single
    67  // canonical types.Object shared among all packages being analyzed.
    68  type Package struct {
    69  	Objects map[types.Object][]analysis.Fact
    70  }
    71  
    72  // NewPackage returns a new set of Package facts.
    73  func NewPackage() *Package {
    74  	return &Package{
    75  		Objects: make(map[types.Object][]analysis.Fact),
    76  	}
    77  }
    78  
    79  func extractObjectpath(obj types.Object) (name objectpath.Path, err error) {
    80  	defer func() {
    81  		// Unfortunately, objectpath.For will occasionally panic for
    82  		// certain objects. This happens with basic analysis packages
    83  		// (buildssa), and therefore cannot be avoided.
    84  		if r := recover(); r != nil {
    85  			err = fmt.Errorf("panic: %v", r)
    86  		}
    87  	}()
    88  	// Allow empty name for no object.
    89  	if obj != nil {
    90  		name, err = objectpath.For(obj)
    91  	}
    92  	return
    93  }
    94  
    95  // Serialize implements Serializer.Serialize.
    96  func (p *Package) Serialize(w io.Writer) error {
    97  	is := make([]item, 0, len(p.Objects))
    98  	for obj, facts := range p.Objects {
    99  		name, err := extractObjectpath(obj)
   100  		if err != nil {
   101  			continue // Not exported; expected.
   102  		}
   103  		is = append(is, item{
   104  			Key:   string(name),
   105  			Value: facts,
   106  		})
   107  	}
   108  	return writeItems(w, is)
   109  }
   110  
   111  // ReadFrom deserializes a package.
   112  func (p *Package) ReadFrom(pkg *types.Package, r io.Reader) error {
   113  	is, err := readItems(r)
   114  	if err != nil {
   115  		return err
   116  	}
   117  	for _, fi := range is {
   118  		var (
   119  			obj types.Object
   120  			err error
   121  		)
   122  		if fi.Key != "" {
   123  			obj, err = objectpath.Object(pkg, objectpath.Path(fi.Key))
   124  		}
   125  		if err != nil {
   126  			// This could simply be a fact saved on an unexported
   127  			// object. We just suppress this error and ignore it.
   128  			continue
   129  		}
   130  		p.Objects[obj] = fi.Value.([]analysis.Fact)
   131  	}
   132  	return nil
   133  }
   134  
   135  // ExportFact exports an object fact.
   136  func (p *Package) ExportFact(obj types.Object, ptr analysis.Fact) {
   137  	for i, v := range p.Objects[obj] {
   138  		if reflect.TypeOf(v) == reflect.TypeOf(ptr) {
   139  			p.Objects[obj][i] = ptr // Replace.
   140  			return
   141  		}
   142  	}
   143  	// Append this new fact.
   144  	p.Objects[obj] = append(p.Objects[obj], ptr)
   145  }
   146  
   147  // ImportFact imports an object fact.
   148  func (p *Package) ImportFact(obj types.Object, ptr analysis.Fact) bool {
   149  	if p == nil {
   150  		return false // No facts.
   151  	}
   152  	for _, v := range p.Objects[obj] {
   153  		if reflect.TypeOf(v) == reflect.TypeOf(ptr) {
   154  			// Set the value to the element saved in our facts.
   155  			reflect.ValueOf(ptr).Elem().Set(reflect.ValueOf(v).Elem())
   156  			return true
   157  		}
   158  	}
   159  	return false
   160  }
   161  
   162  // Bundle is a set of facts about different packages.
   163  //
   164  // This is used to serialize a collection of facts about different packages,
   165  // which will be loaded and evaluated lazily.
   166  type Bundle struct {
   167  	reader  *zip.ReadCloser
   168  	decoded map[string]*Package
   169  }
   170  
   171  // NewBundle makes a new package bundle.
   172  func NewBundle() *Bundle {
   173  	return &Bundle{
   174  		decoded: make(map[string]*Package),
   175  	}
   176  }
   177  
   178  // Serialize implements Serializer.Serialize.
   179  func (b *Bundle) Serialize(w io.Writer) error {
   180  	zw := zip.NewWriter(w)
   181  	for pkg, facts := range b.decoded {
   182  		if facts == nil {
   183  			// Some facts may be omitted for bundles, if there is
   184  			// only type information but no source information. We
   185  			// omit these completely from the serialized bundle.
   186  			continue
   187  		}
   188  		if len(facts.Objects) == 0 {
   189  			// Similarly prevent serializing any Packages that have
   190  			// no facts associated with them. This will speed up
   191  			// deserialization since the Package can handle nil.
   192  			continue
   193  		}
   194  		wc, err := zw.Create(pkg)
   195  		if err != nil {
   196  			return err
   197  		}
   198  		if err := facts.Serialize(wc); err != nil {
   199  			return err
   200  		}
   201  	}
   202  	return zw.Close()
   203  }
   204  
   205  // BundleFrom may be used to create a new bundle that deserializes the contents
   206  // of the given file.
   207  //
   208  // Note that there is no explicit close mechanism, and the underlying file will
   209  // be closed only when the object is finalized.
   210  func BundleFrom(filename string) (*Bundle, error) {
   211  	r, err := zip.OpenReader(filename)
   212  	if err != nil {
   213  		return nil, err
   214  	}
   215  	return &Bundle{
   216  		reader:  r,
   217  		decoded: make(map[string]*Package),
   218  	}, nil
   219  }
   220  
   221  // Add adds the package to the Bundle.
   222  func (b *Bundle) Add(path string, facts *Package) {
   223  	b.decoded[path] = facts
   224  }
   225  
   226  // Package looks up the given package in the bundle.
   227  func (b *Bundle) Package(pkg *types.Package) (*Package, error) {
   228  	// Already decoded?
   229  	if facts, ok := b.decoded[pkg.Path()]; ok {
   230  		return facts, nil
   231  	}
   232  
   233  	if b.reader == nil {
   234  		// Nothing available.
   235  		//
   236  		// N.B. some bundles contain only cached packages.
   237  		return nil, nil
   238  	}
   239  
   240  	// Find based on the reader.
   241  	for _, f := range b.reader.File {
   242  		if f.Name != pkg.Path() {
   243  			continue
   244  		}
   245  
   246  		// Extract from the archive.
   247  		facts := NewPackage()
   248  		rc, err := f.Open()
   249  		if err != nil {
   250  			return nil, err
   251  		}
   252  		defer rc.Close()
   253  		if err := facts.ReadFrom(pkg, rc); err != nil {
   254  			return nil, err
   255  		}
   256  
   257  		// Memoize the result.
   258  		b.Add(pkg.Path(), facts)
   259  		return facts, nil
   260  	}
   261  
   262  	// Nothing available.
   263  	return nil, fmt.Errorf("no facts available for package %q", pkg.Path())
   264  }
   265  
   266  func init() {
   267  	gob.Register((*item)(nil))
   268  	gob.Register(([]analysis.Fact)(nil))
   269  }