diffgenome 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- diffgenome/__init__.py +7 -0
- diffgenome/__main__.py +240 -0
- diffgenome/_collectors/go/dg/dg.go +623 -0
- diffgenome/_collectors/go/go.mod +3 -0
- diffgenome/_collectors/go/instrument/facts.go +346 -0
- diffgenome/_collectors/go/instrument/main.go +484 -0
- diffgenome/_collectors/node/instrument.js +289 -0
- diffgenome/_collectors/node/jest-setup.js +40 -0
- diffgenome/_collectors/node/package-lock.json +35 -0
- diffgenome/_collectors/node/package.json +11 -0
- diffgenome/_collectors/node/runtime.js +426 -0
- diffgenome/ambiguity.py +122 -0
- diffgenome/api.py +67 -0
- diffgenome/change.py +86 -0
- diffgenome/change_artifact.py +310 -0
- diffgenome/collect/__init__.py +2 -0
- diffgenome/collect/go_test.py +271 -0
- diffgenome/collect/node_jest.py +319 -0
- diffgenome/collect/py_monitoring.py +985 -0
- diffgenome/collect/py_runtime.py +116 -0
- diffgenome/collect/py_symbols.py +238 -0
- diffgenome/collect/pytest_plugin.py +130 -0
- diffgenome/compose.py +469 -0
- diffgenome/dependence.py +264 -0
- diffgenome/evaluate.py +669 -0
- diffgenome/frontends/__init__.py +0 -0
- diffgenome/frontends/python_ir.py +335 -0
- diffgenome/genome.py +1016 -0
- diffgenome/genome_pipeline.py +674 -0
- diffgenome/genome_prompt.py +33 -0
- diffgenome/genome_state.py +2118 -0
- diffgenome/graph.py +426 -0
- diffgenome/llm.py +189 -0
- diffgenome/model.py +364 -0
- diffgenome/mvp.py +398 -0
- diffgenome/probe.py +509 -0
- diffgenome/projection.py +308 -0
- diffgenome/py.typed +0 -0
- diffgenome/render.py +118 -0
- diffgenome/report.py +363 -0
- diffgenome/resolve.py +37 -0
- diffgenome/runtime.py +74 -0
- diffgenome/runtime_evidence.py +261 -0
- diffgenome/sandbox.py +166 -0
- diffgenome/serialize.py +96 -0
- diffgenome/sites.py +19 -0
- diffgenome/static_types.py +69 -0
- diffgenome/structure.py +462 -0
- diffgenome-0.1.0.dist-info/METADATA +139 -0
- diffgenome-0.1.0.dist-info/RECORD +53 -0
- diffgenome-0.1.0.dist-info/WHEEL +4 -0
- diffgenome-0.1.0.dist-info/entry_points.txt +2 -0
- diffgenome-0.1.0.dist-info/licenses/LICENSE +202 -0
|
@@ -0,0 +1,484 @@
|
|
|
1
|
+
// diffgenome Go source instrumenter.
|
|
2
|
+
//
|
|
3
|
+
// go run ./instrument -root <repo copy> -module <module path> -src <dir>[,dir] -tests <dir>[,dir] -index <out.json>
|
|
4
|
+
//
|
|
5
|
+
// Rewrites every .go file under the roots IN PLACE (the copy is disposable): each function
|
|
6
|
+
// or method body reports entry (dg.Enter with its parameters), and a deferred function
|
|
7
|
+
// reports the results, a trailing error, or a panic (dg.Exit / dg.Panic). Unnamed results
|
|
8
|
+
// are named so the defer can read them. Test functions and t.Run subtests become stimuli
|
|
9
|
+
// (dg.Begin / dg.End). Files under a test root, *_test.go files and generated mock packages
|
|
10
|
+
// are "test" origin; a MockGen file's "Source: <pkg> (interfaces: X)" header gives each
|
|
11
|
+
// mock method a deterministic claim on the in-repo definer of that interface method.
|
|
12
|
+
//
|
|
13
|
+
// Symbol naming: go:<package dir relative to root>.<Recv.Func>; closures are <anon>@line.
|
|
14
|
+
// It also writes the definition index the Python side uses for diff mapping and context.
|
|
15
|
+
package main
|
|
16
|
+
|
|
17
|
+
import (
|
|
18
|
+
"bytes"
|
|
19
|
+
"encoding/json"
|
|
20
|
+
"flag"
|
|
21
|
+
"fmt"
|
|
22
|
+
"go/ast"
|
|
23
|
+
"go/format"
|
|
24
|
+
"go/parser"
|
|
25
|
+
"go/token"
|
|
26
|
+
"os"
|
|
27
|
+
"path/filepath"
|
|
28
|
+
"regexp"
|
|
29
|
+
"sort"
|
|
30
|
+
"strconv"
|
|
31
|
+
"strings"
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
type definition struct {
|
|
35
|
+
Symbol string `json:"symbol"`
|
|
36
|
+
Path string `json:"path"`
|
|
37
|
+
Start int `json:"start"`
|
|
38
|
+
End int `json:"end"`
|
|
39
|
+
Kind string `json:"kind"`
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
type root struct {
|
|
43
|
+
dir string
|
|
44
|
+
origin string
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
var mockSource = regexp.MustCompile(`Source: (\S+) \(interfaces: ([^)]+)\)`)
|
|
48
|
+
|
|
49
|
+
func main() {
|
|
50
|
+
rootFlag := flag.String("root", "", "repository copy")
|
|
51
|
+
module := flag.String("module", "", "module path of the repository")
|
|
52
|
+
srcFlag := flag.String("src", ".", "comma-separated source dirs (repo origin)")
|
|
53
|
+
testFlag := flag.String("tests", "", "comma-separated test dirs (test origin), e.g. db/mock")
|
|
54
|
+
indexFlag := flag.String("index", "", "definition index output")
|
|
55
|
+
factsFlag := flag.String("facts", "", "write the neutral IR of repo-origin functions to this file (from the original source)")
|
|
56
|
+
flag.Parse()
|
|
57
|
+
if *rootFlag == "" || *module == "" {
|
|
58
|
+
fmt.Fprintln(os.Stderr, "-root and -module are required")
|
|
59
|
+
os.Exit(2)
|
|
60
|
+
}
|
|
61
|
+
absRoot, _ := filepath.Abs(*rootFlag)
|
|
62
|
+
var roots []root
|
|
63
|
+
for _, d := range strings.Split(*testFlag, ",") {
|
|
64
|
+
if d != "" {
|
|
65
|
+
roots = append(roots, root{filepath.Join(absRoot, d), "test"})
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
for _, d := range strings.Split(*srcFlag, ",") {
|
|
69
|
+
if d != "" {
|
|
70
|
+
roots = append(roots, root{filepath.Join(absRoot, d), "repo"})
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
// method definers per package dir: pkgdir -> method name -> []type
|
|
74
|
+
definers := collectDefiners(absRoot)
|
|
75
|
+
var index []definition
|
|
76
|
+
seen := map[string]bool{}
|
|
77
|
+
count := 0
|
|
78
|
+
for _, r := range roots {
|
|
79
|
+
filepath.Walk(r.dir, func(path string, info os.FileInfo, err error) error {
|
|
80
|
+
if err != nil {
|
|
81
|
+
return nil
|
|
82
|
+
}
|
|
83
|
+
name := info.Name()
|
|
84
|
+
if info.IsDir() {
|
|
85
|
+
if strings.HasPrefix(name, ".") || name == "vendor" || name == "testdata" || name == "node_modules" || path == filepath.Join(absRoot, "internal", "diffgenome") {
|
|
86
|
+
return filepath.SkipDir
|
|
87
|
+
}
|
|
88
|
+
return nil
|
|
89
|
+
}
|
|
90
|
+
if !strings.HasSuffix(name, ".go") || seen[path] {
|
|
91
|
+
return nil
|
|
92
|
+
}
|
|
93
|
+
seen[path] = true
|
|
94
|
+
origin := r.origin
|
|
95
|
+
if strings.HasSuffix(name, "_test.go") {
|
|
96
|
+
origin = "test"
|
|
97
|
+
}
|
|
98
|
+
changed, defs, err := instrumentFile(absRoot, *module, path, origin, definers, *factsFlag != "")
|
|
99
|
+
if err != nil {
|
|
100
|
+
fmt.Fprintf(os.Stderr, "diffgenome: %s: %v\n", path, err)
|
|
101
|
+
return nil
|
|
102
|
+
}
|
|
103
|
+
index = append(index, defs...)
|
|
104
|
+
if changed {
|
|
105
|
+
count++
|
|
106
|
+
}
|
|
107
|
+
return nil
|
|
108
|
+
})
|
|
109
|
+
}
|
|
110
|
+
if *factsFlag != "" {
|
|
111
|
+
data, _ := json.MarshalIndent(map[string]any{"functions": irFunctions}, "", " ")
|
|
112
|
+
_ = os.WriteFile(*factsFlag, append(data, '\n'), 0o644)
|
|
113
|
+
}
|
|
114
|
+
if *indexFlag != "" {
|
|
115
|
+
sort.Slice(index, func(i, j int) bool {
|
|
116
|
+
return index[i].Path < index[j].Path || (index[i].Path == index[j].Path && index[i].Start < index[j].Start)
|
|
117
|
+
})
|
|
118
|
+
data, _ := json.MarshalIndent(map[string]any{"root": absRoot, "definitions": index}, "", " ")
|
|
119
|
+
_ = os.WriteFile(*indexFlag, append(data, '\n'), 0o644)
|
|
120
|
+
}
|
|
121
|
+
fmt.Printf("instrumented %d files, indexed %d definitions\n", count, len(index))
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// collectDefiners maps, per package directory, each method name to the receiver types
|
|
125
|
+
// defining it, so a mocked interface method can be claimed on its sole in-repo definer.
|
|
126
|
+
func collectDefiners(absRoot string) map[string]map[string][]string {
|
|
127
|
+
out := map[string]map[string][]string{}
|
|
128
|
+
filepath.Walk(absRoot, func(path string, info os.FileInfo, err error) error {
|
|
129
|
+
if err != nil || info.IsDir() || !strings.HasSuffix(path, ".go") || strings.HasSuffix(path, "_test.go") {
|
|
130
|
+
if info != nil && info.IsDir() && (strings.HasPrefix(info.Name(), ".") || info.Name() == "vendor" || info.Name() == "node_modules") {
|
|
131
|
+
return filepath.SkipDir
|
|
132
|
+
}
|
|
133
|
+
return nil
|
|
134
|
+
}
|
|
135
|
+
fset := token.NewFileSet()
|
|
136
|
+
f, perr := parser.ParseFile(fset, path, nil, 0)
|
|
137
|
+
if perr != nil {
|
|
138
|
+
return nil
|
|
139
|
+
}
|
|
140
|
+
pkgDir, _ := filepath.Rel(absRoot, filepath.Dir(path))
|
|
141
|
+
pkgDir = filepath.ToSlash(pkgDir)
|
|
142
|
+
if _, ok := out[pkgDir]; !ok {
|
|
143
|
+
out[pkgDir] = map[string][]string{}
|
|
144
|
+
}
|
|
145
|
+
for _, d := range f.Decls {
|
|
146
|
+
fd, ok := d.(*ast.FuncDecl)
|
|
147
|
+
if !ok || fd.Recv == nil || len(fd.Recv.List) == 0 {
|
|
148
|
+
continue
|
|
149
|
+
}
|
|
150
|
+
rt := receiverType(fd.Recv.List[0].Type)
|
|
151
|
+
if rt == "" || strings.HasPrefix(rt, "Mock") {
|
|
152
|
+
continue
|
|
153
|
+
}
|
|
154
|
+
out[pkgDir][fd.Name.Name] = appendUnique(out[pkgDir][fd.Name.Name], rt)
|
|
155
|
+
}
|
|
156
|
+
return nil
|
|
157
|
+
})
|
|
158
|
+
return out
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
func appendUnique(xs []string, x string) []string {
|
|
162
|
+
for _, y := range xs {
|
|
163
|
+
if y == x {
|
|
164
|
+
return xs
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
return append(xs, x)
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
func receiverType(e ast.Expr) string {
|
|
171
|
+
switch t := e.(type) {
|
|
172
|
+
case *ast.StarExpr:
|
|
173
|
+
return receiverType(t.X)
|
|
174
|
+
case *ast.Ident:
|
|
175
|
+
return t.Name
|
|
176
|
+
case *ast.IndexExpr:
|
|
177
|
+
return receiverType(t.X)
|
|
178
|
+
case *ast.IndexListExpr:
|
|
179
|
+
return receiverType(t.X)
|
|
180
|
+
}
|
|
181
|
+
return ""
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
type fileCtx struct {
|
|
185
|
+
fset *token.FileSet
|
|
186
|
+
rel string
|
|
187
|
+
pkgDir string
|
|
188
|
+
origin string
|
|
189
|
+
module string
|
|
190
|
+
mockPkgDir string // package dir of the mocked interfaces, if this is a MockGen file
|
|
191
|
+
mockIfaces map[string]bool
|
|
192
|
+
definers map[string]map[string][]string
|
|
193
|
+
index *[]definition
|
|
194
|
+
changed bool
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
var irFunctions []irFn
|
|
198
|
+
|
|
199
|
+
func instrumentFile(absRoot, module, path, origin string, definers map[string]map[string][]string, facts bool) (bool, []definition, error) {
|
|
200
|
+
src, err := os.ReadFile(path)
|
|
201
|
+
if err != nil {
|
|
202
|
+
return false, nil, err
|
|
203
|
+
}
|
|
204
|
+
if bytes.Contains(src, []byte("internal/diffgenome/dg\"")) {
|
|
205
|
+
return false, nil, nil // already instrumented (re-runs after adding a probe file)
|
|
206
|
+
}
|
|
207
|
+
fset := token.NewFileSet()
|
|
208
|
+
f, err := parser.ParseFile(fset, path, src, parser.ParseComments)
|
|
209
|
+
if err != nil {
|
|
210
|
+
return false, nil, err
|
|
211
|
+
}
|
|
212
|
+
rel, _ := filepath.Rel(absRoot, path)
|
|
213
|
+
rel = filepath.ToSlash(rel)
|
|
214
|
+
pkgDir := filepath.ToSlash(filepath.Dir(rel))
|
|
215
|
+
if pkgDir == "." {
|
|
216
|
+
pkgDir = ""
|
|
217
|
+
}
|
|
218
|
+
fc := &fileCtx{fset: fset, rel: rel, pkgDir: pkgDir, origin: origin, module: module, definers: definers}
|
|
219
|
+
// MockGen header -> claims for mock methods
|
|
220
|
+
if bytes.Contains(src, []byte("Code generated by MockGen")) {
|
|
221
|
+
if m := mockSource.FindSubmatch(src); m != nil {
|
|
222
|
+
srcPkg := string(m[1])
|
|
223
|
+
fc.mockPkgDir = strings.TrimPrefix(strings.TrimPrefix(srcPkg, module), "/")
|
|
224
|
+
fc.mockIfaces = map[string]bool{}
|
|
225
|
+
for _, name := range strings.Split(string(m[2]), ",") {
|
|
226
|
+
fc.mockIfaces[strings.TrimSpace(name)] = true
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
origin = "test"
|
|
230
|
+
fc.origin = "test"
|
|
231
|
+
}
|
|
232
|
+
var defs []definition
|
|
233
|
+
fc.index = &defs
|
|
234
|
+
// facts and branch sites come from the ORIGINAL tree, before any rewriting
|
|
235
|
+
if origin == "repo" {
|
|
236
|
+
if facts {
|
|
237
|
+
irFunctions = append(irFunctions, fc.lowerFuncs(f)...)
|
|
238
|
+
}
|
|
239
|
+
fc.wrapBranches(f)
|
|
240
|
+
}
|
|
241
|
+
// type declarations for the index (enclosing "class" lookups)
|
|
242
|
+
for _, d := range f.Decls {
|
|
243
|
+
if gd, ok := d.(*ast.GenDecl); ok && gd.Tok == token.TYPE {
|
|
244
|
+
for _, s := range gd.Specs {
|
|
245
|
+
if ts, ok := s.(*ast.TypeSpec); ok {
|
|
246
|
+
defs = append(defs, definition{Symbol: fc.symbolFor(ts.Name.Name), Path: rel, Start: fset.Position(ts.Pos()).Line, End: fset.Position(ts.End()).Line, Kind: "class"})
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
for _, d := range f.Decls {
|
|
252
|
+
if fd, ok := d.(*ast.FuncDecl); ok && fd.Body != nil {
|
|
253
|
+
fc.instrumentFunc(fd)
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
// closures anywhere (including inside already-instrumented bodies)
|
|
257
|
+
ast.Inspect(f, func(n ast.Node) bool {
|
|
258
|
+
if fl, ok := n.(*ast.FuncLit); ok && fl.Body != nil {
|
|
259
|
+
fc.instrumentLit(fl)
|
|
260
|
+
}
|
|
261
|
+
return true
|
|
262
|
+
})
|
|
263
|
+
if !fc.changed {
|
|
264
|
+
return false, defs, nil
|
|
265
|
+
}
|
|
266
|
+
var buf bytes.Buffer
|
|
267
|
+
if err := format.Node(&buf, fset, f); err != nil {
|
|
268
|
+
return false, defs, err
|
|
269
|
+
}
|
|
270
|
+
out := addImport(buf.String(), module+"/internal/diffgenome/dg")
|
|
271
|
+
if err := os.WriteFile(path, []byte(out), 0o644); err != nil {
|
|
272
|
+
return false, defs, err
|
|
273
|
+
}
|
|
274
|
+
return true, defs, nil
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
func (fc *fileCtx) symbolFor(qual string) string {
|
|
278
|
+
if fc.pkgDir == "" {
|
|
279
|
+
return "go:" + qual
|
|
280
|
+
}
|
|
281
|
+
return "go:" + fc.pkgDir + "." + qual
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
var anonStack []string // qualname stack for closures
|
|
285
|
+
|
|
286
|
+
func (fc *fileCtx) instrumentFunc(fd *ast.FuncDecl) {
|
|
287
|
+
qual := fd.Name.Name
|
|
288
|
+
if fd.Recv != nil && len(fd.Recv.List) > 0 {
|
|
289
|
+
if rt := receiverType(fd.Recv.List[0].Type); rt != "" {
|
|
290
|
+
qual = rt + "." + fd.Name.Name
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
sym := fc.symbolFor(qual)
|
|
294
|
+
line := fc.fset.Position(fd.Pos()).Line
|
|
295
|
+
*fc.index = append(*fc.index, definition{Symbol: sym, Path: fc.rel, Start: line, End: fc.fset.Position(fd.End()).Line, Kind: "function"})
|
|
296
|
+
claim, relation := "", ""
|
|
297
|
+
if fc.mockIfaces != nil && fd.Recv != nil {
|
|
298
|
+
rt := receiverType(fd.Recv.List[0].Type)
|
|
299
|
+
iface := strings.TrimPrefix(rt, "Mock")
|
|
300
|
+
if fc.mockIfaces[iface] && !strings.HasSuffix(rt, "MockRecorder") {
|
|
301
|
+
if defs := fc.definers[fc.mockPkgDir][fd.Name.Name]; len(defs) == 1 {
|
|
302
|
+
claim = "go:" + fc.mockPkgDir + "." + defs[0] + "." + fd.Name.Name
|
|
303
|
+
relation = "mocks-interface:" + iface
|
|
304
|
+
} else if len(defs) > 1 {
|
|
305
|
+
claim = "go:" + fc.mockPkgDir + "." + iface + "." + fd.Name.Name
|
|
306
|
+
relation = "mocks-interface:" + iface + ";candidates=" + strings.Join(defs, "|")
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
recv := ""
|
|
311
|
+
if fd.Recv != nil && len(fd.Recv.List) > 0 && len(fd.Recv.List[0].Names) > 0 && fd.Recv.List[0].Names[0].Name != "_" {
|
|
312
|
+
recv = fd.Recv.List[0].Names[0].Name
|
|
313
|
+
}
|
|
314
|
+
fc.wrapBody(fd.Type, fd.Body, sym, line, claim, relation, isTestFunc(fd), recv)
|
|
315
|
+
fc.markLits(fd.Body, qual)
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
func isTestFunc(fd *ast.FuncDecl) bool {
|
|
319
|
+
if fd.Recv != nil || !strings.HasPrefix(fd.Name.Name, "Test") || fd.Type.Params == nil || len(fd.Type.Params.List) != 1 {
|
|
320
|
+
return false
|
|
321
|
+
}
|
|
322
|
+
return exprString(fd.Type.Params.List[0].Type) == "*testing.T"
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
func isSubtestLit(fl *ast.FuncLit) bool {
|
|
326
|
+
if fl.Type.Params == nil || len(fl.Type.Params.List) != 1 {
|
|
327
|
+
return false
|
|
328
|
+
}
|
|
329
|
+
return exprString(fl.Type.Params.List[0].Type) == "*testing.T"
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
func exprString(e ast.Expr) string {
|
|
333
|
+
var b bytes.Buffer
|
|
334
|
+
_ = format.Node(&b, token.NewFileSet(), e)
|
|
335
|
+
return b.String()
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
var litQual = map[*ast.FuncLit]string{}
|
|
339
|
+
|
|
340
|
+
func (fc *fileCtx) markLits(body *ast.BlockStmt, qual string) {
|
|
341
|
+
ast.Inspect(body, func(n ast.Node) bool {
|
|
342
|
+
if fl, ok := n.(*ast.FuncLit); ok {
|
|
343
|
+
if _, done := litQual[fl]; !done {
|
|
344
|
+
litQual[fl] = qual + ".<anon>@" + strconv.Itoa(fc.fset.Position(fl.Pos()).Line)
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
return true
|
|
348
|
+
})
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
var litDone = map[*ast.FuncLit]bool{}
|
|
352
|
+
|
|
353
|
+
func (fc *fileCtx) instrumentLit(fl *ast.FuncLit) {
|
|
354
|
+
if litDone[fl] {
|
|
355
|
+
return
|
|
356
|
+
}
|
|
357
|
+
litDone[fl] = true
|
|
358
|
+
line := fc.fset.Position(fl.Pos()).Line
|
|
359
|
+
qual, ok := litQual[fl]
|
|
360
|
+
if !ok {
|
|
361
|
+
qual = "<anon>@" + strconv.Itoa(line)
|
|
362
|
+
}
|
|
363
|
+
sym := fc.symbolFor(qual)
|
|
364
|
+
*fc.index = append(*fc.index, definition{Symbol: sym, Path: fc.rel, Start: line, End: fc.fset.Position(fl.End()).Line, Kind: "function"})
|
|
365
|
+
fc.wrapBody(fl.Type, fl.Body, sym, line, "", "", isSubtestLit(fl) && fc.origin == "test", "")
|
|
366
|
+
fc.markLits(fl.Body, qual)
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
// wrapBody prepends the entry call and the deferred exit to a function body.
|
|
370
|
+
func (fc *fileCtx) wrapBody(ft *ast.FuncType, body *ast.BlockStmt, sym string, line int, claim, relation string, stimulus bool, recv string) {
|
|
371
|
+
// name unnamed results so the defer can read them
|
|
372
|
+
var resultNames []ast.Expr
|
|
373
|
+
if ft.Results != nil {
|
|
374
|
+
idx := 0
|
|
375
|
+
for _, field := range ft.Results.List {
|
|
376
|
+
if len(field.Names) == 0 {
|
|
377
|
+
name := ast.NewIdent(fmt.Sprintf("__dgr%d", idx))
|
|
378
|
+
field.Names = []*ast.Ident{name}
|
|
379
|
+
resultNames = append(resultNames, ast.NewIdent(name.Name))
|
|
380
|
+
idx++
|
|
381
|
+
continue
|
|
382
|
+
}
|
|
383
|
+
for _, n := range field.Names {
|
|
384
|
+
if n.Name == "_" {
|
|
385
|
+
n.Name = fmt.Sprintf("__dgr%d", idx)
|
|
386
|
+
}
|
|
387
|
+
resultNames = append(resultNames, ast.NewIdent(n.Name))
|
|
388
|
+
idx++
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
// parameters
|
|
393
|
+
var paramNames []string
|
|
394
|
+
var paramValues []ast.Expr
|
|
395
|
+
if ft.Params != nil {
|
|
396
|
+
for _, field := range ft.Params.List {
|
|
397
|
+
for _, n := range field.Names {
|
|
398
|
+
if n.Name == "_" {
|
|
399
|
+
continue
|
|
400
|
+
}
|
|
401
|
+
paramNames = append(paramNames, n.Name)
|
|
402
|
+
paramValues = append(paramValues, ast.NewIdent(n.Name))
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
metaElts := []ast.Expr{
|
|
407
|
+
kv("Sym", strLit(sym)), kv("Origin", strLit(fc.origin)), kv("File", strLit(fc.rel)),
|
|
408
|
+
kv("Line", &ast.BasicLit{Kind: token.INT, Value: strconv.Itoa(line)}),
|
|
409
|
+
kv("Params", &ast.CompositeLit{Type: &ast.ArrayType{Elt: ast.NewIdent("string")}, Elts: strLits(paramNames)}),
|
|
410
|
+
}
|
|
411
|
+
if claim != "" {
|
|
412
|
+
metaElts = append(metaElts, kv("Claim", strLit(claim)), kv("Relation", strLit(relation)))
|
|
413
|
+
}
|
|
414
|
+
if recv != "" {
|
|
415
|
+
metaElts = append(metaElts, kv("Recv", ast.NewIdent(recv)))
|
|
416
|
+
}
|
|
417
|
+
meta := &ast.CompositeLit{Type: sel("dg", "Meta"), Elts: metaElts}
|
|
418
|
+
enterArgs := append([]ast.Expr{meta}, paramValues...)
|
|
419
|
+
enter := &ast.AssignStmt{
|
|
420
|
+
Lhs: []ast.Expr{ast.NewIdent("__dgc")}, Tok: token.DEFINE,
|
|
421
|
+
Rhs: []ast.Expr{&ast.CallExpr{Fun: sel("dg", "Enter"), Args: enterArgs}},
|
|
422
|
+
}
|
|
423
|
+
exitArgs := append([]ast.Expr{ast.NewIdent("__dgc")}, resultNames...)
|
|
424
|
+
deferBody := &ast.BlockStmt{List: []ast.Stmt{
|
|
425
|
+
&ast.IfStmt{
|
|
426
|
+
Init: &ast.AssignStmt{Lhs: []ast.Expr{ast.NewIdent("__dgp")}, Tok: token.DEFINE, Rhs: []ast.Expr{&ast.CallExpr{Fun: ast.NewIdent("recover")}}},
|
|
427
|
+
Cond: &ast.BinaryExpr{X: ast.NewIdent("__dgp"), Op: token.NEQ, Y: ast.NewIdent("nil")},
|
|
428
|
+
Body: &ast.BlockStmt{List: []ast.Stmt{
|
|
429
|
+
&ast.ExprStmt{X: &ast.CallExpr{Fun: sel("dg", "Panic"), Args: []ast.Expr{ast.NewIdent("__dgc"), ast.NewIdent("__dgp")}}},
|
|
430
|
+
&ast.ExprStmt{X: &ast.CallExpr{Fun: ast.NewIdent("panic"), Args: []ast.Expr{ast.NewIdent("__dgp")}}},
|
|
431
|
+
}},
|
|
432
|
+
Else: &ast.BlockStmt{List: []ast.Stmt{
|
|
433
|
+
&ast.ExprStmt{X: &ast.CallExpr{Fun: sel("dg", "Exit"), Args: exitArgs}},
|
|
434
|
+
}},
|
|
435
|
+
},
|
|
436
|
+
}}
|
|
437
|
+
deferStmt := &ast.DeferStmt{Call: &ast.CallExpr{Fun: &ast.FuncLit{Type: &ast.FuncType{Params: &ast.FieldList{}}, Body: deferBody}}}
|
|
438
|
+
litDone[deferStmt.Call.Fun.(*ast.FuncLit)] = true
|
|
439
|
+
stmts := []ast.Stmt{enter, deferStmt}
|
|
440
|
+
if stimulus {
|
|
441
|
+
// dg.Begin(t.Name()); defer func() { dg.End(t.Failed()) }()
|
|
442
|
+
tname := ft.Params.List[0].Names[0].Name
|
|
443
|
+
begin := &ast.ExprStmt{X: &ast.CallExpr{Fun: sel("dg", "Begin"), Args: []ast.Expr{&ast.CallExpr{Fun: sel(tname, "Name")}}}}
|
|
444
|
+
endLit := &ast.FuncLit{Type: &ast.FuncType{Params: &ast.FieldList{}}, Body: &ast.BlockStmt{List: []ast.Stmt{
|
|
445
|
+
&ast.ExprStmt{X: &ast.CallExpr{Fun: sel("dg", "End"), Args: []ast.Expr{&ast.CallExpr{Fun: sel(tname, "Failed")}}}},
|
|
446
|
+
}}}
|
|
447
|
+
litDone[endLit] = true
|
|
448
|
+
end := &ast.DeferStmt{Call: &ast.CallExpr{Fun: endLit}}
|
|
449
|
+
stmts = []ast.Stmt{begin, end, enter, deferStmt}
|
|
450
|
+
}
|
|
451
|
+
body.List = append(stmts, body.List...)
|
|
452
|
+
fc.changed = true
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
func kv(k string, v ast.Expr) ast.Expr { return &ast.KeyValueExpr{Key: ast.NewIdent(k), Value: v} }
|
|
456
|
+
func strLit(s string) ast.Expr { return &ast.BasicLit{Kind: token.STRING, Value: strconv.Quote(s)} }
|
|
457
|
+
func sel(x, name string) ast.Expr {
|
|
458
|
+
return &ast.SelectorExpr{X: ast.NewIdent(x), Sel: ast.NewIdent(name)}
|
|
459
|
+
}
|
|
460
|
+
func strLits(xs []string) []ast.Expr {
|
|
461
|
+
out := make([]ast.Expr, 0, len(xs))
|
|
462
|
+
for _, x := range xs {
|
|
463
|
+
out = append(out, strLit(x))
|
|
464
|
+
}
|
|
465
|
+
return out
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
// addImport inserts the dg import after the package clause (gofmt-compatible enough).
|
|
469
|
+
func addImport(src, path string) string {
|
|
470
|
+
idx := strings.Index(src, "\npackage ")
|
|
471
|
+
if idx < 0 && strings.HasPrefix(src, "package ") {
|
|
472
|
+
idx = 0
|
|
473
|
+
} else if idx >= 0 {
|
|
474
|
+
idx++
|
|
475
|
+
} else {
|
|
476
|
+
return src
|
|
477
|
+
}
|
|
478
|
+
end := strings.Index(src[idx:], "\n")
|
|
479
|
+
if end < 0 {
|
|
480
|
+
return src
|
|
481
|
+
}
|
|
482
|
+
insert := "\n\nimport dg " + strconv.Quote(path) + "\n"
|
|
483
|
+
return src[:idx+end] + insert + src[idx+end:]
|
|
484
|
+
}
|