Example #1
0
// createListPrim creates a list primitive containing a slice of Go statements
// based on the identified subgraph, its node pair mapping and its basic blocks.
// The new control flow primitive conceptually represents a basic block with the
// given name.
//
// Contents of "list.dot":
//
//    digraph list {
//       entry [label="entry"]
//       exit [label="exit"]
//       entry->exit
//    }
func createListPrim(m map[string]string, bbs map[string]BasicBlock, newName string) (*primitive, error) {
	// Locate graph nodes.
	nameEntry, ok := m["entry"]
	if !ok {
		return nil, errutil.New(`unable to locate node pair for sub node "entry"`)
	}
	nameExit, ok := m["exit"]
	if !ok {
		return nil, errutil.New(`unable to locate node pair for sub node "exit"`)
	}
	bbEntry, ok := bbs[nameEntry]
	if !ok {
		return nil, errutil.Newf("unable to locate basic block %q", nameEntry)
	}
	bbExit, ok := bbs[nameExit]
	if !ok {
		return nil, errutil.Newf("unable to locate basic block %q", nameExit)
	}

	// Create and return new primitive.
	//
	//    entry
	//    exit
	stmts := append(bbEntry.Stmts(), bbExit.Stmts()...)
	prim := &primitive{
		name:  newName,
		stmts: stmts,
		term:  bbExit.Term(),
	}
	return prim, nil
}
Example #2
0
// NewSubGraph returns a new subgraph based on graph with a dedicated entry and
// exit node. The entry and exit nodes are identified using the node "label"
// attribute, e.g.
//
//    digraph if {
//       A->B [label="true"]
//       A->C [label="false"]
//       B->C
//       A [label="entry"]
//       B
//       C [label="exit"]
//    }
func NewSubGraph(graph *dot.Graph) (*SubGraph, error) {
	sub := &SubGraph{Graph: graph}

	// Locate entry and exit nodes.
	var hasEntry, hasExit bool
	for _, node := range graph.Nodes.Nodes {
		label, ok := node.Attrs["label"]
		if !ok {
			continue
		}
		switch label {
		case "entry":
			if hasEntry {
				return nil, errutil.Newf(`redefinition of node with "entry" label; previous node %q, new node %q`, sub.entry, node.Name)
			}
			sub.entry = node.Name
			hasEntry = true
		case "exit":
			if hasExit {
				return nil, errutil.Newf(`redefinition of node with "exit" label; previous node %q, new node %q`, sub.exit, node.Name)
			}
			sub.exit = node.Name
			hasExit = true
		}
	}
	if !hasEntry {
		return nil, errutil.New(`unable to locate node with "entry" label`)
	}
	if !hasExit {
		return nil, errutil.New(`unable to locate node with "exit" label`)
	}

	return sub, nil
}
Example #3
0
// createPostLoopPrim creates a post-test loop primitive based on the identified
// subgraph, its node pair mapping and its basic blocks. The new control flow
// primitive conceptually represents a basic block with the given name.
//
// Contents of "post_loop.dot":
//
//    digraph post_loop {
//       cond [label="entry"]
//       exit [label="exit"]
//       cond->cond [label="true"]
//       cond->exit [label="false"]
//    }
func createPostLoopPrim(m map[string]string, bbs map[string]BasicBlock, newName string) (*primitive, error) {
	// Locate graph nodes.
	nameCond, ok := m["cond"]
	if !ok {
		return nil, errutil.New(`unable to locate node pair for sub node "cond"`)
	}
	nameExit, ok := m["exit"]
	if !ok {
		return nil, errutil.New(`unable to locate node pair for sub node "exit"`)
	}
	bbCond, ok := bbs[nameCond]
	if !ok {
		return nil, errutil.Newf("unable to locate basic block %q", nameCond)
	}
	bbExit, ok := bbs[nameExit]
	if !ok {
		return nil, errutil.Newf("unable to locate basic block %q", nameExit)
	}

	// Create and return new primitive.
	//
	//    for {
	//       cond_stmts
	//       if !cond {
	//          break
	//       }
	//    }
	//    exit

	// Create if-statement.
	cond, _, _, err := getBrCond(bbCond.Term())
	if err != nil {
		return nil, errutil.Err(err)
	}
	ifStmt := &ast.IfStmt{
		Cond: &ast.UnaryExpr{Op: token.NOT, X: cond}, // negate condition
		Body: &ast.BlockStmt{List: []ast.Stmt{&ast.BranchStmt{Tok: token.BREAK}}},
	}

	// Create for-loop.
	body := bbCond.Stmts()
	body = append(body, ifStmt)
	forStmt := &ast.ForStmt{
		Body: &ast.BlockStmt{List: body},
	}

	// Create primitive.
	stmts := []ast.Stmt{forStmt}
	stmts = append(stmts, bbExit.Stmts()...)
	prim := &primitive{
		name:  newName,
		stmts: stmts,
		term:  bbExit.Term(),
	}
	return prim, nil
}
Example #4
0
// createIfPrim creates an if-statement primitive based on the identified
// subgraph, its node pair mapping and its basic blocks. The new control flow
// primitive conceptually represents a basic block with the given name.
//
// Contents of "if.dot":
//
//    digraph if {
//       cond [label="entry"]
//       body
//       exit [label="exit"]
//       cond->body [label="true"]
//       cond->exit [label="false"]
//       body->exit
//    }
func createIfPrim(m map[string]string, bbs map[string]BasicBlock, newName string) (*primitive, error) {
	// Locate graph nodes.
	nameCond, ok := m["cond"]
	if !ok {
		return nil, errutil.New(`unable to locate node pair for sub node "cond"`)
	}
	nameBody, ok := m["body"]
	if !ok {
		return nil, errutil.New(`unable to locate node pair for sub node "body"`)
	}
	nameExit, ok := m["exit"]
	if !ok {
		return nil, errutil.New(`unable to locate node pair for sub node "exit"`)
	}
	bbCond, ok := bbs[nameCond]
	if !ok {
		return nil, errutil.Newf("unable to locate basic block %q", nameCond)
	}
	bbBody, ok := bbs[nameBody]
	if !ok {
		return nil, errutil.Newf("unable to locate basic block %q", nameBody)
	}
	bbExit, ok := bbs[nameExit]
	if !ok {
		return nil, errutil.Newf("unable to locate basic block %q", nameExit)
	}

	// Create and return new primitive.
	//
	//    cond_stmts
	//    if cond {
	//       body
	//    }
	//    exit

	// Create if-statement.
	cond, _, _, err := getBrCond(bbCond.Term())
	if err != nil {
		return nil, errutil.Err(err)
	}
	ifStmt := &ast.IfStmt{
		Cond: cond,
		Body: &ast.BlockStmt{List: bbBody.Stmts()},
	}

	// Create primitive.
	stmts := append(bbCond.Stmts(), ifStmt)
	stmts = append(stmts, bbExit.Stmts()...)
	prim := &primitive{
		name:  newName,
		stmts: stmts,
		term:  bbExit.Term(),
	}
	return prim, nil
}
Example #5
0
// solveBrute tries to solve the node pair equation through brute force. It
// recursively locates and attempts to solve the easiest node pair (i.e. the one
// with the fewest number of candidates) until the equation is solved, or until
// all potential solutions have been exhausted.
func (eq *equation) solveBrute(graph *dot.Graph, sub *graphs.SubGraph) (m map[string]string, err error) {
	if eq.isValid(graph, sub) {
		return eq.m, nil
	}
	sname, err := eq.easiest()
	if err != nil {
		return nil, err
	}

	// Sort candidates to make the algorithm deterministic.
	candidates := make([]string, 0, len(eq.c[sname]))
	for gname := range eq.c[sname] {
		candidates = append(candidates, gname)
	}
	sort.Strings(candidates)

	for _, gname := range candidates {
		dup := eq.dup()
		err = dup.setPair(sname, gname)
		if err != nil {
			continue
		}
		m, err := dup.solveBrute(graph, sub)
		if err != nil {
			continue
		}
		return m, nil
	}

	return nil, errutil.New("unable to locate node pair mapping")
}
Example #6
0
func ExampleNew() {
	errutil.UseColor = false
	err := errutil.New("failure.")
	fmt.Println(err)

	// Output:
	// github.com/mewkiz/pkg/errutil_test.ExampleNew (err_test.go:11): failure.
}
Example #7
0
// parseOperand converts the provided LLVM IR operand into an equivalent Go AST
// expression node (a basic literal, a composite literal or an identifier).
//
// Syntax:
//    i32 1
//    %foo = ...
func parseOperand(op llvm.Value) (ast.Expr, error) {
	// TODO: Support *BasicLit, *CompositeLit.

	// Parse and validate tokens.
	tokens, err := getTokens(op)
	if err != nil {
		return nil, err
	}
	if len(tokens) < 2 {
		// TODO: Remove debug output.
		op.Dump()
		return nil, errutil.Newf("unable to parse operand; expected 2 >= tokens, got %d", len(tokens))
	}

	// TODO: Add support for operand of other types than int.
	// TODO: Parse type.

	// Create and return a constant operand.
	//    i32 42
	if tokens[0].Kind == lltoken.Type {
		switch tok := tokens[1]; tok.Kind {
		case lltoken.Int:
			return &ast.BasicLit{Kind: token.INT, Value: tok.Val}, nil
		case lltoken.LocalVar:
			return getIdent(tok)
		default:
			return nil, errutil.Newf("support for LLVM IR token kind %v not yet implemented", tok.Kind)
		}
	}

	// Create and return a variable operand.
	//    %foo = ...
	if tokens[1].Kind == lltoken.Equal {
		switch tok := tokens[0]; tok.Kind {
		case lltoken.LocalVar, lltoken.LocalID:
			//    %foo
			//    %42
			return getIdent(tok)
		default:
			return nil, errutil.Newf("support for LLVM IR token kind %v not yet implemented", tok.Kind)
		}
	}

	return nil, errutil.New("support for LLVM IR operand not yet implemented")
}
Example #8
0
// restructure attempts to create a structured control flow for a function based
// on the provided control flow graph (which contains one node per basic block)
// and the function's basic blocks. It does so by repeatedly locating and
// merging structured subgraphs into single nodes until the entire graph is
// reduced into a single node or no structured subgraphs may be located.
func restructure(graph *dot.Graph, bbs map[string]BasicBlock, hprims []*xprimitive.Primitive) (*ast.BlockStmt, error) {
	for _, hprim := range hprims {
		subName := hprim.Prim // identified primitive; e.g. "if", "if_else"
		m := hprim.Nodes      // node mapping
		newName := hprim.Node // new node name

		// Create a control flow primitive based on the identified subgraph.
		primBBs := make(map[string]BasicBlock)
		for _, gname := range m {
			bb, ok := bbs[gname]
			if !ok {
				return nil, errutil.Newf("unable to locate basic block %q", gname)
			}
			primBBs[gname] = bb
			delete(bbs, gname)
		}
		prim, err := createPrim(subName, m, primBBs, newName)
		if err != nil {
			return nil, errutil.Err(err)
		}
		if flagVerbose && !flagQuiet {
			fmt.Println("located primitive:")
			printBB(prim)
		}
		bbs[prim.Name()] = prim
	}

	for _, bb := range bbs {
		if !bb.Term().IsNil() {
			// TODO: Remove debug output.
			bb.Term().Dump()
			return nil, errutil.Newf("invalid terminator instruction of last basic block in function; expected nil since return statements are already handled")
		}
		block := &ast.BlockStmt{
			List: bb.Stmts(),
		}
		return block, nil
	}
	return nil, errutil.New("unable to locate basic block")
}
Example #9
0
// createPreLoopPrim creates a pre-test loop primitive based on the identified
// subgraph, its node pair mapping and its basic blocks. The new control flow
// primitive conceptually represents a basic block with the given name.
//
// Contents of "pre_loop.dot":
//
//    digraph pre_loop {
//       cond [label="entry"]
//       body
//       exit [label="exit"]
//       cond->body [label="true"]
//       body->cond
//       cond->exit [label="false"]
//    }
func createPreLoopPrim(m map[string]string, bbs map[string]BasicBlock, newName string) (*primitive, error) {
	// Locate graph nodes.
	nameCond, ok := m["cond"]
	if !ok {
		return nil, errutil.New(`unable to locate node pair for sub node "cond"`)
	}
	nameBody, ok := m["body"]
	if !ok {
		return nil, errutil.New(`unable to locate node pair for sub node "body"`)
	}
	nameExit, ok := m["exit"]
	if !ok {
		return nil, errutil.New(`unable to locate node pair for sub node "exit"`)
	}
	bbCond, ok := bbs[nameCond]
	if !ok {
		return nil, errutil.Newf("unable to locate basic block %q", nameCond)
	}
	bbBody, ok := bbs[nameBody]
	if !ok {
		return nil, errutil.Newf("unable to locate basic block %q", nameBody)
	}
	bbExit, ok := bbs[nameExit]
	if !ok {
		return nil, errutil.Newf("unable to locate basic block %q", nameExit)
	}

	// Locate and expand the condition.
	//
	//    // from:
	//    _2 := i < 10
	//    if _2 {
	//
	//    // to:
	//    if i < 10 {
	cond, _, _, err := getBrCond(bbCond.Term())
	if err != nil {
		return nil, errutil.Err(err)
	}
	cond, err = expand(bbCond, cond)
	if err != nil {
		return nil, errutil.Err(err)
	}

	if len(bbCond.Stmts()) != 0 {
		// Produce the following primitive instead of a regular for loop if cond
		// contains statements.
		//
		//    for {
		//       cond_stmts
		//       if !cond {
		//          break
		//       }
		//       body_true
		//    }
		//    exit

		// Create if-statement.
		ifStmt := &ast.IfStmt{
			Cond: &ast.UnaryExpr{Op: token.NOT, X: cond}, // negate condition
			Body: &ast.BlockStmt{List: []ast.Stmt{&ast.BranchStmt{Tok: token.BREAK}}},
		}

		// Create for-loop.
		body := append(bbCond.Stmts(), ifStmt)
		body = append(body, bbBody.Stmts()...)
		forStmt := &ast.ForStmt{
			Body: &ast.BlockStmt{List: body},
		}

		// Create primitive.
		stmts := []ast.Stmt{forStmt}
		stmts = append(stmts, bbExit.Stmts()...)
		prim := &primitive{
			name:  newName,
			stmts: stmts,
			term:  bbExit.Term(),
		}
		return prim, nil
	}

	// Create and return new primitive.
	//
	//    for cond {
	//       body_true
	//    }
	//    exit

	// Create for-loop.
	forStmt := &ast.ForStmt{
		Cond: cond,
		Body: &ast.BlockStmt{List: bbBody.Stmts()},
	}

	// Create primitive.
	stmts := []ast.Stmt{forStmt}
	stmts = append(stmts, bbExit.Stmts()...)
	prim := &primitive{
		name:  newName,
		stmts: stmts,
		term:  bbExit.Term(),
	}
	return prim, nil
}
Example #10
0
// createIfElsePrim creates an if-else primitive based on the identified
// subgraph, its node pair mapping and its basic blocks. The new control flow
// primitive conceptually represents a basic block with the given name.
//
// Contents of "if_else.dot":
//
//    digraph if_else {
//       cond [label="entry"]
//       body_true
//       body_false
//       exit [label="exit"]
//       cond->body_true [label="true"]
//       cond->body_false [label="false"]
//       body_true->exit
//       body_false->exit
//    }
func createIfElsePrim(m map[string]string, bbs map[string]BasicBlock, newName string) (*primitive, error) {
	// Locate graph nodes.
	nameCond, ok := m["cond"]
	if !ok {
		return nil, errutil.New(`unable to locate node pair for sub node "cond"`)
	}
	nameBodyTrue, ok := m["body_true"]
	if !ok {
		return nil, errutil.New(`unable to locate node pair for sub node "body_true"`)
	}
	nameBodyFalse, ok := m["body_false"]
	if !ok {
		return nil, errutil.New(`unable to locate node pair for sub node "body_false"`)
	}
	nameExit, ok := m["exit"]
	if !ok {
		return nil, errutil.New(`unable to locate node pair for sub node "exit"`)
	}
	bbCond, ok := bbs[nameCond]
	if !ok {
		return nil, errutil.Newf("unable to locate basic block %q", nameCond)
	}

	// The body nodes (body_true and body_false) of if-else primitives are
	// indistinguishable at the graph level. Verify their names against the
	// terminator instruction of the basic block and swap them if necessary.
	cond, targetTrue, targetFalse, err := getBrCond(bbCond.Term())
	if err != nil {
		return nil, errutil.Err(err)
	}
	if targetTrue != nameBodyTrue && targetTrue != nameBodyFalse {
		return nil, errutil.Newf("invalid target true branch; got %q, expected %q or %q", targetTrue, nameBodyTrue, nameBodyFalse)
	}
	if targetFalse != nameBodyTrue && targetFalse != nameBodyFalse {
		return nil, errutil.Newf("invalid target false branch; got %q, expected %q or %q", targetFalse, nameBodyTrue, nameBodyFalse)
	}
	nameBodyTrue = targetTrue
	nameBodyFalse = targetFalse

	bbBodyTrue, ok := bbs[nameBodyTrue]
	if !ok {
		return nil, errutil.Newf("unable to locate basic block %q", nameBodyTrue)
	}
	bbBodyFalse, ok := bbs[nameBodyFalse]
	if !ok {
		return nil, errutil.Newf("unable to locate basic block %q", nameBodyFalse)
	}
	bbExit, ok := bbs[nameExit]
	if !ok {
		return nil, errutil.Newf("unable to locate basic block %q", nameExit)
	}

	// Create and return new primitive.
	//
	//    cond_stmts
	//    if cond {
	//       body_true
	//    } else {
	//       body_false
	//    }
	//    exit

	// Create if-else statement.
	ifElseStmt := &ast.IfStmt{
		Cond: cond,
		Body: &ast.BlockStmt{List: bbBodyTrue.Stmts()},
		Else: &ast.BlockStmt{List: bbBodyFalse.Stmts()},
	}

	// Create primitive.
	stmts := append(bbCond.Stmts(), ifElseStmt)
	stmts = append(stmts, bbExit.Stmts()...)
	prim := &primitive{
		name:  newName,
		stmts: stmts,
		term:  bbExit.Term(),
	}
	return prim, nil
}
Example #11
0
// findPrim locates a control flow primitive in the provided control flow graph
// and merges its nodes into a single node.
func findPrim(graph *dot.Graph, subs []*graphs.SubGraph, step int) (*primitive.Primitive, error) {
	for _, sub := range subs {
		// Locate an isomorphism of sub in graph.
		m, ok := iso.Search(graph, sub)
		if !ok {
			// No match, try next control flow primitive.
			continue
		}
		if flagVerbose && !flagQuiet {
			printMapping(graph, sub, m)
		}

		// Output pre-merge intermediate control flow graphs.
		if flagSteps {
			// Highlight nodes to be replaced in red.
			for _, preNodeName := range m {
				preNode, ok := graph.Nodes.Lookup[preNodeName]
				if !ok {
					return nil, errutil.Newf("unable to locate pre-merge node %q", preNodeName)
				}
				if preNode.Attrs == nil {
					preNode.Attrs = dot.NewAttrs()
				}
				preNode.Attrs["fillcolor"] = "red"
				preNode.Attrs["style"] = "filled"
			}

			// Store pre-merge DOT graph.
			stepName := fmt.Sprintf("%s_%da", graph.Name, step)
			if err := createDOT(stepName, graph); err != nil {
				return nil, errutil.Err(err)
			}

			// Restore node colour.
			for _, preNodeName := range m {
				preNode, ok := graph.Nodes.Lookup[preNodeName]
				if !ok {
					return nil, errutil.Newf("unable to locate pre-merge node %q", preNodeName)
				}

				delete(preNode.Attrs, "fillcolor")
				delete(preNode.Attrs, "style")
			}
		}

		// Check if one of the nodes to be merged has the label "entry".
		hasEntry := false
		for _, preNodeName := range m {
			preNode, ok := graph.Nodes.Lookup[preNodeName]
			if !ok {
				return nil, errutil.Newf("unable to locate pre-merge node %q", preNodeName)
			}
			if preNode.Attrs != nil && preNode.Attrs["label"] == "entry" {
				hasEntry = true
				break
			}
		}

		// Merge the nodes of the subgraph isomorphism into a single node.
		postNodeName, err := merge.Merge(graph, m, sub)
		if err != nil {
			return nil, errutil.Err(err)
		}

		// Add "entry" label to new node if present in the pre-merge nodes.
		if hasEntry {
			postNode, ok := graph.Nodes.Lookup[postNodeName]
			if !ok {
				return nil, errutil.Newf("unable to locate post-merge node %q", postNodeName)
			}
			if postNode.Attrs == nil {
				postNode.Attrs = dot.NewAttrs()
			}
			postNode.Attrs["label"] = "entry"
			index := postNode.Index
			graph.Nodes.Nodes[0], graph.Nodes.Nodes[index] = postNode, graph.Nodes.Nodes[0]
		}

		// Output post-merge intermediate control flow graphs.
		if flagSteps {
			// Highlight node to be replaced in red.
			postNode, ok := graph.Nodes.Lookup[postNodeName]
			if !ok {
				return nil, errutil.Newf("unable to locate post-merge node %q", postNodeName)
			}
			if postNode.Attrs == nil {
				postNode.Attrs = dot.NewAttrs()
			}
			postNode.Attrs["fillcolor"] = "red"
			postNode.Attrs["style"] = "filled"

			// Store post-merge DOT graph.
			stepName := fmt.Sprintf("%s_%db", graph.Name, step)
			if err := createDOT(stepName, graph); err != nil {
				return nil, errutil.Err(err)
			}

			// Restore node colour.
			delete(postNode.Attrs, "fillcolor")
			delete(postNode.Attrs, "style")
		}

		// Create a new control flow primitive.
		prim := &primitive.Primitive{
			Node:  postNodeName,
			Prim:  sub.Name,
			Nodes: m,
		}
		return prim, nil
	}

	return nil, errutil.New("unable to locate control flow primitive")
}