 bf27481efd
			
		
	
	
		bf27481efd
		
			
		
	
	
	
	
		
			
			full diff: https://github.com/dominikh/go-tools/compare/2019.2.3...2020.1.3 Also updates tests to accomodate updated rules: --- FAIL: TestSourcesFromTestdataWithIssuesDir/staticcheck.go (0.43s) linters_test.go:137: [run --disable-all --print-issued-lines=false --print-linter-name=false --out-format=line-number --max-same-issues=10 -Estaticcheck --no-config testdata/staticcheck.go] linters_test.go:33: Error Trace: linters_test.go:33 linters_test.go:138 linters_test.go:53 Error: Received unexpected error: staticcheck.go:11: no match for `self-assignment of x to x` vs ["SA4006: this value of `x` is never used"] in: staticcheck.go:11:2: SA4006: this value of `x` is never used unmatched errors staticcheck.go:11:2: SA4006: this value of `x` is never used Test: TestSourcesFromTestdataWithIssuesDir/staticcheck.go Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
		
			
				
	
	
		
			272 lines
		
	
	
		
			7.0 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			272 lines
		
	
	
		
			7.0 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package ir
 | |
| 
 | |
| import (
 | |
| 	"go/types"
 | |
| )
 | |
| 
 | |
| func (b *builder) buildExits(fn *Function) {
 | |
| 	if obj := fn.Object(); obj != nil {
 | |
| 		switch obj.Pkg().Path() {
 | |
| 		case "runtime":
 | |
| 			switch obj.Name() {
 | |
| 			case "exit":
 | |
| 				fn.WillExit = true
 | |
| 				return
 | |
| 			case "throw":
 | |
| 				fn.WillExit = true
 | |
| 				return
 | |
| 			case "Goexit":
 | |
| 				fn.WillUnwind = true
 | |
| 				return
 | |
| 			}
 | |
| 		case "github.com/sirupsen/logrus":
 | |
| 			switch obj.(*types.Func).FullName() {
 | |
| 			case "(*github.com/sirupsen/logrus.Logger).Exit":
 | |
| 				// Technically, this method does not unconditionally exit
 | |
| 				// the process. It dynamically calls a function stored in
 | |
| 				// the logger. If the function is nil, it defaults to
 | |
| 				// os.Exit.
 | |
| 				//
 | |
| 				// The main intent of this method is to terminate the
 | |
| 				// process, and that's what the vast majority of people
 | |
| 				// will use it for. We'll happily accept some false
 | |
| 				// negatives to avoid a lot of false positives.
 | |
| 				fn.WillExit = true
 | |
| 				return
 | |
| 			case "(*github.com/sirupsen/logrus.Logger).Panic",
 | |
| 				"(*github.com/sirupsen/logrus.Logger).Panicf",
 | |
| 				"(*github.com/sirupsen/logrus.Logger).Panicln":
 | |
| 
 | |
| 				// These methods will always panic, but that's not
 | |
| 				// statically known from the code alone, because they
 | |
| 				// take a detour through the generic Log methods.
 | |
| 				fn.WillUnwind = true
 | |
| 				return
 | |
| 			case "(*github.com/sirupsen/logrus.Entry).Panicf",
 | |
| 				"(*github.com/sirupsen/logrus.Entry).Panicln":
 | |
| 
 | |
| 				// Entry.Panic has an explicit panic, but Panicf and
 | |
| 				// Panicln do not, relying fully on the generic Log
 | |
| 				// method.
 | |
| 				fn.WillUnwind = true
 | |
| 				return
 | |
| 			case "(*github.com/sirupsen/logrus.Logger).Log",
 | |
| 				"(*github.com/sirupsen/logrus.Logger).Logf",
 | |
| 				"(*github.com/sirupsen/logrus.Logger).Logln":
 | |
| 				// TODO(dh): we cannot handle these case. Whether they
 | |
| 				// exit or unwind depends on the level, which is set
 | |
| 				// via the first argument. We don't currently support
 | |
| 				// call-site-specific exit information.
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	buildDomTree(fn)
 | |
| 
 | |
| 	isRecoverCall := func(instr Instruction) bool {
 | |
| 		if instr, ok := instr.(*Call); ok {
 | |
| 			if builtin, ok := instr.Call.Value.(*Builtin); ok {
 | |
| 				if builtin.Name() == "recover" {
 | |
| 					return true
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 		return false
 | |
| 	}
 | |
| 
 | |
| 	// All panics branch to the exit block, which means that if every
 | |
| 	// possible path through the function panics, then all
 | |
| 	// predecessors of the exit block must panic.
 | |
| 	willPanic := true
 | |
| 	for _, pred := range fn.Exit.Preds {
 | |
| 		if _, ok := pred.Control().(*Panic); !ok {
 | |
| 			willPanic = false
 | |
| 		}
 | |
| 	}
 | |
| 	if willPanic {
 | |
| 		recovers := false
 | |
| 	recoverLoop:
 | |
| 		for _, u := range fn.Blocks {
 | |
| 			for _, instr := range u.Instrs {
 | |
| 				if instr, ok := instr.(*Defer); ok {
 | |
| 					call := instr.Call.StaticCallee()
 | |
| 					if call == nil {
 | |
| 						// not a static call, so we can't be sure the
 | |
| 						// deferred call isn't calling recover
 | |
| 						recovers = true
 | |
| 						break recoverLoop
 | |
| 					}
 | |
| 					if len(call.Blocks) == 0 {
 | |
| 						// external function, we don't know what's
 | |
| 						// happening inside it
 | |
| 						//
 | |
| 						// TODO(dh): this includes functions from
 | |
| 						// imported packages, due to how go/analysis
 | |
| 						// works. We could introduce another fact,
 | |
| 						// like we've done for exiting and unwinding,
 | |
| 						// but it doesn't seem worth it. Virtually all
 | |
| 						// uses of recover will be in closures.
 | |
| 						recovers = true
 | |
| 						break recoverLoop
 | |
| 					}
 | |
| 					for _, y := range call.Blocks {
 | |
| 						for _, instr2 := range y.Instrs {
 | |
| 							if isRecoverCall(instr2) {
 | |
| 								recovers = true
 | |
| 								break recoverLoop
 | |
| 							}
 | |
| 						}
 | |
| 					}
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 		if !recovers {
 | |
| 			fn.WillUnwind = true
 | |
| 			return
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	// TODO(dh): don't check that any specific call dominates the exit
 | |
| 	// block. instead, check that all calls combined cover every
 | |
| 	// possible path through the function.
 | |
| 	exits := NewBlockSet(len(fn.Blocks))
 | |
| 	unwinds := NewBlockSet(len(fn.Blocks))
 | |
| 	for _, u := range fn.Blocks {
 | |
| 		for _, instr := range u.Instrs {
 | |
| 			if instr, ok := instr.(CallInstruction); ok {
 | |
| 				switch instr.(type) {
 | |
| 				case *Defer, *Call:
 | |
| 				default:
 | |
| 					continue
 | |
| 				}
 | |
| 				if instr.Common().IsInvoke() {
 | |
| 					// give up
 | |
| 					return
 | |
| 				}
 | |
| 				var call *Function
 | |
| 				switch instr.Common().Value.(type) {
 | |
| 				case *Function, *MakeClosure:
 | |
| 					call = instr.Common().StaticCallee()
 | |
| 				case *Builtin:
 | |
| 					// the only builtins that affect control flow are
 | |
| 					// panic and recover, and we've already handled
 | |
| 					// those
 | |
| 					continue
 | |
| 				default:
 | |
| 					// dynamic dispatch
 | |
| 					return
 | |
| 				}
 | |
| 				// buildFunction is idempotent. if we're part of a
 | |
| 				// (mutually) recursive call chain, then buildFunction
 | |
| 				// will immediately return, and fn.WillExit will be false.
 | |
| 				if call.Package() == fn.Package() {
 | |
| 					b.buildFunction(call)
 | |
| 				}
 | |
| 				dom := u.Dominates(fn.Exit)
 | |
| 				if call.WillExit {
 | |
| 					if dom {
 | |
| 						fn.WillExit = true
 | |
| 						return
 | |
| 					}
 | |
| 					exits.Add(u)
 | |
| 				} else if call.WillUnwind {
 | |
| 					if dom {
 | |
| 						fn.WillUnwind = true
 | |
| 						return
 | |
| 					}
 | |
| 					unwinds.Add(u)
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	// depth-first search trying to find a path to the exit block that
 | |
| 	// doesn't cross any of the blacklisted blocks
 | |
| 	seen := NewBlockSet(len(fn.Blocks))
 | |
| 	var findPath func(root *BasicBlock, bl *BlockSet) bool
 | |
| 	findPath = func(root *BasicBlock, bl *BlockSet) bool {
 | |
| 		if root == fn.Exit {
 | |
| 			return true
 | |
| 		}
 | |
| 		if seen.Has(root) {
 | |
| 			return false
 | |
| 		}
 | |
| 		if bl.Has(root) {
 | |
| 			return false
 | |
| 		}
 | |
| 		seen.Add(root)
 | |
| 		for _, succ := range root.Succs {
 | |
| 			if findPath(succ, bl) {
 | |
| 				return true
 | |
| 			}
 | |
| 		}
 | |
| 		return false
 | |
| 	}
 | |
| 
 | |
| 	if exits.Num() > 0 {
 | |
| 		if !findPath(fn.Blocks[0], exits) {
 | |
| 			fn.WillExit = true
 | |
| 			return
 | |
| 		}
 | |
| 	}
 | |
| 	if unwinds.Num() > 0 {
 | |
| 		seen.Clear()
 | |
| 		if !findPath(fn.Blocks[0], unwinds) {
 | |
| 			fn.WillUnwind = true
 | |
| 			return
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func (b *builder) addUnreachables(fn *Function) {
 | |
| 	for _, bb := range fn.Blocks {
 | |
| 		for i, instr := range bb.Instrs {
 | |
| 			if instr, ok := instr.(*Call); ok {
 | |
| 				var call *Function
 | |
| 				switch v := instr.Common().Value.(type) {
 | |
| 				case *Function:
 | |
| 					call = v
 | |
| 				case *MakeClosure:
 | |
| 					call = v.Fn.(*Function)
 | |
| 				}
 | |
| 				if call == nil {
 | |
| 					continue
 | |
| 				}
 | |
| 				if call.Package() == fn.Package() {
 | |
| 					// make sure we have information on all functions in this package
 | |
| 					b.buildFunction(call)
 | |
| 				}
 | |
| 				if call.WillExit {
 | |
| 					// This call will cause the process to terminate.
 | |
| 					// Remove remaining instructions in the block and
 | |
| 					// replace any control flow with Unreachable.
 | |
| 					for _, succ := range bb.Succs {
 | |
| 						succ.removePred(bb)
 | |
| 					}
 | |
| 					bb.Succs = bb.Succs[:0]
 | |
| 
 | |
| 					bb.Instrs = bb.Instrs[:i+1]
 | |
| 					bb.emit(new(Unreachable), instr.Source())
 | |
| 					addEdge(bb, fn.Exit)
 | |
| 					break
 | |
| 				} else if call.WillUnwind {
 | |
| 					// This call will cause the goroutine to terminate
 | |
| 					// and defers to run (i.e. a panic or
 | |
| 					// runtime.Goexit). Remove remaining instructions
 | |
| 					// in the block and replace any control flow with
 | |
| 					// an unconditional jump to the exit block.
 | |
| 					for _, succ := range bb.Succs {
 | |
| 						succ.removePred(bb)
 | |
| 					}
 | |
| 					bb.Succs = bb.Succs[:0]
 | |
| 
 | |
| 					bb.Instrs = bb.Instrs[:i+1]
 | |
| 					bb.emit(new(Jump), instr.Source())
 | |
| 					addEdge(bb, fn.Exit)
 | |
| 					break
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| }
 |