GoPLS Viewer

Home|gopls/go/ssa/source_test.go
1// Copyright 2013 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5package ssa_test
6
7// This file defines tests of source-level debugging utilities.
8
9import (
10    "fmt"
11    "go/ast"
12    "go/constant"
13    "go/parser"
14    "go/token"
15    "go/types"
16    "io/ioutil"
17    "os"
18    "runtime"
19    "strings"
20    "testing"
21
22    "golang.org/x/tools/go/ast/astutil"
23    "golang.org/x/tools/go/expect"
24    "golang.org/x/tools/go/loader"
25    "golang.org/x/tools/go/ssa"
26    "golang.org/x/tools/go/ssa/ssautil"
27)
28
29func TestObjValueLookup(t *testing.T) {
30    if runtime.GOOS == "android" {
31        t.Skipf("no testdata directory on %s"runtime.GOOS)
32    }
33
34    conf := loader.Config{ParserModeparser.ParseComments}
35    srcerr := ioutil.ReadFile("testdata/objlookup.go")
36    if err != nil {
37        t.Fatal(err)
38    }
39    readFile := func(filename string) ([]byteerror) { return srcnil }
40    ferr := conf.ParseFile("testdata/objlookup.go"src)
41    if err != nil {
42        t.Fatal(err)
43    }
44    conf.CreateFromFiles("main"f)
45
46    // Maps each var Ident (represented "name:linenum") to the
47    // kind of ssa.Value we expect (represented "Constant", "&Alloc").
48    expectations := make(map[string]string)
49
50    // Each note of the form @ssa(x, "BinOp") in testdata/objlookup.go
51    // specifies an expectation that an object named x declared on the
52    // same line is associated with an an ssa.Value of type *ssa.BinOp.
53    noteserr := expect.ExtractGo(conf.Fsetf)
54    if err != nil {
55        t.Fatal(err)
56    }
57    for _n := range notes {
58        if n.Name != "ssa" {
59            t.Errorf("%v: unexpected note type %q, want \"ssa\""conf.Fset.Position(n.Pos), n.Name)
60            continue
61        }
62        if len(n.Args) != 2 {
63            t.Errorf("%v: ssa has %d args, want 2"conf.Fset.Position(n.Pos), len(n.Args))
64            continue
65        }
66        identok := n.Args[0].(expect.Identifier)
67        if !ok {
68            t.Errorf("%v: got %v for arg 1, want identifier"conf.Fset.Position(n.Pos), n.Args[0])
69            continue
70        }
71        expok := n.Args[1].(string)
72        if !ok {
73            t.Errorf("%v: got %v for arg 2, want string"conf.Fset.Position(n.Pos), n.Args[1])
74            continue
75        }
76        p_err := expect.MatchBefore(conf.FsetreadFilen.Posstring(ident))
77        if err != nil {
78            t.Error(err)
79            continue
80        }
81        pos := conf.Fset.Position(p)
82        key := fmt.Sprintf("%s:%d"identpos.Line)
83        expectations[key] = exp
84    }
85
86    iprogerr := conf.Load()
87    if err != nil {
88        t.Error(err)
89        return
90    }
91
92    prog := ssautil.CreateProgram(iprogssa.BuilderMode(0/*|ssa.PrintFunctions*/)
93    mainInfo := iprog.Created[0]
94    mainPkg := prog.Package(mainInfo.Pkg)
95    mainPkg.SetDebugMode(true)
96    mainPkg.Build()
97
98    var varIds []*ast.Ident
99    var varObjs []*types.Var
100    for idobj := range mainInfo.Defs {
101        // Check invariants for func and const objects.
102        switch obj := obj.(type) {
103        case *types.Func:
104            checkFuncValue(tprogobj)
105
106        case *types.Const:
107            checkConstValue(tprogobj)
108
109        case *types.Var:
110            if id.Name == "_" {
111                continue
112            }
113            varIds = append(varIdsid)
114            varObjs = append(varObjsobj)
115        }
116    }
117    for idobj := range mainInfo.Uses {
118        if objok := obj.(*types.Var); ok {
119            varIds = append(varIdsid)
120            varObjs = append(varObjsobj)
121        }
122    }
123
124    // Check invariants for var objects.
125    // The result varies based on the specific Ident.
126    for iid := range varIds {
127        obj := varObjs[i]
128        ref_ := astutil.PathEnclosingInterval(fid.Pos(), id.Pos())
129        pos := prog.Fset.Position(id.Pos())
130        exp := expectations[fmt.Sprintf("%s:%d"id.Namepos.Line)]
131        if exp == "" {
132            t.Errorf("%s: no expectation for var ident %s "posid.Name)
133            continue
134        }
135        wantAddr := false
136        if exp[0] == '&' {
137            wantAddr = true
138            exp = exp[1:]
139        }
140        checkVarValue(tprogmainPkgrefobjexpwantAddr)
141    }
142}
143
144func checkFuncValue(t *testing.Tprog *ssa.Programobj *types.Func) {
145    fn := prog.FuncValue(obj)
146    // fmt.Printf("FuncValue(%s) = %s\n", obj, fn) // debugging
147    if fn == nil {
148        if obj.Name() != "interfaceMethod" {
149            t.Errorf("FuncValue(%s) == nil"obj)
150        }
151        return
152    }
153    if fnobj := fn.Object(); fnobj != obj {
154        t.Errorf("FuncValue(%s).Object() == %s; value was %s",
155            objfnobjfn.Name())
156        return
157    }
158    if !types.Identical(fn.Type(), obj.Type()) {
159        t.Errorf("FuncValue(%s).Type() == %s"objfn.Type())
160        return
161    }
162}
163
164func checkConstValue(t *testing.Tprog *ssa.Programobj *types.Const) {
165    c := prog.ConstValue(obj)
166    // fmt.Printf("ConstValue(%s) = %s\n", obj, c) // debugging
167    if c == nil {
168        t.Errorf("ConstValue(%s) == nil"obj)
169        return
170    }
171    if !types.Identical(c.Type(), obj.Type()) {
172        t.Errorf("ConstValue(%s).Type() == %s"objc.Type())
173        return
174    }
175    if obj.Name() != "nil" {
176        if !constant.Compare(c.Valuetoken.EQLobj.Val()) {
177            t.Errorf("ConstValue(%s).Value (%s) != %s",
178                objc.Valueobj.Val())
179            return
180        }
181    }
182}
183
184func checkVarValue(t *testing.Tprog *ssa.Programpkg *ssa.Packageref []ast.Nodeobj *types.VarexpKind stringwantAddr bool) {
185    // The prefix of all assertions messages.
186    prefix := fmt.Sprintf("VarValue(%s @ L%d)",
187        objprog.Fset.Position(ref[0].Pos()).Line)
188
189    vgotAddr := prog.VarValue(objpkgref)
190
191    // Kind is the concrete type of the ssa Value.
192    gotKind := "nil"
193    if v != nil {
194        gotKind = fmt.Sprintf("%T"v)[len("*ssa."):]
195    }
196
197    // fmt.Printf("%s = %v (kind %q; expect %q) wantAddr=%t gotAddr=%t\n", prefix, v, gotKind, expKind, wantAddr, gotAddr) // debugging
198
199    // Check the kinds match.
200    // "nil" indicates expected failure (e.g. optimized away).
201    if expKind != gotKind {
202        t.Errorf("%s concrete type == %s, want %s"prefixgotKindexpKind)
203    }
204
205    // Check the types match.
206    // If wantAddr, the expected type is the object's address.
207    if v != nil {
208        expType := obj.Type()
209        if wantAddr {
210            expType = types.NewPointer(expType)
211            if !gotAddr {
212                t.Errorf("%s: got value, want address"prefix)
213            }
214        } else if gotAddr {
215            t.Errorf("%s: got address, want value"prefix)
216        }
217        if !types.Identical(v.Type(), expType) {
218            t.Errorf("%s.Type() == %s, want %s"prefixv.Type(), expType)
219        }
220    }
221}
222
223// Ensure that, in debug mode, we can determine the ssa.Value
224// corresponding to every ast.Expr.
225func TestValueForExpr(t *testing.T) {
226    testValueForExpr(t"testdata/valueforexpr.go")
227}
228
229func testValueForExpr(t *testing.Ttestfile string) {
230    if runtime.GOOS == "android" {
231        t.Skipf("no testdata dir on %s"runtime.GOOS)
232    }
233
234    conf := loader.Config{ParserModeparser.ParseComments}
235    ferr := conf.ParseFile(testfilenil)
236    if err != nil {
237        t.Error(err)
238        return
239    }
240    conf.CreateFromFiles("main"f)
241
242    iprogerr := conf.Load()
243    if err != nil {
244        t.Error(err)
245        return
246    }
247
248    mainInfo := iprog.Created[0]
249
250    prog := ssautil.CreateProgram(iprogssa.BuilderMode(0))
251    mainPkg := prog.Package(mainInfo.Pkg)
252    mainPkg.SetDebugMode(true)
253    mainPkg.Build()
254
255    if false {
256        // debugging
257        for _mem := range mainPkg.Members {
258            if fnok := mem.(*ssa.Function); ok {
259                fn.WriteTo(os.Stderr)
260            }
261        }
262    }
263
264    var parenExprs []*ast.ParenExpr
265    ast.Inspect(f, func(n ast.Nodebool {
266        if n != nil {
267            if eok := n.(*ast.ParenExpr); ok {
268                parenExprs = append(parenExprse)
269            }
270        }
271        return true
272    })
273
274    noteserr := expect.ExtractGo(prog.Fsetf)
275    if err != nil {
276        t.Fatal(err)
277    }
278    for _n := range notes {
279        want := n.Name
280        if want == "nil" {
281            want = "<nil>"
282        }
283        position := prog.Fset.Position(n.Pos)
284        var e ast.Expr
285        for _paren := range parenExprs {
286            if paren.Pos() > n.Pos {
287                e = paren.X
288                break
289            }
290        }
291        if e == nil {
292            t.Errorf("%s: note doesn't precede ParenExpr: %q"positionwant)
293            continue
294        }
295
296        path_ := astutil.PathEnclosingInterval(fn.Posn.Pos)
297        if path == nil {
298            t.Errorf("%s: can't find AST path from root to comment: %s"positionwant)
299            continue
300        }
301
302        fn := ssa.EnclosingFunction(mainPkgpath)
303        if fn == nil {
304            t.Errorf("%s: can't find enclosing function"position)
305            continue
306        }
307
308        vgotAddr := fn.ValueForExpr(e// (may be nil)
309        got := strings.TrimPrefix(fmt.Sprintf("%T"v), "*ssa.")
310        if got != want {
311            t.Errorf("%s: got value %q, want %q"positiongotwant)
312        }
313        if v != nil {
314            T := v.Type()
315            if gotAddr {
316                T = T.Underlying().(*types.Pointer).Elem() // deref
317            }
318            if !types.Identical(TmainInfo.TypeOf(e)) {
319                t.Errorf("%s: got type %s, want %s"positionmainInfo.TypeOf(e), T)
320            }
321        }
322    }
323}
324
325// findInterval parses input and returns the [start, end) positions of
326// the first occurrence of substr in input.  f==nil indicates failure;
327// an error has already been reported in that case.
328func findInterval(t *testing.Tfset *token.FileSetinputsubstr string) (f *ast.Filestartend token.Pos) {
329    ferr := parser.ParseFile(fset"<input>"input0)
330    if err != nil {
331        t.Errorf("parse error: %s"err)
332        return
333    }
334
335    i := strings.Index(inputsubstr)
336    if i < 0 {
337        t.Errorf("%q is not a substring of input"substr)
338        f = nil
339        return
340    }
341
342    filePos := fset.File(f.Package)
343    return ffilePos.Pos(i), filePos.Pos(i + len(substr))
344}
345
346func TestEnclosingFunction(t *testing.T) {
347    tests := []struct {
348        input  string // the input file
349        substr string // first occurrence of this string denotes interval
350        fn     string // name of expected containing function
351    }{
352        // We use distinctive numbers as syntactic landmarks.
353
354        // Ordinary function:
355        {`package main
356          func f() { println(1003) }`,
357            "100""main.f"},
358        // Methods:
359        {`package main
360                  type T int
361          func (t T) f() { println(200) }`,
362            "200""(main.T).f"},
363        // Function literal:
364        {`package main
365          func f() { println(func() { print(300) }) }`,
366            "300""main.f$1"},
367        // Doubly nested
368        {`package main
369          func f() { println(func() { print(func() { print(350) })})}`,
370            "350""main.f$1$1"},
371        // Implicit init for package-level var initializer.
372        {"package main; var a = 400""400""main.init"},
373        // No code for constants:
374        {"package main; const a = 500""500""(none)"},
375        // Explicit init()
376        {"package main; func init() { println(600) }""600""main.init#1"},
377        // Multiple explicit init functions:
378        {`package main
379          func init() { println("foo") }
380          func init() { println(800) }`,
381            "800""main.init#2"},
382        // init() containing FuncLit.
383        {`package main
384          func init() { println(func(){print(900)}) }`,
385            "900""main.init#1$1"},
386    }
387    for _test := range tests {
388        conf := loader.Config{Fsettoken.NewFileSet()}
389        fstartend := findInterval(tconf.Fsettest.inputtest.substr)
390        if f == nil {
391            continue
392        }
393        pathexact := astutil.PathEnclosingInterval(fstartend)
394        if !exact {
395            t.Errorf("EnclosingFunction(%q) not exact"test.substr)
396            continue
397        }
398
399        conf.CreateFromFiles("main"f)
400
401        iprogerr := conf.Load()
402        if err != nil {
403            t.Error(err)
404            continue
405        }
406        prog := ssautil.CreateProgram(iprogssa.BuilderMode(0))
407        pkg := prog.Package(iprog.Created[0].Pkg)
408        pkg.Build()
409
410        name := "(none)"
411        fn := ssa.EnclosingFunction(pkgpath)
412        if fn != nil {
413            name = fn.String()
414        }
415
416        if name != test.fn {
417            t.Errorf("EnclosingFunction(%q in %q) got %s, want %s",
418                test.substrtest.inputnametest.fn)
419            continue
420        }
421
422        // While we're here: test HasEnclosingFunction.
423        if has := ssa.HasEnclosingFunction(pkgpath); has != (fn != nil) {
424            t.Errorf("HasEnclosingFunction(%q in %q) got %v, want %v",
425                test.substrtest.inputhasfn != nil)
426            continue
427        }
428    }
429}
430
MembersX
testValueForExpr.RangeStmt_7121.BlockStmt.RangeStmt_7266.paren
TestEnclosingFunction.RangeStmt_10169.BlockStmt.end
TestObjValueLookup.varObjs
checkVarValue.pkg
checkVarValue.gotKind
testValueForExpr.RangeStmt_7121.BlockStmt.position
TestObjValueLookup
findInterval.t
testValueForExpr.conf
testValueForExpr.iprog
findInterval.input
TestObjValueLookup.conf
TestObjValueLookup.RangeStmt_1475.BlockStmt._
checkFuncValue.t
testValueForExpr.testfile
TestObjValueLookup.RangeStmt_1475.BlockStmt.p
TestObjValueLookup.varIds
astutil
TestObjValueLookup.RangeStmt_2620.obj
testValueForExpr.BlockStmt.RangeStmt_6724.mem
TestEnclosingFunction.RangeStmt_10169.BlockStmt.err
TestObjValueLookup.src
checkFuncValue
checkConstValue.t
testValueForExpr.f
testValueForExpr
testValueForExpr.RangeStmt_7121.BlockStmt.fn
findInterval.i
findInterval.filePos
TestObjValueLookup.t
TestObjValueLookup.RangeStmt_1475.BlockStmt.key
TestEnclosingFunction
TestEnclosingFunction.t
checkVarValue.BlockStmt.expType
TestEnclosingFunction.RangeStmt_10169.BlockStmt.pkg
runtime
TestObjValueLookup.err
checkFuncValue.fnobj
checkVarValue.prog
testValueForExpr.mainPkg
TestObjValueLookup.RangeStmt_1475.BlockStmt.pos
TestObjValueLookup.RangeStmt_3216.id
checkVarValue.ref
checkVarValue.v
ioutil
checkFuncValue.obj
checkVarValue.wantAddr
TestEnclosingFunction.RangeStmt_10169.BlockStmt.path
TestObjValueLookup.prog
testValueForExpr.parenExprs
findInterval.end
TestEnclosingFunction.tests
TestObjValueLookup.expectations
testValueForExpr.RangeStmt_7121.BlockStmt.want
TestEnclosingFunction.RangeStmt_10169.BlockStmt.fn
TestObjValueLookup.notes
checkConstValue.obj
findInterval.substr
findInterval.err
TestObjValueLookup.RangeStmt_3216.i
testValueForExpr.RangeStmt_7121.BlockStmt.BlockStmt.T
TestObjValueLookup.RangeStmt_3216.BlockStmt.wantAddr
TestEnclosingFunction.RangeStmt_10169.BlockStmt.exact
TestObjValueLookup.RangeStmt_3216.BlockStmt.pos
checkConstValue.c
testValueForExpr.RangeStmt_7121.BlockStmt.gotAddr
findInterval.fset
TestValueForExpr.t
TestObjValueLookup.RangeStmt_1475.BlockStmt.err
TestEnclosingFunction.RangeStmt_10169.BlockStmt.has
TestObjValueLookup.RangeStmt_3216.BlockStmt.ref
testValueForExpr.err
findInterval
TestObjValueLookup.mainPkg
TestObjValueLookup.RangeStmt_2977.id
checkVarValue.gotAddr
testValueForExpr.prog
TestObjValueLookup.RangeStmt_1475.n
TestObjValueLookup.RangeStmt_2977.obj
checkVarValue.expKind
testValueForExpr.RangeStmt_7121.BlockStmt._
TestObjValueLookup.f
TestObjValueLookup.RangeStmt_2620.id
testValueForExpr.notes
testValueForExpr.RangeStmt_7121.n
TestObjValueLookup.RangeStmt_3216.BlockStmt._
checkVarValue.obj
TestEnclosingFunction.RangeStmt_10169.BlockStmt.start
testValueForExpr.RangeStmt_7121.BlockStmt.path
findInterval.f
TestEnclosingFunction.RangeStmt_10169.BlockStmt.conf
testValueForExpr.RangeStmt_7121.BlockStmt.e
TestEnclosingFunction.RangeStmt_10169.test
checkFuncValue.prog
checkVarValue.prefix
TestValueForExpr
checkVarValue.t
testValueForExpr.t
TestEnclosingFunction.RangeStmt_10169.BlockStmt.prog
checkConstValue.prog
TestObjValueLookup.iprog
TestEnclosingFunction.RangeStmt_10169.BlockStmt.f
TestEnclosingFunction.RangeStmt_10169.BlockStmt.name
testValueForExpr.RangeStmt_7121.BlockStmt.got
findInterval.start
TestEnclosingFunction.RangeStmt_10169.BlockStmt.iprog
checkFuncValue.fn
checkConstValue
checkVarValue
testValueForExpr.RangeStmt_7121.BlockStmt.v
Members
X