Example #1
0
func grindFunc(ctxt *grinder.Context, pkg *grinder.Package, edit *grinder.EditBuffer, fn *ast.FuncDecl) {
	if fn.Body == nil {
		return
	}
	blocks := block.Build(pkg.FileSet, fn.Body)
	ast.Inspect(fn.Body, func(x ast.Node) bool {
		var list []ast.Stmt
		switch x := x.(type) {
		default:
			return true
		case *ast.BlockStmt:
			list = x.List
		case *ast.CommClause:
			list = x.Body
		case *ast.CaseClause:
			list = x.Body
		}

		for i := 0; i < len(list); i++ {
			x := list[i]
			if grinder.IsTerminatingStmt(blocks, x) {
				end := i + 1
				for end < len(list) && !isGotoTarget(blocks, list[end]) {
					end++
				}
				if end > i+1 {
					edit.Delete(edit.End(x), edit.End(list[end-1]))
					i = end - 1 // after i++, next iteration starts at end
				}
			}
		}
		return true
	})
}
Example #2
0
func grindFunc(ctxt *grinder.Context, pkg *grinder.Package, edit *grinder.EditBuffer, fn *ast.FuncDecl) {
	if fn.Name.Name == "evconst" {
		old := debug
		debug = true
		defer func() { debug = old }()
	}

	if pkg.TypesError != nil {
		// Without scoping information, we can't be sure code moves are okay.
		fmt.Printf("%s: cannot inline gotos without type information\n", fn.Name)
		return
	}

	if fn.Body == nil {
		return
	}
	blocks := block.Build(pkg.FileSet, fn.Body)
	for labelname, gotos := range blocks.Goto {
		target, ok := findTargetBlock(pkg, edit, fn, blocks, labelname)
		if debug {
			println("TARGET", ok, labelname, len(gotos), target.dead, target.short)
		}
		if ok && (len(gotos) == 1 && target.dead || target.short) {
			numReplaced := 0
			for _, g := range gotos {
				code := edit.TextAt(target.comment, target.start) + target.code
				if !objsMatch(pkg, fn, g.Pos(), target.objs, target.start, target.end) {
					if debug {
						println("OBJS DO NOT MATCH")
					}
					// Cannot inline code here; needed identifiers have different meanings.
					continue
				}
				if target.needReturn {
					// NOTE: Should really check to see if function results are shadowed.
					// If we screw up, the code won't compile, so we can put it off.
					code += "; return"
				}
				if target.needGoto != "" {
					code += "; goto " + target.needGoto
				}
				edit.Replace(g.Pos(), g.End(), code)
				numReplaced++
			}
			if numReplaced == len(gotos) {
				if len(gotos) == 1 && target.dead {
					edit.Delete(target.comment, target.end)
				} else {
					edit.DeleteLine(target.start, target.endLabel)
				}
			}
			// The code we move might itself have gotos to inline,
			// and we can't make that change until we get new line
			// number position, so return after each label change.
			if numReplaced > 0 {
				return
			}
		}
	}
}
Example #3
0
func DeleteUnusedLabels(ctxt *grinder.Context, pkg *grinder.Package) {
	grinder.GrindFuncDecls(ctxt, pkg, func(ctxt *grinder.Context, pkg *grinder.Package, edit *grinder.EditBuffer, fn *ast.FuncDecl) {
		if fn.Body == nil {
			return
		}
		blocks := block.Build(pkg.FileSet, fn.Body)
		ast.Inspect(fn.Body, func(x ast.Node) bool {
			switch x := x.(type) {
			case *ast.LabeledStmt:
				if len(blocks.Goto[x.Label.Name])+len(blocks.Break[x.Label.Name])+len(blocks.Continue[x.Label.Name]) == 0 {
					edit.DeleteLine(x.Pos(), x.Colon+1)
				}
			case ast.Expr:
				return false
			}
			return true
		})
	})
}
Example #4
0
func analyzeFunc(pkg *grinder.Package, edit *grinder.EditBuffer, body *ast.BlockStmt) []*Var {
	const debug = false

	// Build list of candidate var declarations.
	inClosure := make(map[*ast.Object]bool)
	var objs []*ast.Object
	vardecl := make(map[*ast.Object]*ast.DeclStmt)
	ast.Inspect(body, func(x ast.Node) bool {
		switch x := x.(type) {
		case *ast.DeclStmt:
			decl := x.Decl.(*ast.GenDecl)
			if len(decl.Specs) != 1 || decl.Tok != token.VAR {
				break
			}
			spec := decl.Specs[0].(*ast.ValueSpec)
			if len(spec.Values) > 0 {
				break
			}
			for _, id := range spec.Names {
				if id.Obj != nil {
					objs = append(objs, id.Obj)
					vardecl[id.Obj] = x
				}
			}
		case *ast.FuncLit:
			ast.Inspect(x, func(x ast.Node) bool {
				switch x := x.(type) {
				case *ast.Ident:
					if x.Obj != nil {
						inClosure[x.Obj] = true
					}
				}
				return true
			})
			return false
		}
		return true
	})

	// Compute block information for entire AST.
	blocks := block.Build(pkg.FileSet, body)

	var vars []*Var
	// Handle each variable separately.
	for _, obj := range objs {
		// For now, refuse to touch variables shared with closures.
		// Could instead treat those variables as having their addresses
		// taken at the point where the closure appears in the source code.
		if inClosure[obj] {
			continue
		}
		// Build flow graph of nodes relevant to v.
		g := flow.Build(pkg.FileSet, body, func(x ast.Node) bool {
			return needForObj(pkg, obj, x)
		})

		// Find reaching definitions.
		m := newIdentMatcher(pkg, g, obj)
		g.Dataflow(m)

		// If an instance of v can refer to multiple definitions, merge them.
		t := newUnionFind()
		for _, x := range m.list {
			for _, def := range m.out[x].list {
				t.Add(def)
			}
		}
		for _, x := range m.list {
			defs := m.out[x].list
			if len(defs) > 1 {
				for _, def := range defs[1:] {
					t.Merge(defs[0], def)
				}
			}
		}

		// Build list of candidate definitions.
		var defs []*Def
		nodedef := make(map[ast.Node]*Def)
		for _, list := range t.Sets() {
			x := list[0].(ast.Node)
			d := &Def{}
			defs = append(defs, d)
			nodedef[x] = d
		}

		// Build map from uses to candidate definitions.
		idToDef := make(map[ast.Node]*Def)
		for _, x := range m.list {
			if _, ok := x.(*ast.Ident); ok {
				if debug {
					fmt.Printf("ID:IN %s\n", m.nodeIn(x))
					fmt.Printf("ID:OUT %s\n", m.nodeOut(x))
				}
				defs := m.out[x].list
				if len(defs) > 0 {
					idToDef[x] = nodedef[t.Find(defs[0]).(ast.Node)]
				}
			}
		}

		// Compute start/end of where defn is needed,
		// along with block where defn must be placed.
		for _, x := range m.list {
			// Skip declaration without initializer.
			// We can move the zero initialization forward.
			switch x := x.(type) {
			case *ast.DeclStmt:
				if x.Decl.(*ast.GenDecl).Specs[0].(*ast.ValueSpec).Values == nil {
					continue
				}
			}
			// Must use all entries in list.
			// Although most defs have been merged in previous passes,
			// the implicit zero definition of a var decl has not been.
			for _, def := range m.out[x].list {
				d := nodedef[t.Find(def).(ast.Node)]
				bx := blocks.Map[x]
				if debug {
					ddepth := -1
					if d.Block != nil {
						ddepth = d.Block.Depth
					}
					fmt.Printf("ID:X %s | d=%p %p b=%p bxdepth=%d ddepth=%d\n", m.nodeIn(x), d, d.Block, bx, bx.Depth, ddepth)
				}
				if d.Block == nil {
					d.Block = blocks.Map[x]
				} else {
					// Hoist into block containing both preliminary d.Block and x.
					for bx.Depth > d.Block.Depth {
						bx = bx.Parent
					}
					for d.Block.Depth > bx.Depth {
						d.Start = d.Block.Root.Pos()
						d.Block = d.Block.Parent
					}
					for d.Block != bx {
						d.Start = d.Block.Root.Pos()
						d.Block = d.Block.Parent
						bx = bx.Parent
					}
				}
				if pos := x.Pos(); d.Start == 0 || pos < d.Start {
					d.Start = pos
				}
				if end := x.End(); end > d.End {
					d.End = end
				}
				if debug {
					fmt.Printf("ID:X -> %s:%d,%d (%d,%d) ddepth=%d\n", pkg.FileSet.Position(d.Start).Filename, pkg.FileSet.Position(d.Start).Line, pkg.FileSet.Position(d.End).Line, d.Start, d.End, d.Block.Depth)
				}
			}
		}

		// Move tentative declaration sites up as required.
		for {
			changed := false

			for di, d := range defs {
				if d == nil {
					continue
				}
				orig := blocks.Map[vardecl[obj]].Depth
				if d.Block == nil {
					continue
				}
				// Cannot move declarations into loops.
				// Without liveness analysis we cannot be sure the variable is dead on entry.
				for b := d.Block; b.Depth > orig; b = b.Parent {
					switch b.Root.(type) {
					case *ast.ForStmt, *ast.RangeStmt:
						for d.Block != b {
							d.Start = d.Block.Root.Pos()
							d.End = d.Block.Root.End()
							d.Block = d.Block.Parent
							changed = true
						}
					}
				}

				// Gotos.
				for labelname, list := range blocks.Goto {
					label := blocks.Label[labelname]
					for _, g := range list {
						// Cannot declare between backward goto (possibly in nested block)
						// and target label in same or outer block; without liveness information,
						// we can't be sure the variable is dead at the label.
						if vardecl[obj].Pos() < label.Pos() && label.Pos() < d.Start && d.Start < g.Pos() {
							for label.Pos() < d.Block.Root.Pos() {
								d.Block = d.Block.Parent
							}
							d.Start = label.Pos()
							changed = true
						}

						// Cannot declare between forward goto (possibly in nested block)
						// and target label in same block; Go disallows jumping over declaration.
						if g.Pos() < d.Start && d.Start <= label.Pos() && blocks.Map[label] == d.Block {
							if false {
								fmt.Printf("%s:%d: goto %s blocks declaration of %s here\n", pkg.FileSet.Position(d.Start).Filename, pkg.FileSet.Position(d.Start).Line, labelname, obj.Name)
							}
							d.Start = g.Pos()
							changed = true
						}
					}
				}

				// If we've decided on an implicit if/for/switch block,
				// make sure we can actually put the declaration there.
				// If we can't initialize the variable with := in the initializer,
				// must move it up out of the loop.
				// Need to do this now, so that the move is visible to the
				// goto and "one definition per block" checks below.
				// TODO(rsc): This should be done for all variables simultaneously,
				// to allow
				//	for x, y := range z
				// instead of
				//	var x, y int
				//	var x, y = range z
				if !canDeclare(d.Block.Root, obj) {
					d.Start = d.Block.Root.Pos()
					d.Block = d.Block.Parent
					changed = true
				}

				// From a purely flow control point of view, in something like:
				//	var x int
				//	{
				//		x = 2
				//		y = x+x
				//		x = y
				//	}
				//	use(x)
				// The declaration 'x = 2' could be turned into a :=, since that value
				// is only used on the next line, except that the := will make the assignment
				// on the next line no longer refer to the outer x.
				// For each instance of x, if the proposed declaration shadows the
				// actual target of that x, merge the proposal into the outer declaration.
				for x, xDef := range idToDef {
					if xDef != d && xDef.Block.Depth < d.Block.Depth && d.Start <= x.Pos() && x.Pos() < d.Block.Root.End() {
						// xDef is an outer definition, so its start is already earlier than d's.
						// No need to update xDef.Start.
						// Not clear we care about xDef.End.
						// Update idToDef mappings to redirect d to xDef.
						for y, yDef := range idToDef {
							if yDef == d {
								idToDef[y] = xDef
							}
						}
						defs[di] = nil
						changed = true // because idToDef changed
					}
				}
			}

			// There can only be one definition (with a given name) per block.
			// Merge as needed.
			blockdef := make(map[*block.Block]*Def)
			for di, d := range defs {
				if d == nil {
					continue
				}
				dd := blockdef[d.Block]
				if dd == nil {
					blockdef[d.Block] = d
					continue
				}
				//fmt.Printf("merge defs %p %p\n", d, dd)
				if d.Start < dd.Start {
					dd.Start = d.Start
				}
				if d.End > dd.End {
					dd.End = d.End
				}
				for y, yDef := range idToDef {
					if yDef == d {
						idToDef[y] = dd
					}
				}
				defs[di] = nil
				changed = true
			}

			if changed {
				continue
			}

			// Find place to put declaration.
			// We established canDeclare(d.Block, obj) above.
			for _, d := range defs {
				if d == nil || d.Block == nil {
					continue
				}
				switch x := d.Block.Root.(type) {
				default:
					panic(fmt.Sprintf("unexpected declaration block root %T", d.Block.Root))

				case *ast.BlockStmt:
					d.Init = placeInit(edit, d.Start, obj, vardecl[obj], x.List)

				case *ast.CaseClause:
					d.Init = placeInit(edit, d.Start, obj, vardecl[obj], x.Body)

				case *ast.CommClause:
					d.Init = placeInit(edit, d.Start, obj, vardecl[obj], x.Body)

				case *ast.IfStmt:
					if x.Init == nil {
						panic("if without init")
					}
					d.Init = x.Init

				case *ast.ForStmt:
					if x.Init == nil {
						panic("for without init")
					}
					d.Init = x.Init

				case *ast.RangeStmt:
					d.Init = x

				case *ast.SwitchStmt:
					if x.Init == nil {
						panic("switch without init")
					}
					d.Init = x

				case *ast.TypeSwitchStmt:
					if x.Init == nil {
						panic("type switch without init")
					}
					d.Init = x
				}
				if d.Init != nil && d.Init.Pos() < d.Start {
					d.Start = d.Init.Pos()
					changed = true
				}
			}

			if !changed {
				break
			}
		}

		// Build report.
		v := &Var{Obj: obj, Decl: vardecl[obj]}
		for _, d := range defs {
			if d == nil || d.Block == nil {
				continue
			}
			if debug {
				fset := pkg.FileSet
				fmt.Printf("\tdepth %d: %s:%d,%d (%d,%d)\n", d.Block.Depth, fset.Position(d.Start).Filename, fset.Position(d.Start).Line, fset.Position(d.End).Line, d.Start, d.End)
				for _, x := range m.list {
					if len(m.out[x].list) > 0 {
						if d.Block == nodedef[t.Find(m.out[x].list[0]).(ast.Node)].Block {
							fmt.Printf("\t%s:%d %T (%d)\n", fset.Position(x.Pos()).Filename, fset.Position(x.Pos()).Line, x, x.Pos())
						}
					}
				}
			}
			v.Defs = append(v.Defs, d)
		}

		if len(v.Defs) == 1 && v.Defs[0].Init == vardecl[obj] {
			// No changes suggested.
			continue
		}

		vars = append(vars, v)
	}

	return vars
}