package host import ( "go/ast" "go/parser" "go/token" "sort" "strings" "testing" ) // TestEveryRenameAndFinishIsGuarded is the invariant of moves Task 13 (fix // round 3) as a scan of systemapply.go: every os.Rename (an executable put // in place), every a.finish( and every a.removeJournal() or // a.closeGuardJournal() (a journal finished; closeGuardJournal is the claim // release floor's close from Plan 8, which removes the journal and then // completes the guard transaction) sits inside a function literal passed to // a.activationGuard, // unless the table below names it and says why. A new call that is // neither fails here, and so does an exemption no call uses any more. func TestEveryRenameAndFinishIsGuarded(t *testing.T) { exempt := map[string]string{ // The ordinary install finish follows the guarded rename: the // floor protects activation, and the rename is the activation. "install a.finish": "after the guarded rename", // Inside finish itself; its callers are guarded or exempt. "finish a.closeGuardJournal": "finish is the guarded or exempt step", // The install never reached its rename: nothing was activated. "abandonInstall a.removeJournal": "no executable changed", // A legacy backing-up journal: nothing on the box had changed. "recoverIfNeeded a.closeGuardJournal (legacy backing-up)": "no executable changed", } // Sites that must be guarded, so a guard removed from one is caught even // if someone also adds it to the exemptions. mustGuard := []string{ "install os.Rename", "restoreFiles os.Rename", "recoverIfNeeded a.finish", "recoverIfNeeded a.closeGuardRollback", "rollback a.closeGuardRollback", } fset := token.NewFileSet() file, err := parser.ParseFile(fset, "systemapply.go", nil, parser.ParseComments) if err != nil { t.Fatal(err) } guarded := map[string]int{} used := map[string]bool{} var unguarded []string for _, decl := range file.Decls { fn, ok := decl.(*ast.FuncDecl) if !ok || fn.Body == nil { continue } var stack []ast.Node ast.Inspect(fn.Body, func(n ast.Node) bool { if n == nil { stack = stack[:len(stack)-1] return true } stack = append(stack, n) call, ok := n.(*ast.CallExpr) if !ok { return true } name := callName(call) if name != "os.Rename" && name != "a.finish" && name != "a.removeJournal" && name != "a.closeGuardJournal" && name != "a.closeGuardRollback" { return true } site := fn.Name.Name + " " + name if insideGuard(stack) { guarded[site]++ return true } key := site if _, ok := exempt[key]; !ok && fn.Name.Name == "recoverIfNeeded" && (name == "a.removeJournal" || name == "a.closeGuardJournal") && legacyBranch(stack) { key = site + " (legacy backing-up)" } if _, ok := exempt[key]; ok { used[key] = true return true } unguarded = append(unguarded, site+" at "+fset.Position(call.Pos()).String()) return true }) } sort.Strings(unguarded) for _, u := range unguarded { t.Errorf("%s is outside activationGuard and not exempt", u) } for _, s := range mustGuard { if guarded[s] == 0 { t.Errorf("%s is not inside activationGuard", s) } } for k := range exempt { if !used[k] { t.Errorf("exemption %q matches no call any more", k) } } } func callName(call *ast.CallExpr) string { sel, ok := call.Fun.(*ast.SelectorExpr) if !ok { return "" } if id, ok := sel.X.(*ast.Ident); ok { return id.Name + "." + sel.Sel.Name } return "" } // insideGuard: some enclosing function literal is an argument of a call to // a.activationGuard. func insideGuard(stack []ast.Node) bool { for i := len(stack) - 1; i > 0; i-- { lit, ok := stack[i].(*ast.FuncLit) if !ok { continue } if call, ok := stack[i-1].(*ast.CallExpr); ok && callName(call) == "a.activationGuard" { for _, arg := range call.Args { if arg == lit { return true } } } } return false } // legacyBranch: the call sits in the `if j.Phase == systemPhaseBackingUp` // branch. func legacyBranch(stack []ast.Node) bool { for _, n := range stack { if ifs, ok := n.(*ast.IfStmt); ok { if be, ok := ifs.Cond.(*ast.BinaryExpr); ok { if id, ok := be.Y.(*ast.Ident); ok && strings.Contains(id.Name, "BackingUp") { return true } } } } return false }