-
Notifications
You must be signed in to change notification settings - Fork 2k
Expand file tree
/
Copy pathlabels.go
More file actions
241 lines (219 loc) · 7.9 KB
/
labels.go
File metadata and controls
241 lines (219 loc) · 7.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
package trap
import (
"fmt"
"go/types"
"path/filepath"
"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(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(filepath.ToSlash(srcarchive.TransformPath(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
}