// labels checks correct label use in body. func (check *Checker) labels(body *ast.BlockStmt) { // set of all labels in this body all := NewScope(nil, body.Pos(), body.End(), "label") fwdJumps := check.blockBranches(all, nil, nil, body.List) // If there are any forward jumps left, no label was found for // the corresponding goto statements. Either those labels were // never defined, or they are inside blocks and not reachable // for the respective gotos. for _, jmp := range fwdJumps { var msg string name := jmp.Label.Name if alt := all.Lookup(name); alt != nil { msg = "goto %s jumps into block" alt.(*Label).used = true // avoid another error } else { msg = "label %s not declared" } check.errorf(jmp.Label.Pos(), msg, name) } // spec: "It is illegal to define a label that is never used." for _, obj := range all.elems { if lbl := obj.(*Label); !lbl.used { check.softErrorf(lbl.pos, "label %s declared but not used", lbl.name) } } }
// lastComment returns the last comment inside the provided block. func lastComment(b *ast.BlockStmt, c []*ast.CommentGroup) (i int, last *ast.CommentGroup) { pos, end := b.Pos(), b.End() for j, cg := range c { if cg.Pos() < pos { continue } if cg.End() > end { break } i, last = j, cg } return }
// New returns a new control-flow graph for the specified function body, // which must be non-nil. // // The CFG builder calls mayReturn to determine whether a given function // call may return. For example, calls to panic, os.Exit, and log.Fatal // do not return, so the builder can remove infeasible graph edges // following such calls. The builder calls mayReturn only for a // CallExpr beneath an ExprStmt. func New(body *ast.BlockStmt, mayReturn func(*ast.CallExpr) bool) *CFG { b := builder{ mayReturn: mayReturn, cfg: new(CFG), } b.current = b.newBlock("entry") b.stmt(body) // Does control fall off the end of the function's body? // Make implicit return explicit. if b.current != nil && !b.current.unreachable { b.add(&ast.ReturnStmt{ Return: body.End() - 1, }) } return b.cfg }
func (check *Checker) funcBody(decl *declInfo, name string, sig *Signature, body *ast.BlockStmt) { if trace { if name == "" { name = "<function literal>" } fmt.Printf("--- %s: %s {\n", name, sig) defer fmt.Println("--- <end>") } // set function scope extent sig.scope.pos = body.Pos() sig.scope.end = body.End() // save/restore current context and setup function context // (and use 0 indentation at function start) defer func(ctxt context, indent int) { check.context = ctxt check.indent = indent }(check.context, check.indent) check.context = context{ decl: decl, scope: sig.scope, sig: sig, } check.indent = 0 check.stmtList(0, body.List) if check.hasLabel { check.labels(body) } if sig.results.Len() > 0 && !check.isTerminating(body, "") { check.error(body.Rbrace, "missing return") } // spec: "Implementation restriction: A compiler may make it illegal to // declare a variable inside a function body if the variable is never used." // (One could check each scope after use, but that distributes this check // over several places because CloseScope is not always called explicitly.) check.usage(sig.scope) }
// playExample synthesizes a new *ast.File based on the provided // file with the provided function body as the body of main. func playExample(file *ast.File, body *ast.BlockStmt) *ast.File { if !strings.HasSuffix(file.Name.Name, "_test") { // We don't support examples that are part of the // greater package (yet). return nil } // Find top-level declarations in the file. topDecls := make(map[*ast.Object]bool) for _, decl := range file.Decls { switch d := decl.(type) { case *ast.FuncDecl: topDecls[d.Name.Obj] = true case *ast.GenDecl: for _, spec := range d.Specs { switch s := spec.(type) { case *ast.TypeSpec: topDecls[s.Name.Obj] = true case *ast.ValueSpec: for _, id := range s.Names { topDecls[id.Obj] = true } } } } } // Find unresolved identifiers and uses of top-level declarations. unresolved := make(map[string]bool) usesTopDecl := false var inspectFunc func(ast.Node) bool inspectFunc = func(n ast.Node) bool { // For selector expressions, only inspect the left hand side. // (For an expression like fmt.Println, only add "fmt" to the // set of unresolved names, not "Println".) if e, ok := n.(*ast.SelectorExpr); ok { ast.Inspect(e.X, inspectFunc) return false } // For key value expressions, only inspect the value // as the key should be resolved by the type of the // composite literal. if e, ok := n.(*ast.KeyValueExpr); ok { ast.Inspect(e.Value, inspectFunc) return false } if id, ok := n.(*ast.Ident); ok { if id.Obj == nil { unresolved[id.Name] = true } else if topDecls[id.Obj] { usesTopDecl = true } } return true } ast.Inspect(body, inspectFunc) if usesTopDecl { // We don't support examples that are not self-contained (yet). return nil } // Remove predeclared identifiers from unresolved list. for n := range unresolved { if predeclaredTypes[n] || predeclaredConstants[n] || predeclaredFuncs[n] { delete(unresolved, n) } } // Use unresolved identifiers to determine the imports used by this // example. The heuristic assumes package names match base import // paths for imports w/o renames (should be good enough most of the time). namedImports := make(map[string]string) // [name]path var blankImports []ast.Spec // _ imports for _, s := range file.Imports { p, err := strconv.Unquote(s.Path.Value) if err != nil { continue } n := path.Base(p) if s.Name != nil { n = s.Name.Name switch n { case "_": blankImports = append(blankImports, s) continue case ".": // We can't resolve dot imports (yet). return nil } } if unresolved[n] { namedImports[n] = p delete(unresolved, n) } } // If there are other unresolved identifiers, give up because this // synthesized file is not going to build. if len(unresolved) > 0 { return nil } // Include documentation belonging to blank imports. var comments []*ast.CommentGroup for _, s := range blankImports { if c := s.(*ast.ImportSpec).Doc; c != nil { comments = append(comments, c) } } // Include comments that are inside the function body. for _, c := range file.Comments { if body.Pos() <= c.Pos() && c.End() <= body.End() { comments = append(comments, c) } } // Strip "Output:" commment and adjust body end position. body, comments = stripOutputComment(body, comments) // Synthesize import declaration. importDecl := &ast.GenDecl{ Tok: token.IMPORT, Lparen: 1, // Need non-zero Lparen and Rparen so that printer Rparen: 1, // treats this as a factored import. } for n, p := range namedImports { s := &ast.ImportSpec{Path: &ast.BasicLit{Value: strconv.Quote(p)}} if path.Base(p) != n { s.Name = ast.NewIdent(n) } importDecl.Specs = append(importDecl.Specs, s) } importDecl.Specs = append(importDecl.Specs, blankImports...) // Synthesize main function. funcDecl := &ast.FuncDecl{ Name: ast.NewIdent("main"), Type: &ast.FuncType{}, Body: body, } // Synthesize file. return &ast.File{ Name: ast.NewIdent("main"), Decls: []ast.Decl{importDecl, funcDecl}, Comments: comments, } }
// playExample synthesizes a new *ast.File based on the provided // file with the provided function body as the body of main. func playExample(file *ast.File, body *ast.BlockStmt) *ast.File { if !strings.HasSuffix(file.Name.Name, "_test") { // We don't support examples that are part of the // greater package (yet). return nil } // Find unresolved identifiers unresolved := make(map[string]bool) ast.Inspect(body, func(n ast.Node) bool { // For an expression like fmt.Println, only add "fmt" to the // set of unresolved names. if e, ok := n.(*ast.SelectorExpr); ok { if id, ok := e.X.(*ast.Ident); ok && id.Obj == nil { unresolved[id.Name] = true } return false } if id, ok := n.(*ast.Ident); ok && id.Obj == nil { unresolved[id.Name] = true } return true }) // Remove predeclared identifiers from unresolved list. for n := range unresolved { if predeclaredTypes[n] || predeclaredConstants[n] || predeclaredFuncs[n] { delete(unresolved, n) } } // Use unresolved identifiers to determine the imports used by this // example. The heuristic assumes package names match base import // paths. (Should be good enough most of the time.) imports := make(map[string]string) // [name]path for _, s := range file.Imports { p, err := strconv.Unquote(s.Path.Value) if err != nil { continue } n := path.Base(p) if s.Name != nil { if s.Name.Name == "." { // We can't resolve dot imports (yet). return nil } n = s.Name.Name } if unresolved[n] { imports[n] = p delete(unresolved, n) } } // If there are other unresolved identifiers, give up because this // synthesized file is not going to build. if len(unresolved) > 0 { return nil } // Filter out comments that are outside the function body. var comments []*ast.CommentGroup for _, c := range file.Comments { if c.Pos() < body.Pos() || c.Pos() >= body.End() { continue } comments = append(comments, c) } // Strip "Output:" commment and adjust body end position. body, comments = stripOutputComment(body, comments) // Synthesize import declaration. importDecl := &ast.GenDecl{ Tok: token.IMPORT, Lparen: 1, // Need non-zero Lparen and Rparen so that printer Rparen: 1, // treats this as a factored import. } for n, p := range imports { s := &ast.ImportSpec{Path: &ast.BasicLit{Value: strconv.Quote(p)}} if path.Base(p) != n { s.Name = ast.NewIdent(n) } importDecl.Specs = append(importDecl.Specs, s) } // Synthesize main function. funcDecl := &ast.FuncDecl{ Name: ast.NewIdent("main"), Type: &ast.FuncType{}, Body: body, } // Synthesize file. return &ast.File{ Name: ast.NewIdent("main"), Decls: []ast.Decl{importDecl, funcDecl}, Comments: comments, } }
// buildFunction takes a function Value, a list of parameters, and a body, // and generates code for the function. func (c *compiler) buildFunction(f *LLVMValue, context, params, results *types.Tuple, body *ast.BlockStmt) { if currblock := c.builder.GetInsertBlock(); !currblock.IsNil() { defer c.builder.SetInsertPointAtEnd(currblock) } llvm_fn := llvm.ConstExtractValue(f.LLVMValue(), []uint32{0}) entry := llvm.AddBasicBlock(llvm_fn, "entry") c.builder.SetInsertPointAtEnd(entry) // For closures, context is the captured context values. var paramoffset int if context != nil { paramoffset++ // Store the existing values. We're going to temporarily // replace the values with offsets into the context param. oldvalues := make([]*LLVMValue, context.Len()) for i := range oldvalues { v := context.At(i) oldvalues[i] = c.objectdata[v].Value } defer func() { for i := range oldvalues { v := context.At(i) c.objectdata[v].Value = oldvalues[i] } }() // The context parameter is a pointer to a struct // whose elements are pointers to captured values. arg0 := llvm_fn.Param(0) for i := range oldvalues { v := context.At(i) argptr := c.builder.CreateStructGEP(arg0, i, "") argptr = c.builder.CreateLoad(argptr, "") ptrtyp := oldvalues[i].pointer.Type() newvalue := c.NewValue(argptr, ptrtyp) c.objectdata[v].Value = newvalue.makePointee() } } // Bind receiver, arguments and return values to their // identifiers/objects. We'll store each parameter on the stack so // they're addressable. nparams := int(params.Len()) for i := 0; i < nparams; i++ { v := params.At(i) name := v.Name() if !isBlank(name) { value := llvm_fn.Param(i + paramoffset) c.newArgStackVar(i+1, f, v, value, name) } } funcstate := &function{LLVMValue: f, results: results} c.functions.push(funcstate) hasdefer := hasDefer(funcstate, body) // Allocate space on the stack for named results. for i := 0; i < results.Len(); i++ { v := results.At(i) name := v.Name() allocstack := !isBlank(name) if !allocstack && hasdefer { c.objectdata[v] = &ObjectData{} allocstack = true } if allocstack { typ := v.Type() llvmtyp := c.types.ToLLVM(typ) c.newStackVar(f, v, llvm.ConstNull(llvmtyp), name) } } // Create the function body. if hasdefer { c.makeDeferBlock(funcstate, body) } c.VisitBlockStmt(body, false) c.functions.pop() c.setDebugLine(body.End()) // If the last instruction in the function is not a terminator, then // we either have unreachable code or a missing optional return statement // (the latter case is allowable only for functions without results). // // Use GetInsertBlock rather than LastBasicBlock, since the // last basic block might actually be a "defer" block. last := c.builder.GetInsertBlock() if in := last.LastInstruction(); in.IsNil() || in.IsATerminatorInst().IsNil() { c.builder.SetInsertPointAtEnd(last) if results.Len() == 0 { if funcstate.deferblock.IsNil() { c.builder.CreateRetVoid() } else { c.builder.CreateBr(funcstate.deferblock) } } else { c.builder.CreateUnreachable() } } }