goot is a static analysis framework for Go. goot is easy-to-learn, easy-to-use and highly extensible, allowing you to easily develop new analyses on top of it.
Currently, goot provides the following major analysis components (and more analyses are on the way):
- Control/Data-flow analysis framework
- Control-flow graph construction
- Classic data-flow analyses, e.g., constant propagtion analysis
- Your dataflow analyses
First intall goot by
go get -u github.com/cokeBeer/goot/goot
then you can run examples in package cmd
, like cmd/constantpropagationanalysis
package main
import (
"github.com/cokeBeer/goot/pkg/example/constantpropagation"
)
const src = `package main
func Hello(a int, b int) bool {
a = 1
x := a + 3
y := b + 2
if x > y {
x = x + 1
} else {
x = y + 1
}
w := x > 0
return w
}`
func main() {
runner := &constantpropagation.Runner{Src: src, Function: "Hello"}
runner.Run()
}
To use goot as a framework, first create two structs implementing pkg/toolkits/scalar.FlowAnalysis
interface
// FlowAnalysis represents a flow analysis
type FlowAnalysis interface {
GetGraph() *graph.UnitGraph
IsForward() bool
Computations() int
FlowThrougth(inMap *map[any]any, unit ssa.Instruction, outMap *map[any]any)
NewInitalFlow() *map[any]any
EntryInitalFlow() *map[any]any
Copy(srcMap *map[any]any, dstMap *map[any]any)
MergeInto(Unit ssa.Instruction, inout *map[any]any, in *map[any]any)
End(universe []*entry.Entry)
}
and pkg/golang/switcher.Switcher
interface seperately
// Switcher represents a ssa instruction switcher
type Switcher interface {
CaseAlloc(inst *ssa.Alloc)
CasePhi(inst *ssa.Phi)
CaseCall(inst *ssa.Call)
CaseBinOp(inst *ssa.BinOp)
CaseUnOp(inst *ssa.UnOp)
CaseChangeType(inst *ssa.ChangeType)
CaseConvert(inst *ssa.Convert)
CaseChangeInterface(inst *ssa.ChangeInterface)
CaseSliceToArrayPointer(inst *ssa.SliceToArrayPointer)
CaseMakeInterface(inst *ssa.MakeInterface)
CaseMakeClosure(inst *ssa.MakeClosure)
CaseMakeMap(inst *ssa.MakeMap)
CaseMakeChan(inst *ssa.MakeChan)
CaseMakeSlice(inst *ssa.MakeSlice)
CaseSlice(inst *ssa.Slice)
CaseFieldAddr(inst *ssa.FieldAddr)
CaseField(inst *ssa.Field)
CaseIndexAddr(inst *ssa.IndexAddr)
CaseIndex(inst *ssa.Index)
CaseLookup(inst *ssa.Lookup)
CaseSelect(inst *ssa.Select)
CaseRange(inst *ssa.Range)
CaseNext(inst *ssa.Next)
CaseTypeAssert(inst *ssa.TypeAssert)
CaseExtract(inst *ssa.Extract)
CaseJump(inst *ssa.Jump)
CaseIf(inst *ssa.If)
CaseReturn(inst *ssa.Return)
CaseRunDefers(inst *ssa.RunDefers)
CasePanic(inst *ssa.Panic)
CaseGo(inst *ssa.Go)
CaseDefer(inst *ssa.Defer)
CaseSend(inst *ssa.Send)
CaseStore(inst *ssa.Store)
CaseMapUpdate(inst *ssa.MapUpdate)
CaseDebugRef(inst *ssa.DebugRef)
}
Don't worry for these apis. An easy way to implement them is using compose like pkg/toolkits/scalar.BaseFlowAnalysis
// ConstantPropagationAnalysis represents a constant propagtion analysis
type ConstantPropagationAnalysis struct {
scalar.BaseFlowAnalysis
constantPropagationSwitcher *ConstantPropagationSwitcher
}
and pkg/golang/switcher.BaseSwitcher
// ConstantPropagationSwitcher represents a constant propagtion switcher
type ConstantPropagationSwitcher struct {
switcher.BaseSwitcher
constanctPropagationAnalysis *ConstantPropagationAnalysis
inMap *map[any]any
outMap *map[any]any
}
These can make you focus on the core methods you really need to design carefully in specific analyses
Some examples can be found in pkg/example
package
And you can learn how to run an analysis from cmd
package
Run cmd/constantpropagationanalysis
on
package main
func Hello(a int, b int) bool {
a = 1
x := a + 3
y := b + 2
if x > y {
x = x + 1
} else {
x = y + 1
}
w := x > 0
return w
}
You can get
# Name: constantpropagtionanalysis.Hello
# Package: constantpropagtionanalysis
# Location: 3:6
func Hello(a int, b int) bool:
0: entry P:0 S:2
t0 = 1:int + 3:int int
t1 = b + 2:int int
t2 = t0 > t1 bool
if t2 goto 1 else 3
1: if.then P:1 S:1
t3 = t0 + 1:int int
jump 2
2: if.done P:2 S:0
t4 = phi [1: t3, 3: t6] #x int
t5 = t4 > 0:int bool
return t5
3: if.else P:1 S:1
t6 = t1 + 1:int int
jump 2
constant fact for instruction: 1:int + 3:int
a=UNDEF b=UNDEF t0=4
constant fact for instruction: b + 2:int
a=UNDEF b=UNDEF t0=4 t1=UNDEF
constant fact for instruction: t0 > t1
a=UNDEF b=UNDEF t0=4 t1=UNDEF t2=UNDEF
constant fact for instruction: if t2 goto 1 else 3
a=UNDEF b=UNDEF t0=4 t1=UNDEF t2=UNDEF
constant fact for instruction: t1 + 1:int
a=UNDEF b=UNDEF t0=4 t1=UNDEF t2=UNDEF t6=UNDEF
constant fact for instruction: jump 2
a=UNDEF b=UNDEF t0=4 t1=UNDEF t2=UNDEF t6=UNDEF
constant fact for instruction: t0 + 1:int
a=UNDEF b=UNDEF t0=4 t1=UNDEF t2=UNDEF t3=5
constant fact for instruction: jump 2
a=UNDEF b=UNDEF t0=4 t1=UNDEF t2=UNDEF t3=5
constant fact for instruction: phi [1: t3, 3: t6] #x
a=UNDEF b=UNDEF t0=4 t1=UNDEF t2=UNDEF t3=5 t4=5 t6=UNDEF
constant fact for instruction: t4 > 0:int
a=UNDEF b=UNDEF t0=4 t1=UNDEF t2=UNDEF t3=5 t4=5 t5=NAC t6=UNDEF
constant fact for instruction: return t5
a=UNDEF b=UNDEF t0=4 t1=UNDEF t2=UNDEF t3=5 t4=5 t5=NAC t6=UNDEF
- goot's api is similar to soot, so if you wonder how goot's api work, you can learn soot first
- goot uses
*map[any]any
as flow andssa.Instruction
as unit, so please be careful of type assertion