forked from open-policy-agent/opa
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathtrace.go
447 lines (366 loc) · 11.6 KB
/
trace.go
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
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
// Copyright 2016 The OPA Authors. All rights reserved.
// Use of this source code is governed by an Apache2
// license that can be found in the LICENSE file.
package topdown
import (
"fmt"
"io"
"strings"
iStrs "github.com/open-policy-agent/opa/internal/strings"
"github.com/open-policy-agent/opa/ast"
"github.com/open-policy-agent/opa/topdown/builtins"
)
const (
minLocationWidth = 5 // len("query")
maxIdealLocationWidth = 64
locationPadding = 4
)
// Op defines the types of tracing events.
type Op string
const (
// EnterOp is emitted when a new query is about to be evaluated.
EnterOp Op = "Enter"
// ExitOp is emitted when a query has evaluated to true.
ExitOp Op = "Exit"
// EvalOp is emitted when an expression is about to be evaluated.
EvalOp Op = "Eval"
// RedoOp is emitted when an expression, rule, or query is being re-evaluated.
RedoOp Op = "Redo"
// SaveOp is emitted when an expression is saved instead of evaluated
// during partial evaluation.
SaveOp Op = "Save"
// FailOp is emitted when an expression evaluates to false.
FailOp Op = "Fail"
// DuplicateOp is emitted when a query has produced a duplicate value. The search
// will stop at the point where the duplicate was emitted and backtrack.
DuplicateOp Op = "Duplicate"
// NoteOp is emitted when an expression invokes a tracing built-in function.
NoteOp Op = "Note"
// IndexOp is emitted during an expression evaluation to represent lookup
// matches.
IndexOp Op = "Index"
// WasmOp is emitted when resolving a ref using an external
// Resolver.
WasmOp Op = "Wasm"
)
// VarMetadata provides some user facing information about
// a variable in some policy.
type VarMetadata struct {
Name ast.Var `json:"name"`
Location *ast.Location `json:"location"`
}
// Event contains state associated with a tracing event.
type Event struct {
Op Op // Identifies type of event.
Node ast.Node // Contains AST node relevant to the event.
Location *ast.Location // The location of the Node this event relates to.
QueryID uint64 // Identifies the query this event belongs to.
ParentID uint64 // Identifies the parent query this event belongs to.
Locals *ast.ValueMap // Contains local variable bindings from the query context. Nil if variables were not included in the trace event.
LocalMetadata map[ast.Var]VarMetadata // Contains metadata for the local variable bindings. Nil if variables were not included in the trace event.
Message string // Contains message for Note events.
Ref *ast.Ref // Identifies the subject ref for the event. Only applies to Index and Wasm operations.
input *ast.Term
bindings *bindings
}
// HasRule returns true if the Event contains an ast.Rule.
func (evt *Event) HasRule() bool {
_, ok := evt.Node.(*ast.Rule)
return ok
}
// HasBody returns true if the Event contains an ast.Body.
func (evt *Event) HasBody() bool {
_, ok := evt.Node.(ast.Body)
return ok
}
// HasExpr returns true if the Event contains an ast.Expr.
func (evt *Event) HasExpr() bool {
_, ok := evt.Node.(*ast.Expr)
return ok
}
// Equal returns true if this event is equal to the other event.
func (evt *Event) Equal(other *Event) bool {
if evt.Op != other.Op {
return false
}
if evt.QueryID != other.QueryID {
return false
}
if evt.ParentID != other.ParentID {
return false
}
if !evt.equalNodes(other) {
return false
}
return evt.Locals.Equal(other.Locals)
}
func (evt *Event) String() string {
return fmt.Sprintf("%v %v %v (qid=%v, pqid=%v)", evt.Op, evt.Node, evt.Locals, evt.QueryID, evt.ParentID)
}
// Input returns the input object as it was at the event.
func (evt *Event) Input() *ast.Term {
return evt.input
}
// Plug plugs event bindings into the provided ast.Term. Because bindings are mutable, this only makes sense to do when
// the event is emitted rather than on recorded trace events as the bindings are going to be different by then.
func (evt *Event) Plug(term *ast.Term) *ast.Term {
return evt.bindings.Plug(term)
}
func (evt *Event) equalNodes(other *Event) bool {
switch a := evt.Node.(type) {
case ast.Body:
if b, ok := other.Node.(ast.Body); ok {
return a.Equal(b)
}
case *ast.Rule:
if b, ok := other.Node.(*ast.Rule); ok {
return a.Equal(b)
}
case *ast.Expr:
if b, ok := other.Node.(*ast.Expr); ok {
return a.Equal(b)
}
case nil:
return other.Node == nil
}
return false
}
// Tracer defines the interface for tracing in the top-down evaluation engine.
// Deprecated: Use QueryTracer instead.
type Tracer interface {
Enabled() bool
Trace(*Event)
}
// QueryTracer defines the interface for tracing in the top-down evaluation engine.
// The implementation can provide additional configuration to modify the tracing
// behavior for query evaluations.
type QueryTracer interface {
Enabled() bool
TraceEvent(Event)
Config() TraceConfig
}
// TraceConfig defines some common configuration for Tracer implementations
type TraceConfig struct {
PlugLocalVars bool // Indicate whether to plug local variable bindings before calling into the tracer.
}
// legacyTracer Implements the QueryTracer interface by wrapping an older Tracer instance.
type legacyTracer struct {
t Tracer
}
func (l *legacyTracer) Enabled() bool {
return l.t.Enabled()
}
func (l *legacyTracer) Config() TraceConfig {
return TraceConfig{
PlugLocalVars: true, // For backwards compatibility old tracers will plug local variables
}
}
func (l *legacyTracer) TraceEvent(evt Event) {
l.t.Trace(&evt)
}
// WrapLegacyTracer will create a new QueryTracer which wraps an
// older Tracer instance.
func WrapLegacyTracer(tracer Tracer) QueryTracer {
return &legacyTracer{t: tracer}
}
// BufferTracer implements the Tracer and QueryTracer interface by
// simply buffering all events received.
type BufferTracer []*Event
// NewBufferTracer returns a new BufferTracer.
func NewBufferTracer() *BufferTracer {
return &BufferTracer{}
}
// Enabled always returns true if the BufferTracer is instantiated.
func (b *BufferTracer) Enabled() bool {
return b != nil
}
// Trace adds the event to the buffer.
// Deprecated: Use TraceEvent instead.
func (b *BufferTracer) Trace(evt *Event) {
*b = append(*b, evt)
}
// TraceEvent adds the event to the buffer.
func (b *BufferTracer) TraceEvent(evt Event) {
*b = append(*b, &evt)
}
// Config returns the Tracers standard configuration
func (b *BufferTracer) Config() TraceConfig {
return TraceConfig{PlugLocalVars: true}
}
// PrettyTrace pretty prints the trace to the writer.
func PrettyTrace(w io.Writer, trace []*Event) {
depths := depths{}
for _, event := range trace {
depth := depths.GetOrSet(event.QueryID, event.ParentID)
fmt.Fprintln(w, formatEvent(event, depth))
}
}
// PrettyTraceWithLocation prints the trace to the writer and includes location information
func PrettyTraceWithLocation(w io.Writer, trace []*Event) {
depths := depths{}
filePathAliases, longest := getShortenedFileNames(trace)
// Always include some padding between the trace and location
locationWidth := longest + locationPadding
for _, event := range trace {
depth := depths.GetOrSet(event.QueryID, event.ParentID)
location := formatLocation(event, filePathAliases)
fmt.Fprintf(w, "%-*s %s\n", locationWidth, location, formatEvent(event, depth))
}
}
func formatEvent(event *Event, depth int) string {
padding := formatEventPadding(event, depth)
if event.Op == NoteOp {
return fmt.Sprintf("%v%v %q", padding, event.Op, event.Message)
}
var details interface{}
if node, ok := event.Node.(*ast.Rule); ok {
details = node.Path()
} else if event.Ref != nil {
details = event.Ref
} else {
details = rewrite(event).Node
}
template := "%v%v %v"
opts := []interface{}{padding, event.Op, details}
if event.Message != "" {
template += " %v"
opts = append(opts, event.Message)
}
return fmt.Sprintf(template, opts...)
}
func formatEventPadding(event *Event, depth int) string {
spaces := formatEventSpaces(event, depth)
if spaces > 1 {
return strings.Repeat("| ", spaces-1)
}
return ""
}
func formatEventSpaces(event *Event, depth int) int {
switch event.Op {
case EnterOp:
return depth
case RedoOp:
if _, ok := event.Node.(*ast.Expr); !ok {
return depth
}
}
return depth + 1
}
// getShortenedFileNames will return a map of file paths to shortened aliases
// that were found in the trace. It also returns the longest location expected
func getShortenedFileNames(trace []*Event) (map[string]string, int) {
// Get a deduplicated list of all file paths
// and the longest file path size
fpAliases := map[string]string{}
var canShorten []string
longestLocation := 0
for _, event := range trace {
if event.Location != nil {
if event.Location.File != "" {
// length of "<name>:<row>"
curLen := len(event.Location.File) + numDigits10(event.Location.Row) + 1
if curLen > longestLocation {
longestLocation = curLen
}
if _, ok := fpAliases[event.Location.File]; ok {
continue
}
canShorten = append(canShorten, event.Location.File)
// Default to just alias their full path
fpAliases[event.Location.File] = event.Location.File
} else {
// length of "<min width>:<row>"
curLen := minLocationWidth + numDigits10(event.Location.Row) + 1
if curLen > longestLocation {
longestLocation = curLen
}
}
}
}
if len(canShorten) > 0 && longestLocation > maxIdealLocationWidth {
fpAliases, longestLocation = iStrs.TruncateFilePaths(maxIdealLocationWidth, longestLocation, canShorten...)
}
return fpAliases, longestLocation
}
func numDigits10(n int) int {
if n < 10 {
return 1
}
return numDigits10(n/10) + 1
}
func formatLocation(event *Event, fileAliases map[string]string) string {
location := event.Location
if location == nil {
return ""
}
if location.File == "" {
return fmt.Sprintf("query:%v", location.Row)
}
return fmt.Sprintf("%v:%v", fileAliases[location.File], location.Row)
}
// depths is a helper for computing the depth of an event. Events within the
// same query all have the same depth. The depth of query is
// depth(parent(query))+1.
type depths map[uint64]int
func (ds depths) GetOrSet(qid uint64, pqid uint64) int {
depth := ds[qid]
if depth == 0 {
depth = ds[pqid]
depth++
ds[qid] = depth
}
return depth
}
func builtinTrace(bctx BuiltinContext, args []*ast.Term, iter func(*ast.Term) error) error {
str, err := builtins.StringOperand(args[0].Value, 1)
if err != nil {
return handleBuiltinErr(ast.Trace.Name, bctx.Location, err)
}
if !bctx.TraceEnabled {
return iter(ast.BooleanTerm(true))
}
evt := Event{
Op: NoteOp,
Location: bctx.Location,
QueryID: bctx.QueryID,
ParentID: bctx.ParentID,
Message: string(str),
}
for i := range bctx.QueryTracers {
bctx.QueryTracers[i].TraceEvent(evt)
}
return iter(ast.BooleanTerm(true))
}
func rewrite(event *Event) *Event {
cpy := *event
var node ast.Node
switch v := event.Node.(type) {
case *ast.Expr:
expr := v.Copy()
// Hide generated local vars in 'key' position that have not been
// rewritten.
if ev, ok := v.Terms.(*ast.Every); ok {
if kv, ok := ev.Key.Value.(ast.Var); ok {
if rw, ok := cpy.LocalMetadata[kv]; !ok || rw.Name.IsGenerated() {
expr.Terms.(*ast.Every).Key = nil
}
}
}
node = expr
case ast.Body:
node = v.Copy()
case *ast.Rule:
node = v.Copy()
}
_, _ = ast.TransformVars(node, func(v ast.Var) (ast.Value, error) {
if meta, ok := cpy.LocalMetadata[v]; ok {
return meta.Name, nil
}
return v, nil
})
cpy.Node = node
return &cpy
}
func init() {
RegisterBuiltinFunc(ast.Trace.Name, builtinTrace)
}