// Run function analyses main.main() then all the goroutines collected, and // finally output the analysis results. func (extract *CFSMExtract) Run() { startTime := time.Now() mainPkg := ssabuilder.MainPkg(extract.SSA.Prog) if mainPkg == nil { fmt.Fprintf(os.Stderr, "Error: 'main' package not found\n") os.Exit(1) } init := mainPkg.Func("init") main := mainPkg.Func("main") fr := makeToplevelFrame(extract) for _, pkg := range extract.SSA.Prog.AllPackages() { for _, memb := range pkg.Members { switch val := memb.(type) { case *ssa.Global: switch derefAll(val.Type()).(type) { case *types.Array: vd := utils.NewDef(val) fr.env.globals[val] = vd fr.env.arrays[vd] = make(Elems) case *types.Struct: vd := utils.NewDef(val) fr.env.globals[val] = vd fr.env.structs[vd] = make(Fields) case *types.Chan: var c *types.Chan vd := utils.NewDef(utils.EmptyValue{T: c}) fr.env.globals[val] = vd default: fr.env.globals[val] = utils.NewDef(val) } } } } fmt.Fprintf(os.Stderr, "++ call.toplevel %s()\n", orange("init")) visitFunc(init, fr) if main == nil { fmt.Fprintf(os.Stderr, "Error: 'main()' function not found in 'main' package\n") os.Exit(1) } fmt.Fprintf(os.Stderr, "++ call.toplevel %s()\n", orange("main")) visitFunc(main, fr) fr.env.session.Types[fr.gortn.role] = fr.gortn.root var goFrm *frame for len(extract.goQueue) > 0 { goFrm, extract.goQueue = extract.goQueue[0], extract.goQueue[1:] fmt.Fprintf(os.Stderr, "\n%s\nLOCATION: %s%s\n", goFrm.fn.Name(), goFrm.gortn.role.Name(), loc(goFrm, goFrm.fn.Pos())) visitFunc(goFrm.fn, goFrm) goFrm.env.session.Types[goFrm.gortn.role] = goFrm.gortn.root } extract.Time = time.Since(startTime) extract.Done <- struct{}{} }
func visitIndex(inst *ssa.Index, fr *frame) { elem := inst array := inst.X index := inst.Index _, isArray := array.Type().Underlying().(*types.Array) _, isSlice := array.Type().Underlying().(*types.Slice) if isArray || isSlice { switch vd, kind := fr.get(array); kind { case Array: fmt.Fprintf(os.Stderr, " %s = %s(=%s)[%d] of type %s\n", cyan(reg(elem)), array.Name(), vd.String(), index, elem.Type().String()) if fr.env.arrays[vd][index] == nil { // First use vdelem := utils.NewDef(elem) fr.env.arrays[vd][index] = vdelem fmt.Fprintf(os.Stderr, " ^ accessed for the first time: use %s as elem definition\n", elem.Name()) } else if fr.env.arrays[vd][index].Var != elem { // Previously defined fmt.Fprintf(os.Stderr, " ^ elem %s previously defined as %s\n", elem.Name(), reg(fr.env.arrays[vd][index].Var)) } // else Accessed before (and unchanged) fr.locals[elem] = fr.env.arrays[vd][index] case LocalArray: fmt.Fprintf(os.Stderr, " %s = %s(=%s)[%d] (local) of type %s\n", cyan(reg(elem)), array.Name(), vd.String(), index, elem.Type().String()) if fr.arrays[vd][index] == nil { // First use vdElem := utils.NewDef(elem) fr.arrays[vd][index] = vdElem fmt.Fprintf(os.Stderr, " ^ accessed for the first time: use %s as elem definition\n", elem.Name()) } else if fr.arrays[vd][index].Var != elem { // Previously defined fmt.Fprintf(os.Stderr, " ^ elem %s previously defined as %s\n", elem.Name(), reg(fr.arrays[vd][index].Var)) } // else Accessed before (and unchanged) fr.locals[elem] = fr.arrays[vd][index] case Nothing, Untracked: // Nothing: Very likely external struct. // Untracked: likely branches of return values (e.g. returning nil) fmt.Fprintf(os.Stderr, " %s = %s(=%s)[%d] (external) of type %s\n", cyan(reg(elem)), inst.X.Name(), vd.String(), index, elem.Type().String()) vd := utils.NewDef(array) // New external array fr.locals[array] = vd fr.env.arrays[vd] = make(Elems) vdElem := utils.NewDef(elem) // New external elem fr.env.arrays[vd][index] = vdElem fr.locals[elem] = vdElem fmt.Fprintf(os.Stderr, " ^ accessed for the first time: use %s as elem definition of type %s\n", elem.Name(), inst.Type().(*types.Pointer).Elem().Underlying().String()) default: panic(fmt.Sprintf("Index: Cannot access non-array %s", reg(array))) } } else { panic(fmt.Sprintf("Index: Cannot access element - %s not an array", reg(array))) } }
func visitExtract(e *ssa.Extract, fr *frame) { if recvCh, ok := fr.recvok[e.Tuple]; ok && e.Index == 1 { // 1 = ok (bool) fmt.Fprintf(os.Stderr, " EXTRACT for %s\n", recvCh.Name()) //fr.locals[e] = e fr.env.recvTest[e] = recvCh return } if tpl, ok := fr.tuples[e.Tuple]; ok { fmt.Fprintf(os.Stderr, " %s = extract %s[#%d] == %s\n", reg(e), e.Tuple.Name(), e.Index, tpl[e.Index].String()) fr.locals[e] = tpl[e.Index] } else { // Check if we are extracting select index if _, ok := fr.env.selNode[e.Tuple]; ok && e.Index == 0 { fmt.Fprintf(os.Stderr, " | %s = select %s index\n", e.Name(), e.Tuple.Name()) fr.env.selIdx[e] = e.Tuple return } // Check if value is an external tuple (return value) if extType, isExtern := fr.env.extern[e.Tuple]; isExtern { if extTpl, isTuple := extType.(*types.Tuple); isTuple { if extTpl.Len() < e.Index { panic(fmt.Sprintf("Extract: Cannot extract from tuple %s\n", e.Tuple.Name())) } // if extracted value is a chan create a new channel for it if _, ok := extTpl.At(e.Index).Type().(*types.Chan); ok { panic("Extract: Undefined channel") } } if e.Index < len(tpl) { fmt.Fprintf(os.Stderr, " extract %s[#%d] == %s\n", e.Tuple.Name(), e.Index, tpl[e.Index].String()) } else { fmt.Fprintf(os.Stderr, " extract %s[#%d/%d]\n", e.Tuple.Name(), e.Index, len(tpl)) } } else { fmt.Fprintf(os.Stderr, " # %s = %s of type %s\n", e.Name(), red(e.String()), e.Type().String()) switch derefAll(e.Type()).Underlying().(type) { case *types.Array: vd := utils.NewDef(e) fr.locals[e] = vd fr.arrays[vd] = make(Elems) fmt.Fprintf(os.Stderr, " ^ local array (used as definition)\n") case *types.Struct: vd := utils.NewDef(e) fr.locals[e] = vd fr.structs[vd] = make(Fields) fmt.Fprintf(os.Stderr, " ^ local struct (used as definition)\n") } } } }
func visitRecv(recv *ssa.UnOp, fr *frame) { locn := loc(fr, recv.X.Pos()) if vd, kind := fr.get(recv.X); kind == Chan { ch := fr.env.chans[vd] if recv.CommaOk { // ReceiveOK test fr.recvok[recv] = ch // TODO(nickng) technically this should do receive (both branches) } else { // Normal receive fr.gortn.AddNode(sesstype.NewRecvNode(*ch, fr.gortn.role, recv.X.Type())) fmt.Fprintf(os.Stderr, " %s\n", orange((*fr.gortn.leaf).String())) } } else if kind == Nothing { fr.locals[recv.X] = utils.NewDef(recv.X) ch := fr.env.session.MakeExtChan(fr.locals[recv.X], fr.gortn.role) fr.env.chans[fr.locals[recv.X]] = &ch fr.gortn.AddNode(sesstype.NewRecvNode(ch, fr.gortn.role, recv.X.Type())) fmt.Fprintf(os.Stderr, " %s\n", orange((*fr.gortn.leaf).String())) fmt.Fprintf(os.Stderr, " ^ Recv: Channel %s at %s is external\n", reg(recv.X), locn) } else { fr.printCallStack() panic(fmt.Sprintf("Recv: Channel %s at %s is of wrong kind", reg(recv.X), locn)) } }
// Tests SendNode creation. func TestSendNode(t *testing.T) { s := CreateSession() r := s.GetRole("main") c := s.MakeChan(utils.NewDef(utils.EmptyValue{T: nil}), r) n := NewSendNode(r, c, nil) if n.Kind() != SendOp { t.Errorf("Expecting node kind to be %s but got %s\n", SendOp, n.Kind()) } if n.(*SendNode).nondet { t.Errorf("Expecting Send to be deterministic by default\n") } if len(n.Children()) != 0 { t.Errorf("Expecting node to have 0 children but got %d\n", len(n.Children())) } n2 := NewSelectSendNode(r, c, nil) if n2.Kind() != SendOp { t.Errorf("Expecting node kind to be %s but got %s\n", SendOp, n2.Kind()) } if !n2.(*SendNode).nondet { t.Errorf("Expecting Select-Send to be non-deterministic by default\n") } if len(n2.Children()) != 0 { t.Errorf("Expecting node to have 0 children but got %d\n", len(n2.Children())) } if n2 != n.Append(n2) { t.Errorf("Appended node is not same as expected\n") } if len(n.Children()) != 1 { t.Errorf("Expecting node to have 1 children but got %d\n", len(n.Children())) } }
func visitMakeChan(inst *ssa.MakeChan, caller *frame) { locn := loc(caller, inst.Pos()) role := caller.gortn.role vd := utils.NewDef(inst) // Unique identifier for inst ch := caller.env.session.MakeChan(vd, role) caller.env.chans[vd] = &ch caller.gortn.AddNode(sesstype.NewNewChanNode(ch)) caller.locals[inst] = vd fmt.Fprintf(os.Stderr, " New channel %s { type: %s } by %s at %s\n", green(ch.Name()), ch.Type(), vd.String(), locn) fmt.Fprintf(os.Stderr, " ^ in role %s\n", role.Name()) }
// visitAlloc is for variable allocation (usually by 'new') // Everything allocated here are pointers func visitAlloc(inst *ssa.Alloc, fr *frame) { locn := loc(fr, inst.Pos()) allocType := inst.Type().(*types.Pointer).Elem() if allocType == nil { panic("Alloc: Cannot Alloc for non-pointer type") } var val ssa.Value = inst switch t := allocType.Underlying().(type) { case *types.Array: vd := utils.NewDef(val) fr.locals[val] = vd if inst.Heap { fr.env.arrays[vd] = make(Elems) fmt.Fprintf(os.Stderr, " %s = Alloc (array@heap) of type %s (%d elems) at %s\n", cyan(reg(inst)), inst.Type().String(), t.Len(), locn) } else { fr.arrays[vd] = make(Elems) fmt.Fprintf(os.Stderr, " %s = Alloc (array@local) of type %s (%d elems) at %s\n", cyan(reg(inst)), inst.Type().String(), t.Len(), locn) } case *types.Chan: // VD will be created in MakeChan so no need to allocate here. fmt.Fprintf(os.Stderr, " %s = Alloc (chan) of type %s at %s\n", cyan(reg(inst)), inst.Type().String(), locn) case *types.Struct: vd := utils.NewDef(val) fr.locals[val] = vd if inst.Heap { fr.env.structs[vd] = make(Fields, t.NumFields()) fmt.Fprintf(os.Stderr, " %s = Alloc (struct@heap) of type %s (%d fields) at %s\n", cyan(reg(inst)), inst.Type().String(), t.NumFields(), locn) } else { fr.structs[vd] = make(Fields, t.NumFields()) fmt.Fprintf(os.Stderr, " %s = Alloc (struct@local) of type %s (%d fields) at %s\n", cyan(reg(inst)), inst.Type().String(), t.NumFields(), locn) } default: fmt.Fprintf(os.Stderr, " # %s = "+red("Alloc %s")+" of type %s\n", inst.Name(), inst.String(), t.String()) } }
func visitSend(send *ssa.Send, fr *frame) { locn := loc(fr, send.Chan.Pos()) if vd, kind := fr.get(send.Chan); kind == Chan { ch := fr.env.chans[vd] fr.gortn.AddNode(sesstype.NewSendNode(fr.gortn.role, *ch, send.Chan.Type())) fmt.Fprintf(os.Stderr, " %s\n", orange((*fr.gortn.leaf).String())) } else if kind == Nothing { fr.locals[send.Chan] = utils.NewDef(send.Chan) ch := fr.env.session.MakeExtChan(fr.locals[send.Chan], fr.gortn.role) fr.env.chans[fr.locals[send.Chan]] = &ch fr.gortn.AddNode(sesstype.NewSendNode(fr.gortn.role, ch, send.Chan.Type())) fmt.Fprintf(os.Stderr, " %s\n", orange((*fr.gortn.leaf).String())) fmt.Fprintf(os.Stderr, " ^ Send: Channel %s at %s is external\n", reg(send.Chan), locn) } else { fr.printCallStack() panic(fmt.Sprintf("Send: Channel %s at %s is of wrong kind", reg(send.Chan), locn)) } }
// Tests NewEndNode creation. func TestEndNode(t *testing.T) { s := CreateSession() r := s.GetRole("main") c := s.MakeChan(utils.NewDef(utils.EmptyValue{T: nil}), r) n := NewEndNode(c) if n.Kind() != EndOp { t.Errorf("Expecting node kind to be %s but got %s\n", EndOp, n.Kind()) } if len(n.Children()) != 0 { t.Errorf("Expecting node to have 0 children but got %d\n", len(n.Children())) } n2 := NewEndNode(c) if n2 != n.Append(n2) { t.Errorf("Appended node is not same as expected\n") } if len(n.Children()) != 1 { t.Errorf("Expecting node to have 1 children but got %d\n", len(n.Children())) } }
// handleExtRetvals looks up and stores return value from (ext) function calls. // Ext functions have no code (no body to analyse) and unlike normal values, // the return values/tuples are stored until they are referenced. func (caller *frame) handleExtRetvals(returned ssa.Value, callee *frame) { // Since there are no code for the function, we use the function // signature to see if any of these are channels. // XXX We don't know where these come from so we put them in extern. resultsLen := callee.fn.Signature.Results().Len() if resultsLen > 0 { caller.env.extern[returned] = callee.fn.Signature.Results() if resultsLen == 1 { fmt.Fprintf(os.Stderr, "-- Return from %s (builtin/ext) with a single value\n", callee.fn.String()) if _, ok := callee.fn.Signature.Results().At(0).Type().(*types.Chan); ok { vardef := utils.NewDef(returned) ch := caller.env.session.MakeExtChan(vardef, caller.gortn.role) caller.env.chans[vardef] = &ch fmt.Fprintf(os.Stderr, "-- Return value from %s (builtin/ext) is a channel %s (ext)\n", callee.fn.String(), (*caller.env.chans[vardef]).Name()) } } else { fmt.Fprintf(os.Stderr, "-- Return from %s (builtin/ext) with %d-tuple\n", callee.fn.String(), resultsLen) } } }
func visitField(inst *ssa.Field, fr *frame) { field := inst struc := inst.X index := inst.Field if stype, ok := struc.Type().Underlying().(*types.Struct); ok { switch vd, kind := fr.get(struc); kind { case Struct: fmt.Fprintf(os.Stderr, " %s = %s(=%s).[%d] of type %s\n", cyan(reg(field)), struc.Name(), vd.String(), index, field.Type().String()) if fr.env.structs[vd][index] == nil { // First use vdField := utils.NewDef(field) fr.env.structs[vd][index] = vdField fmt.Fprintf(os.Stderr, " ^ accessed for the first time: use %s as field definition\n", field.Name()) // If field is struct if fieldType, ok := field.Type().Underlying().(*types.Struct); ok { fr.env.structs[vdField] = make(Fields, fieldType.NumFields()) fmt.Fprintf(os.Stderr, " ^ field %s is a struct (allocating)\n", field.Name()) } } else if fr.env.structs[vd][index].Var != field { // Previously defined fmt.Fprintf(os.Stderr, " ^ field %s previously defined as %s\n", field.Name(), reg(fr.env.structs[vd][index].Var)) } // else Accessed before (and unchanged) fr.locals[field] = fr.env.structs[vd][index] case LocalStruct: fmt.Fprintf(os.Stderr, " %s = %s(=%s).[%d] (local) of type %s\n", cyan(reg(field)), struc.Name(), vd.String(), index, field.Type().String()) if fr.structs[vd][index] == nil { // First use vdField := utils.NewDef(field) fr.structs[vd][index] = vdField fmt.Fprintf(os.Stderr, " ^ accessed for the first time: use %s as field definition\n", field.Name()) // If field is struct if fieldType, ok := field.Type().Underlying().(*types.Struct); ok { fr.structs[vdField] = make(Fields, fieldType.NumFields()) fmt.Fprintf(os.Stderr, " ^ field %s is a struct (allocating locally)\n", field.Name()) } } else if fr.structs[vd][index].Var != field { // Previously defined fmt.Fprintf(os.Stderr, " ^ field %s previously defined as %s\n", field.Name(), reg(fr.structs[vd][index].Var)) } // else Accessed before (and unchanged) fr.locals[field] = fr.structs[vd][index] case Nothing, Untracked: // Nothing: Very likely external struct. // Untracked: likely branches of return values (e.g. returning nil) fmt.Fprintf(os.Stderr, " %s = %s(=%s).[%d] (external) of type %s\n", cyan(reg(field)), inst.X.Name(), vd.String(), index, field.Type().String()) vd := utils.NewDef(struc) // New external struct fr.locals[struc] = vd fr.env.structs[vd] = make(Fields, stype.NumFields()) vdField := utils.NewDef(field) // New external field fr.env.structs[vd][index] = vdField fr.locals[field] = vdField fmt.Fprintf(os.Stderr, " ^ accessed for the first time: use %s as field definition of type %s\n", field.Name(), inst.Type().Underlying().String()) // If field is struct if fieldType, ok := field.Type().Underlying().(*types.Struct); ok { fr.env.structs[vdField] = make(Fields, fieldType.NumFields()) fmt.Fprintf(os.Stderr, " ^ field %s previously defined as %s\n", field.Name(), reg(fr.env.structs[vd][index].Var)) } default: panic(fmt.Sprintf("Field: Cannot access non-struct %s %T %d", reg(struc), struc.Type(), kind)) } } else { panic(fmt.Sprintf("Field: Cannot access field - %s not a struct\n", reg(struc))) } }
func visitMakeSlice(inst *ssa.MakeSlice, fr *frame) { fr.env.arrays[utils.NewDef(inst)] = make(Elems) }