sanity.go (19790B)
1 // Copyright 2013 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package ssa 6 7 // An optional pass for sanity-checking invariants of the SSA representation. 8 // Currently it checks CFG invariants but little at the instruction level. 9 10 import ( 11 "bytes" 12 "fmt" 13 "go/ast" 14 "go/types" 15 "io" 16 "os" 17 "slices" 18 "strings" 19 20 "golang.org/x/tools/internal/typeparams" 21 ) 22 23 type sanity struct { 24 reporter io.Writer 25 fn *Function 26 block *BasicBlock 27 instrs map[Instruction]unit 28 insane bool 29 } 30 31 // sanityCheck performs integrity checking of the SSA representation 32 // of the function fn (which must have been "built") and returns true 33 // if it was valid. Diagnostics are written to reporter if non-nil, 34 // os.Stderr otherwise. Some diagnostics are only warnings and do not 35 // imply a negative result. 36 // 37 // Sanity-checking is intended to facilitate the debugging of code 38 // transformation passes. 39 func sanityCheck(fn *Function, reporter io.Writer) bool { 40 if reporter == nil { 41 reporter = os.Stderr 42 } 43 return (&sanity{reporter: reporter}).checkFunction(fn) 44 } 45 46 // mustSanityCheck is like sanityCheck but panics instead of returning 47 // a negative result. 48 func mustSanityCheck(fn *Function, reporter io.Writer) { 49 if !sanityCheck(fn, reporter) { 50 fn.WriteTo(os.Stderr) 51 panic("SanityCheck failed") 52 } 53 } 54 55 func (s *sanity) diagnostic(prefix, format string, args ...any) { 56 fmt.Fprintf(s.reporter, "%s: function %s", prefix, s.fn) 57 if s.block != nil { 58 fmt.Fprintf(s.reporter, ", block %s", s.block) 59 } 60 io.WriteString(s.reporter, ": ") 61 fmt.Fprintf(s.reporter, format, args...) 62 io.WriteString(s.reporter, "\n") 63 } 64 65 func (s *sanity) errorf(format string, args ...any) { 66 s.insane = true 67 s.diagnostic("Error", format, args...) 68 } 69 70 func (s *sanity) warnf(format string, args ...any) { 71 s.diagnostic("Warning", format, args...) 72 } 73 74 // findDuplicate returns an arbitrary basic block that appeared more 75 // than once in blocks, or nil if all were unique. 76 func findDuplicate(blocks []*BasicBlock) *BasicBlock { 77 if len(blocks) < 2 { 78 return nil 79 } 80 if blocks[0] == blocks[1] { 81 return blocks[0] 82 } 83 // Slow path: 84 m := make(map[*BasicBlock]bool) 85 for _, b := range blocks { 86 if m[b] { 87 return b 88 } 89 m[b] = true 90 } 91 return nil 92 } 93 94 func (s *sanity) checkInstr(idx int, instr Instruction) { 95 switch instr := instr.(type) { 96 case *If, *Jump, *Return, *Panic: 97 s.errorf("control flow instruction not at end of block") 98 case *Phi: 99 if idx == 0 { 100 // It suffices to apply this check to just the first phi node. 101 if dup := findDuplicate(s.block.Preds); dup != nil { 102 s.errorf("phi node in block with duplicate predecessor %s", dup) 103 } 104 } else { 105 prev := s.block.Instrs[idx-1] 106 if _, ok := prev.(*Phi); !ok { 107 s.errorf("Phi instruction follows a non-Phi: %T", prev) 108 } 109 } 110 if ne, np := len(instr.Edges), len(s.block.Preds); ne != np { 111 s.errorf("phi node has %d edges but %d predecessors", ne, np) 112 113 } else { 114 for i, e := range instr.Edges { 115 if e == nil { 116 s.errorf("phi node '%s' has no value for edge #%d from %s", instr.Comment, i, s.block.Preds[i]) 117 } else if !types.Identical(instr.typ, e.Type()) { 118 s.errorf("phi node '%s' has a different type (%s) for edge #%d from %s (%s)", 119 instr.Comment, instr.Type(), i, s.block.Preds[i], e.Type()) 120 } 121 } 122 } 123 124 case *Alloc: 125 if !instr.Heap { 126 found := slices.Contains(s.fn.Locals, instr) 127 if !found { 128 s.errorf("local alloc %s = %s does not appear in Function.Locals", instr.Name(), instr) 129 } 130 } 131 132 case *BinOp: 133 case *Call: 134 if common := instr.Call; common.IsInvoke() { 135 if !types.IsInterface(common.Value.Type()) { 136 s.errorf("invoke on %s (%s) which is not an interface type (or type param)", common.Value, common.Value.Type()) 137 } 138 } 139 case *ChangeInterface: 140 case *ChangeType: 141 case *SliceToArrayPointer: 142 case *Convert: 143 if from := instr.X.Type(); !isBasicConvTypes(from) { 144 if to := instr.Type(); !isBasicConvTypes(to) { 145 s.errorf("convert %s -> %s: at least one type must be basic (or all basic, []byte, or []rune)", from, to) 146 } 147 } 148 case *MultiConvert: 149 case *Defer: 150 case *Extract: 151 case *Field: 152 case *FieldAddr: 153 case *Go: 154 case *Index: 155 case *IndexAddr: 156 case *Lookup: 157 case *MakeChan: 158 case *MakeClosure: 159 fn := instr.Fn.(*Function) 160 if numFree, numBind := len(fn.FreeVars), len(instr.Bindings); numFree != numBind { 161 s.errorf("MakeClosure has %d Bindings for function %s with %d free vars", 162 numBind, instr.Fn, numFree) 163 } else { 164 for i, fv := range fn.FreeVars { 165 if !types.Identical(instr.Bindings[i].Type(), fv.Type()) { 166 s.errorf("MakeClosure binding %d for %s has type %s, expected %s", 167 i, fv.Name(), instr.Bindings[i].Type(), fv.Type()) 168 } 169 } 170 } 171 if recv := instr.Type().(*types.Signature).Recv(); recv != nil { 172 s.errorf("MakeClosure's type includes receiver %s", recv.Type()) 173 } 174 175 case *MakeInterface: 176 case *MakeMap: 177 case *MakeSlice: 178 case *MapUpdate: 179 case *Next: 180 rng, ok := instr.Iter.(*Range) 181 if !ok { 182 s.errorf("Next: Iter is %T, not *Range", instr.Iter) 183 } 184 if rng.Type() != tRangeIter { 185 s.errorf("Next: Iter has type %s, expected %s", rng.Type(), tRangeIter) 186 } 187 var ek, ev types.Type 188 switch xt := typeparams.CoreType(rng.X.Type()).(type) { 189 case *types.Basic: 190 if types.Default(xt) != tString { 191 s.errorf("Next: basic operand of Next.Iter (Range) is %s, want string or untyped string", xt) 192 } 193 ek, ev = tInt, tRune 194 case *types.Map: 195 ek, ev = xt.Key(), xt.Elem() 196 } 197 198 res := instr.Type().(*types.Tuple) // (ok bool, k K, v V), but K or V may be invalid if unused 199 if !types.Identical(res.At(1).Type(), ek) && res.At(1).Type() != tInvalid { 200 s.errorf("Next: key type %s does not match map key type %s", res.At(1).Type(), ek) 201 } 202 if !types.Identical(res.At(2).Type(), ev) && res.At(2).Type() != tInvalid { 203 s.errorf("Next: value type %s does not match map value type %s", res.At(2).Type(), ev) 204 } 205 206 case *Range: 207 case *RunDefers: 208 case *Select: 209 case *Send: 210 case *Slice: 211 case *Store: 212 if !types.Identical(instr.Val.Type(), typeparams.CoreType(instr.Addr.Type()).(*types.Pointer).Elem()) { 213 s.errorf("Store: value type %s does not match address type %s", 214 instr.Val.Type(), instr.Addr.Type()) 215 } 216 case *TypeAssert: 217 case *UnOp: 218 case *DebugRef: 219 // TODO(adonovan): implement checks. 220 default: 221 panic(fmt.Sprintf("Unknown instruction type: %T", instr)) 222 } 223 224 if call, ok := instr.(CallInstruction); ok { 225 if call.Common().Signature() == nil { 226 s.errorf("nil signature: %s", call) 227 } 228 } 229 230 // Check that value-defining instructions have valid types 231 // and a valid referrer list. 232 if v, ok := instr.(Value); ok { 233 t := v.Type() 234 if t == nil { 235 s.errorf("no type: %s = %s", v.Name(), v) 236 } else if t == tRangeIter || t == tDeferStack { 237 // not a proper type; ignore. 238 } else if b, ok := t.Underlying().(*types.Basic); ok && b.Info()&types.IsUntyped != 0 { 239 s.errorf("instruction has 'untyped' result: %s = %s : %s", v.Name(), v, t) 240 } 241 s.checkReferrerList(v) 242 } 243 244 // Untyped constants are legal as instruction Operands(), 245 // for example: 246 // _ = "foo"[0] 247 // or: 248 // if wordsize==64 {...} 249 250 // All other non-Instruction Values can be found via their 251 // enclosing Function or Package. 252 } 253 254 func (s *sanity) checkFinalInstr(instr Instruction) { 255 switch instr := instr.(type) { 256 case *If: 257 if nsuccs := len(s.block.Succs); nsuccs != 2 { 258 s.errorf("If-terminated block has %d successors; expected 2", nsuccs) 259 return 260 } 261 if s.block.Succs[0] == s.block.Succs[1] { 262 s.errorf("If-instruction has same True, False target blocks: %s", s.block.Succs[0]) 263 return 264 } 265 266 case *Jump: 267 if nsuccs := len(s.block.Succs); nsuccs != 1 { 268 s.errorf("Jump-terminated block has %d successors; expected 1", nsuccs) 269 return 270 } 271 272 case *Return: 273 if nsuccs := len(s.block.Succs); nsuccs != 0 { 274 s.errorf("Return-terminated block has %d successors; expected none", nsuccs) 275 return 276 } 277 if na, nf := len(instr.Results), s.fn.Signature.Results().Len(); nf != na { 278 s.errorf("%d-ary return in %d-ary function", na, nf) 279 } 280 281 case *Panic: 282 if nsuccs := len(s.block.Succs); nsuccs != 0 { 283 s.errorf("Panic-terminated block has %d successors; expected none", nsuccs) 284 return 285 } 286 287 default: 288 s.errorf("non-control flow instruction at end of block") 289 } 290 } 291 292 func (s *sanity) checkBlock(b *BasicBlock, index int) { 293 s.block = b 294 295 if b.Index != index { 296 s.errorf("block has incorrect Index %d", b.Index) 297 } 298 if b.parent != s.fn { 299 s.errorf("block has incorrect parent %s", b.parent) 300 } 301 302 // Check all blocks are reachable. 303 // (The entry block is always implicitly reachable, 304 // as is the Recover block, if any.) 305 if (index > 0 && b != b.parent.Recover) && len(b.Preds) == 0 { 306 s.warnf("unreachable block") 307 if b.Instrs == nil { 308 // Since this block is about to be pruned, 309 // tolerating transient problems in it 310 // simplifies other optimizations. 311 return 312 } 313 } 314 315 // Check predecessor and successor relations are dual, 316 // and that all blocks in CFG belong to same function. 317 for _, a := range b.Preds { 318 found := slices.Contains(a.Succs, b) 319 if !found { 320 s.errorf("expected successor edge in predecessor %s; found only: %s", a, a.Succs) 321 } 322 if a.parent != s.fn { 323 s.errorf("predecessor %s belongs to different function %s", a, a.parent) 324 } 325 } 326 for _, c := range b.Succs { 327 found := slices.Contains(c.Preds, b) 328 if !found { 329 s.errorf("expected predecessor edge in successor %s; found only: %s", c, c.Preds) 330 } 331 if c.parent != s.fn { 332 s.errorf("successor %s belongs to different function %s", c, c.parent) 333 } 334 } 335 336 // Check each instruction is sane. 337 n := len(b.Instrs) 338 if n == 0 { 339 s.errorf("basic block contains no instructions") 340 } 341 var rands [10]*Value // reuse storage 342 for j, instr := range b.Instrs { 343 if instr == nil { 344 s.errorf("nil instruction at index %d", j) 345 continue 346 } 347 if b2 := instr.Block(); b2 == nil { 348 s.errorf("nil Block() for instruction at index %d", j) 349 continue 350 } else if b2 != b { 351 s.errorf("wrong Block() (%s) for instruction at index %d ", b2, j) 352 continue 353 } 354 if j < n-1 { 355 s.checkInstr(j, instr) 356 } else { 357 s.checkFinalInstr(instr) 358 } 359 360 // Check Instruction.Operands. 361 operands: 362 for i, op := range instr.Operands(rands[:0]) { 363 if op == nil { 364 s.errorf("nil operand pointer %d of %s", i, instr) 365 continue 366 } 367 val := *op 368 if val == nil { 369 continue // a nil operand is ok 370 } 371 372 // Check that "untyped" types only appear on constant operands. 373 if _, ok := (*op).(*Const); !ok { 374 if basic, ok := (*op).Type().Underlying().(*types.Basic); ok { 375 if basic.Info()&types.IsUntyped != 0 { 376 s.errorf("operand #%d of %s is untyped: %s", i, instr, basic) 377 } 378 } 379 } 380 381 // Check that Operands that are also Instructions belong to same function. 382 // TODO(adonovan): also check their block dominates block b. 383 if val, ok := val.(Instruction); ok { 384 if val.Block() == nil { 385 s.errorf("operand %d of %s is an instruction (%s) that belongs to no block", i, instr, val) 386 } else if val.Parent() != s.fn { 387 s.errorf("operand %d of %s is an instruction (%s) from function %s", i, instr, val, val.Parent()) 388 } 389 } 390 391 // Check that each function-local operand of 392 // instr refers back to instr. (NB: quadratic) 393 switch val := val.(type) { 394 case *Const, *Global, *Builtin: 395 continue // not local 396 case *Function: 397 if val.parent == nil { 398 continue // only anon functions are local 399 } 400 } 401 402 // TODO(adonovan): check val.Parent() != nil <=> val.Referrers() is defined. 403 404 if refs := val.Referrers(); refs != nil { 405 for _, ref := range *refs { 406 if ref == instr { 407 continue operands 408 } 409 } 410 s.errorf("operand %d of %s (%s) does not refer to us", i, instr, val) 411 } else { 412 s.errorf("operand %d of %s (%s) has no referrers", i, instr, val) 413 } 414 } 415 } 416 } 417 418 func (s *sanity) checkReferrerList(v Value) { 419 refs := v.Referrers() 420 if refs == nil { 421 s.errorf("%s has missing referrer list", v.Name()) 422 return 423 } 424 for i, ref := range *refs { 425 if _, ok := s.instrs[ref]; !ok { 426 s.errorf("%s.Referrers()[%d] = %s is not an instruction belonging to this function", v.Name(), i, ref) 427 } 428 } 429 } 430 431 func (s *sanity) checkFunctionParams() { 432 signature := s.fn.Signature 433 params := s.fn.Params 434 435 // startSigParams is the start of signature.Params() within params. 436 startSigParams := 0 437 if signature.Recv() != nil { 438 startSigParams = 1 439 } 440 441 if startSigParams+signature.Params().Len() != len(params) { 442 s.errorf("function has %d parameters in signature but has %d after building", 443 startSigParams+signature.Params().Len(), len(params)) 444 return 445 } 446 447 for i, param := range params { 448 var sigType types.Type 449 si := i - startSigParams 450 if si < 0 { 451 sigType = signature.Recv().Type() 452 } else { 453 sigType = signature.Params().At(si).Type() 454 } 455 456 if !types.Identical(sigType, param.Type()) { 457 s.errorf("expect type %s in signature but got type %s in param %d", sigType, param.Type(), i) 458 } 459 } 460 } 461 462 // checkTransientFields checks whether all transient fields of Function are cleared. 463 func (s *sanity) checkTransientFields() { 464 fn := s.fn 465 if fn.build != nil { 466 s.errorf("function transient field 'build' is not nil") 467 } 468 if fn.currentBlock != nil { 469 s.errorf("function transient field 'currentBlock' is not nil") 470 } 471 if fn.vars != nil { 472 s.errorf("function transient field 'vars' is not nil") 473 } 474 if fn.results != nil { 475 s.errorf("function transient field 'results' is not nil") 476 } 477 if fn.returnVars != nil { 478 s.errorf("function transient field 'returnVars' is not nil") 479 } 480 if fn.targets != nil { 481 s.errorf("function transient field 'targets' is not nil") 482 } 483 if fn.lblocks != nil { 484 s.errorf("function transient field 'lblocks' is not nil") 485 } 486 if fn.subst != nil { 487 s.errorf("function transient field 'subst' is not nil") 488 } 489 if fn.jump != nil { 490 s.errorf("function transient field 'jump' is not nil") 491 } 492 if fn.deferstack != nil { 493 s.errorf("function transient field 'deferstack' is not nil") 494 } 495 if fn.source != nil { 496 s.errorf("function transient field 'source' is not nil") 497 } 498 if fn.exits != nil { 499 s.errorf("function transient field 'exits' is not nil") 500 } 501 if fn.uniq != 0 { 502 s.errorf("function transient field 'uniq' is not zero") 503 } 504 } 505 506 func (s *sanity) checkFunction(fn *Function) bool { 507 s.fn = fn 508 s.checkFunctionParams() 509 s.checkTransientFields() 510 511 // TODO(taking): Sanity check origin, typeparams, and typeargs. 512 if fn.Prog == nil { 513 s.errorf("nil Prog") 514 } 515 516 var buf bytes.Buffer 517 _ = fn.String() // must not crash 518 _ = fn.RelString(fn.relPkg()) // must not crash 519 WriteFunction(&buf, fn) // must not crash 520 521 // All functions have a package, except delegates (which are 522 // shared across packages, or duplicated as weak symbols in a 523 // separate-compilation model), and error.Error. 524 if fn.Pkg == nil { 525 if strings.HasPrefix(fn.Synthetic, "from type information (on demand)") || 526 strings.HasPrefix(fn.Synthetic, "wrapper ") || 527 strings.HasPrefix(fn.Synthetic, "bound ") || 528 strings.HasPrefix(fn.Synthetic, "thunk ") || 529 strings.HasSuffix(fn.name, "Error") || 530 strings.HasPrefix(fn.Synthetic, "instance ") || 531 strings.HasPrefix(fn.Synthetic, "instantiation ") || 532 fn.parent != nil && fn.parent.hasTypeArgs() /* anon fun in instance */ { 533 // ok 534 } else { 535 s.errorf("nil Pkg") 536 } 537 } 538 if src, syn := fn.Synthetic == "", fn.Syntax() != nil; src != syn { 539 if fn.hasTypeArgs() && fn.Prog.mode&InstantiateGenerics != 0 { 540 // ok (instantiation with InstantiateGenerics on) 541 } else if fn.hasTypeArgs() && fn.topLevelOrigin != nil { 542 // ok (we always have the syntax set for instantiation) 543 } else if _, rng := fn.syntax.(*ast.RangeStmt); rng && fn.Synthetic == "range-over-func yield" { 544 // ok (range-func-yields are both synthetic and keep syntax) 545 } else { 546 s.errorf("got fromSource=%t, hasSyntax=%t; want same values", src, syn) 547 } 548 } 549 550 // Build the set of valid referrers. 551 s.instrs = make(map[Instruction]unit) 552 553 // instrs are the instructions that are present in the function. 554 for instr := range fn.instrs() { 555 s.instrs[instr] = unit{} 556 } 557 558 // Check all Locals allocations appear in the function instruction. 559 for i, l := range fn.Locals { 560 if _, present := s.instrs[l]; !present { 561 s.warnf("function doesn't contain Local alloc %s", l.Name()) 562 } 563 564 if l.Parent() != fn { 565 s.errorf("Local %s at index %d has wrong parent", l.Name(), i) 566 } 567 if l.Heap { 568 s.errorf("Local %s at index %d has Heap flag set", l.Name(), i) 569 } 570 } 571 for i, p := range fn.Params { 572 if p.Parent() != fn { 573 s.errorf("Param %s at index %d has wrong parent", p.Name(), i) 574 } 575 // Check common suffix of Signature and Params match type. 576 if sig := fn.Signature; sig != nil { 577 j := i - len(fn.Params) + sig.Params().Len() // index within sig.Params 578 if j < 0 { 579 continue 580 } 581 if !types.Identical(p.Type(), sig.Params().At(j).Type()) { 582 s.errorf("Param %s at index %d has wrong type (%s, versus %s in Signature)", p.Name(), i, p.Type(), sig.Params().At(j).Type()) 583 584 } 585 } 586 s.checkReferrerList(p) 587 } 588 for i, fv := range fn.FreeVars { 589 if fv.Parent() != fn { 590 s.errorf("FreeVar %s at index %d has wrong parent", fv.Name(), i) 591 } 592 s.checkReferrerList(fv) 593 } 594 595 if fn.Blocks != nil && len(fn.Blocks) == 0 { 596 // Function _had_ blocks (so it's not external) but 597 // they were "optimized" away, even the entry block. 598 s.errorf("Blocks slice is non-nil but empty") 599 } 600 for i, b := range fn.Blocks { 601 if b == nil { 602 s.warnf("nil *BasicBlock at f.Blocks[%d]", i) 603 continue 604 } 605 s.checkBlock(b, i) 606 } 607 if fn.Recover != nil && fn.Blocks[fn.Recover.Index] != fn.Recover { 608 s.errorf("Recover block is not in Blocks slice") 609 } 610 611 s.block = nil 612 for i, anon := range fn.AnonFuncs { 613 if anon.Parent() != fn { 614 s.errorf("AnonFuncs[%d]=%s but %s.Parent()=%s", i, anon, anon, anon.Parent()) 615 } 616 if i != int(anon.anonIdx) { 617 s.errorf("AnonFuncs[%d]=%s but %s.anonIdx=%d", i, anon, anon, anon.anonIdx) 618 } 619 } 620 s.fn = nil 621 return !s.insane 622 } 623 624 // sanityCheckPackage checks invariants of packages upon creation. 625 // It does not require that the package is built. 626 // Unlike sanityCheck (for functions), it just panics at the first error. 627 func sanityCheckPackage(pkg *Package) { 628 if pkg.Pkg == nil { 629 panic(fmt.Sprintf("Package %s has no Object", pkg)) 630 } 631 if pkg.info != nil { 632 panic(fmt.Sprintf("package %s field 'info' is not cleared", pkg)) 633 } 634 if pkg.files != nil { 635 panic(fmt.Sprintf("package %s field 'files' is not cleared", pkg)) 636 } 637 if pkg.created != nil { 638 panic(fmt.Sprintf("package %s field 'created' is not cleared", pkg)) 639 } 640 if pkg.initVersion != nil { 641 panic(fmt.Sprintf("package %s field 'initVersion' is not cleared", pkg)) 642 } 643 644 _ = pkg.String() // must not crash 645 646 for name, mem := range pkg.Members { 647 if name != mem.Name() { 648 panic(fmt.Sprintf("%s: %T.Name() = %s, want %s", 649 pkg.Pkg.Path(), mem, mem.Name(), name)) 650 } 651 obj := mem.Object() 652 if obj == nil { 653 // This check is sound because fields 654 // {Global,Function}.object have type 655 // types.Object. (If they were declared as 656 // *types.{Var,Func}, we'd have a non-empty 657 // interface containing a nil pointer.) 658 659 continue // not all members have typechecker objects 660 } 661 if obj.Name() != name { 662 if obj.Name() == "init" && strings.HasPrefix(mem.Name(), "init#") { 663 // Ok. The name of a declared init function varies between 664 // its types.Func ("init") and its ssa.Function ("init#%d"). 665 } else { 666 panic(fmt.Sprintf("%s: %T.Object().Name() = %s, want %s", 667 pkg.Pkg.Path(), mem, obj.Name(), name)) 668 } 669 } 670 if obj.Pos() != mem.Pos() { 671 panic(fmt.Sprintf("%s Pos=%d obj.Pos=%d", mem, mem.Pos(), obj.Pos())) 672 } 673 } 674 }