mirror of
https://github.com/github/codeql.git
synced 2025-12-16 16:53:25 +01:00
241 lines
7.9 KiB
Go
241 lines
7.9 KiB
Go
package trap
|
|
|
|
import (
|
|
"fmt"
|
|
"go/types"
|
|
|
|
"github.com/github/codeql-go/extractor/srcarchive"
|
|
"github.com/github/codeql-go/extractor/util"
|
|
)
|
|
|
|
// Label represents a label
|
|
type Label struct {
|
|
id string
|
|
}
|
|
|
|
// InvalidLabel represents an uninitialized or otherwise invalid label
|
|
var InvalidLabel Label
|
|
|
|
func (lbl *Label) String() string {
|
|
return lbl.id
|
|
}
|
|
|
|
// Labeler is used to represent labels for a file. It is used to write
|
|
// associate objects with labels.
|
|
type Labeler struct {
|
|
tw *Writer
|
|
|
|
nextid int
|
|
fileLabel Label
|
|
nodeLabels map[interface{}]Label // labels associated with AST nodes
|
|
scopeLabels map[*types.Scope]Label // labels associated with scopes
|
|
objectLabels map[types.Object]Label // labels associated with objects (that is, declared entities)
|
|
TypeLabels map[types.Type]Label // labels associated with types
|
|
keyLabels map[string]Label
|
|
}
|
|
|
|
func newLabeler(tw *Writer) *Labeler {
|
|
return &Labeler{
|
|
tw,
|
|
10000,
|
|
InvalidLabel,
|
|
make(map[interface{}]Label),
|
|
make(map[*types.Scope]Label),
|
|
make(map[types.Object]Label),
|
|
make(map[types.Type]Label),
|
|
make(map[string]Label),
|
|
}
|
|
}
|
|
|
|
func (l *Labeler) nextID() string {
|
|
var id = l.nextid
|
|
l.nextid++
|
|
return fmt.Sprintf("#%d", id)
|
|
}
|
|
|
|
// GlobalID associates a label with the given `key` and returns it
|
|
func (l *Labeler) GlobalID(key string) Label {
|
|
label, exists := l.keyLabels[key]
|
|
if !exists {
|
|
id := l.nextID()
|
|
fmt.Fprintf(l.tw.wzip, "%s=@\"%s\"\n", id, escapeString(key))
|
|
label = Label{id}
|
|
l.keyLabels[key] = label
|
|
}
|
|
return label
|
|
}
|
|
|
|
// FileLabel returns the label for a file with path `path`.
|
|
func (l *Labeler) FileLabel() Label {
|
|
if l.fileLabel == InvalidLabel {
|
|
l.fileLabel = l.FileLabelFor(srcarchive.TransformPath(l.tw.path))
|
|
}
|
|
return l.fileLabel
|
|
}
|
|
|
|
// FileLabelFor returns the label for the file for which the trap writer `tw` is associated
|
|
func (l *Labeler) FileLabelFor(path string) Label {
|
|
return l.GlobalID(util.EscapeTrapSpecialChars(path) + ";sourcefile")
|
|
}
|
|
|
|
// LocalID associates a label with the given AST node `nd` and returns it
|
|
func (l *Labeler) LocalID(nd interface{}) Label {
|
|
label, exists := l.nodeLabels[nd]
|
|
if !exists {
|
|
label = l.FreshID()
|
|
l.nodeLabels[nd] = label
|
|
}
|
|
return label
|
|
}
|
|
|
|
// FreshID creates a fresh label and returns it
|
|
func (l *Labeler) FreshID() Label {
|
|
id := l.nextID()
|
|
fmt.Fprintf(l.tw.wzip, "%s=*\n", id)
|
|
return Label{id}
|
|
}
|
|
|
|
// ScopeID associates a label with the given scope and returns it
|
|
func (l *Labeler) ScopeID(scope *types.Scope, pkg *types.Package) Label {
|
|
label, exists := l.scopeLabels[scope]
|
|
if !exists {
|
|
if scope == types.Universe {
|
|
label = l.GlobalID("universe;scope")
|
|
} else {
|
|
if pkg != nil && pkg.Scope() == scope {
|
|
// if this scope is the package scope
|
|
pkgLabel := l.GlobalID(util.EscapeTrapSpecialChars(pkg.Path()) + ";package")
|
|
label = l.GlobalID("{" + pkgLabel.String() + "};scope")
|
|
} else {
|
|
label = l.FreshID()
|
|
}
|
|
}
|
|
l.scopeLabels[scope] = label
|
|
}
|
|
return label
|
|
}
|
|
|
|
// LookupObjectID looks up the label associated with the given object and returns it; if the object does not have
|
|
// a label yet, it tries to construct one based on its scope and/or name, and otherwise returns InvalidLabel
|
|
func (l *Labeler) LookupObjectID(object types.Object, typelbl Label) (Label, bool) {
|
|
label, exists := l.objectLabels[object]
|
|
if !exists {
|
|
if object.Parent() == nil {
|
|
// blank identifiers and the pseudo-package `.` (from `import . "..."` imports) can only be referenced
|
|
// once, so we can use a fresh label for them
|
|
if object.Name() == "_" || object.Name() == "." {
|
|
label = l.FreshID()
|
|
l.objectLabels[object] = label
|
|
return label, false
|
|
}
|
|
label = InvalidLabel
|
|
} else {
|
|
label, exists = l.ScopedObjectID(object, func() Label { return typelbl })
|
|
}
|
|
}
|
|
return label, exists
|
|
}
|
|
|
|
// ScopedObjectID associates a label with the given object and returns it,
|
|
// together with a flag indicating whether the object already had a label
|
|
// associated with it; the object must have a scope, since the scope's label is
|
|
// used to construct the label of the object.
|
|
//
|
|
// There is a special case for variables that are method receivers. When this is
|
|
// detected, we must construct a special label, as the variable can be reached
|
|
// from several files via the method. As the type label is required to construct
|
|
// the receiver object id, it is also required here.
|
|
func (l *Labeler) ScopedObjectID(object types.Object, getTypeLabel func() Label) (Label, bool) {
|
|
label, exists := l.objectLabels[object]
|
|
if !exists {
|
|
scope := object.Parent()
|
|
if scope == nil {
|
|
panic(fmt.Sprintf("Object has no scope: %v :: %v.\n", object,
|
|
l.tw.Package.Fset.Position(object.Pos())))
|
|
} else {
|
|
if meth := findMethodWithGivenReceiver(object); meth != nil {
|
|
// associate method receiver objects to special keys, because those can be
|
|
// referenced from other files via their method
|
|
methlbl, _ := l.MethodID(meth, getTypeLabel())
|
|
label, _ = l.ReceiverObjectID(object, methlbl)
|
|
} else {
|
|
scopeLbl := l.ScopeID(scope, object.Pkg())
|
|
label = l.GlobalID(fmt.Sprintf("{%v},%s;object", scopeLbl, object.Name()))
|
|
}
|
|
}
|
|
l.objectLabels[object] = label
|
|
}
|
|
return label, exists
|
|
}
|
|
|
|
// findMethodWithGivenReceiver finds a method with `object` as its receiver, if one exists
|
|
func findMethodWithGivenReceiver(object types.Object) *types.Func {
|
|
unaliasedType := types.Unalias(object.Type())
|
|
meth := findMethodOnTypeWithGivenReceiver(unaliasedType, object)
|
|
if meth != nil {
|
|
return meth
|
|
}
|
|
if pointerType, ok := unaliasedType.(*types.Pointer); ok {
|
|
meth = findMethodOnTypeWithGivenReceiver(pointerType.Elem(), object)
|
|
}
|
|
return meth
|
|
}
|
|
|
|
// findMethodWithGivenReceiver finds a method on type `tp` with `object` as its receiver, if one exists
|
|
func findMethodOnTypeWithGivenReceiver(tp types.Type, object types.Object) *types.Func {
|
|
if definedType, ok := tp.(*types.Named); ok {
|
|
for i := 0; i < definedType.NumMethods(); i++ {
|
|
meth := definedType.Method(i)
|
|
if object == meth.Type().(*types.Signature).Recv() {
|
|
return meth
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ReceiverObjectID associates a label with the given object and returns it, together with a flag indicating whether
|
|
// the object already had a label associated with it; the object must be the receiver of `methlbl`, since that label
|
|
// is used to construct the label of the object
|
|
func (l *Labeler) ReceiverObjectID(object types.Object, methlbl Label) (Label, bool) {
|
|
label, exists := l.objectLabels[object]
|
|
if !exists {
|
|
// if we can't, construct a special label
|
|
label = l.GlobalID(fmt.Sprintf("{%v},%s;receiver", methlbl, object.Name()))
|
|
l.objectLabels[object] = label
|
|
}
|
|
return label, exists
|
|
}
|
|
|
|
// FieldID associates a label with the given field and returns it, together with
|
|
// a flag indicating whether the field already had a label associated with it;
|
|
// the field must belong to `structlbl`, since that label is used to construct
|
|
// the label of the field. When the field name is the blank identifier `_`,
|
|
// `idx` is used to generate a unique name.
|
|
func (l *Labeler) FieldID(field *types.Var, idx int, structlbl Label) (Label, bool) {
|
|
label, exists := l.objectLabels[field]
|
|
if !exists {
|
|
name := field.Name()
|
|
// there can be multiple fields with the blank identifier, so use index to
|
|
// distinguish them
|
|
if field.Name() == "_" {
|
|
name = fmt.Sprintf("_%d", idx)
|
|
}
|
|
label = l.GlobalID(fmt.Sprintf("{%v},%s;field", structlbl, name))
|
|
l.objectLabels[field] = label
|
|
}
|
|
return label, exists
|
|
}
|
|
|
|
// MethodID associates a label with the given method and returns it, together with a flag indicating whether
|
|
// the method already had a label associated with it; the method must belong to `recvtyplbl`, since that label
|
|
// is used to construct the label of the method
|
|
func (l *Labeler) MethodID(method types.Object, recvtyplbl Label) (Label, bool) {
|
|
label, exists := l.objectLabels[method]
|
|
if !exists {
|
|
label = l.GlobalID(fmt.Sprintf("{%v},%s;method", recvtyplbl, method.Name()))
|
|
l.objectLabels[method] = label
|
|
}
|
|
return label, exists
|
|
}
|