gitee.com/wgliang/goreporter@v0.0.0-20180902115603-df1b20f7c5d0/linters/simpler/ssa/ssautil/switch.go (about)

     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  // +build go1.5
     6  
     7  package ssautil
     8  
     9  // This file implements discovery of switch and type-switch constructs
    10  // from low-level control flow.
    11  //
    12  // Many techniques exist for compiling a high-level switch with
    13  // constant cases to efficient machine code.  The optimal choice will
    14  // depend on the data type, the specific case values, the code in the
    15  // body of each case, and the hardware.
    16  // Some examples:
    17  // - a lookup table (for a switch that maps constants to constants)
    18  // - a computed goto
    19  // - a binary tree
    20  // - a perfect hash
    21  // - a two-level switch (to partition constant strings by their first byte).
    22  
    23  import (
    24  	"bytes"
    25  	"fmt"
    26  	"go/token"
    27  	"go/types"
    28  
    29  	"github.com/360EntSecGroup-Skylar/goreporter/linters/simpler/ssa"
    30  )
    31  
    32  // A ConstCase represents a single constant comparison.
    33  // It is part of a Switch.
    34  type ConstCase struct {
    35  	Block *ssa.BasicBlock // block performing the comparison
    36  	Body  *ssa.BasicBlock // body of the case
    37  	Value *ssa.Const      // case comparand
    38  }
    39  
    40  // A TypeCase represents a single type assertion.
    41  // It is part of a Switch.
    42  type TypeCase struct {
    43  	Block   *ssa.BasicBlock // block performing the type assert
    44  	Body    *ssa.BasicBlock // body of the case
    45  	Type    types.Type      // case type
    46  	Binding ssa.Value       // value bound by this case
    47  }
    48  
    49  // A Switch is a logical high-level control flow operation
    50  // (a multiway branch) discovered by analysis of a CFG containing
    51  // only if/else chains.  It is not part of the ssa.Instruction set.
    52  //
    53  // One of ConstCases and TypeCases has length >= 2;
    54  // the other is nil.
    55  //
    56  // In a value switch, the list of cases may contain duplicate constants.
    57  // A type switch may contain duplicate types, or types assignable
    58  // to an interface type also in the list.
    59  // TODO(adonovan): eliminate such duplicates.
    60  //
    61  type Switch struct {
    62  	Start      *ssa.BasicBlock // block containing start of if/else chain
    63  	X          ssa.Value       // the switch operand
    64  	ConstCases []ConstCase     // ordered list of constant comparisons
    65  	TypeCases  []TypeCase      // ordered list of type assertions
    66  	Default    *ssa.BasicBlock // successor if all comparisons fail
    67  }
    68  
    69  func (sw *Switch) String() string {
    70  	// We represent each block by the String() of its
    71  	// first Instruction, e.g. "print(42:int)".
    72  	var buf bytes.Buffer
    73  	if sw.ConstCases != nil {
    74  		fmt.Fprintf(&buf, "switch %s {\n", sw.X.Name())
    75  		for _, c := range sw.ConstCases {
    76  			fmt.Fprintf(&buf, "case %s: %s\n", c.Value, c.Body.Instrs[0])
    77  		}
    78  	} else {
    79  		fmt.Fprintf(&buf, "switch %s.(type) {\n", sw.X.Name())
    80  		for _, c := range sw.TypeCases {
    81  			fmt.Fprintf(&buf, "case %s %s: %s\n",
    82  				c.Binding.Name(), c.Type, c.Body.Instrs[0])
    83  		}
    84  	}
    85  	if sw.Default != nil {
    86  		fmt.Fprintf(&buf, "default: %s\n", sw.Default.Instrs[0])
    87  	}
    88  	fmt.Fprintf(&buf, "}")
    89  	return buf.String()
    90  }
    91  
    92  // Switches examines the control-flow graph of fn and returns the
    93  // set of inferred value and type switches.  A value switch tests an
    94  // ssa.Value for equality against two or more compile-time constant
    95  // values.  Switches involving link-time constants (addresses) are
    96  // ignored.  A type switch type-asserts an ssa.Value against two or
    97  // more types.
    98  //
    99  // The switches are returned in dominance order.
   100  //
   101  // The resulting switches do not necessarily correspond to uses of the
   102  // 'switch' keyword in the source: for example, a single source-level
   103  // switch statement with non-constant cases may result in zero, one or
   104  // many Switches, one per plural sequence of constant cases.
   105  // Switches may even be inferred from if/else- or goto-based control flow.
   106  // (In general, the control flow constructs of the source program
   107  // cannot be faithfully reproduced from the SSA representation.)
   108  //
   109  func Switches(fn *ssa.Function) []Switch {
   110  	// Traverse the CFG in dominance order, so we don't
   111  	// enter an if/else-chain in the middle.
   112  	var switches []Switch
   113  	seen := make(map[*ssa.BasicBlock]bool) // TODO(adonovan): opt: use ssa.blockSet
   114  	for _, b := range fn.DomPreorder() {
   115  		if x, k := isComparisonBlock(b); x != nil {
   116  			// Block b starts a switch.
   117  			sw := Switch{Start: b, X: x}
   118  			valueSwitch(&sw, k, seen)
   119  			if len(sw.ConstCases) > 1 {
   120  				switches = append(switches, sw)
   121  			}
   122  		}
   123  
   124  		if y, x, T := isTypeAssertBlock(b); y != nil {
   125  			// Block b starts a type switch.
   126  			sw := Switch{Start: b, X: x}
   127  			typeSwitch(&sw, y, T, seen)
   128  			if len(sw.TypeCases) > 1 {
   129  				switches = append(switches, sw)
   130  			}
   131  		}
   132  	}
   133  	return switches
   134  }
   135  
   136  func valueSwitch(sw *Switch, k *ssa.Const, seen map[*ssa.BasicBlock]bool) {
   137  	b := sw.Start
   138  	x := sw.X
   139  	for x == sw.X {
   140  		if seen[b] {
   141  			break
   142  		}
   143  		seen[b] = true
   144  
   145  		sw.ConstCases = append(sw.ConstCases, ConstCase{
   146  			Block: b,
   147  			Body:  b.Succs[0],
   148  			Value: k,
   149  		})
   150  		b = b.Succs[1]
   151  		if len(b.Instrs) > 2 {
   152  			// Block b contains not just 'if x == k',
   153  			// so it may have side effects that
   154  			// make it unsafe to elide.
   155  			break
   156  		}
   157  		if len(b.Preds) != 1 {
   158  			// Block b has multiple predecessors,
   159  			// so it cannot be treated as a case.
   160  			break
   161  		}
   162  		x, k = isComparisonBlock(b)
   163  	}
   164  	sw.Default = b
   165  }
   166  
   167  func typeSwitch(sw *Switch, y ssa.Value, T types.Type, seen map[*ssa.BasicBlock]bool) {
   168  	b := sw.Start
   169  	x := sw.X
   170  	for x == sw.X {
   171  		if seen[b] {
   172  			break
   173  		}
   174  		seen[b] = true
   175  
   176  		sw.TypeCases = append(sw.TypeCases, TypeCase{
   177  			Block:   b,
   178  			Body:    b.Succs[0],
   179  			Type:    T,
   180  			Binding: y,
   181  		})
   182  		b = b.Succs[1]
   183  		if len(b.Instrs) > 4 {
   184  			// Block b contains not just
   185  			//  {TypeAssert; Extract #0; Extract #1; If}
   186  			// so it may have side effects that
   187  			// make it unsafe to elide.
   188  			break
   189  		}
   190  		if len(b.Preds) != 1 {
   191  			// Block b has multiple predecessors,
   192  			// so it cannot be treated as a case.
   193  			break
   194  		}
   195  		y, x, T = isTypeAssertBlock(b)
   196  	}
   197  	sw.Default = b
   198  }
   199  
   200  // isComparisonBlock returns the operands (v, k) if a block ends with
   201  // a comparison v==k, where k is a compile-time constant.
   202  //
   203  func isComparisonBlock(b *ssa.BasicBlock) (v ssa.Value, k *ssa.Const) {
   204  	if n := len(b.Instrs); n >= 2 {
   205  		if i, ok := b.Instrs[n-1].(*ssa.If); ok {
   206  			if binop, ok := i.Cond.(*ssa.BinOp); ok && binop.Block() == b && binop.Op == token.EQL {
   207  				if k, ok := binop.Y.(*ssa.Const); ok {
   208  					return binop.X, k
   209  				}
   210  				if k, ok := binop.X.(*ssa.Const); ok {
   211  					return binop.Y, k
   212  				}
   213  			}
   214  		}
   215  	}
   216  	return
   217  }
   218  
   219  // isTypeAssertBlock returns the operands (y, x, T) if a block ends with
   220  // a type assertion "if y, ok := x.(T); ok {".
   221  //
   222  func isTypeAssertBlock(b *ssa.BasicBlock) (y, x ssa.Value, T types.Type) {
   223  	if n := len(b.Instrs); n >= 4 {
   224  		if i, ok := b.Instrs[n-1].(*ssa.If); ok {
   225  			if ext1, ok := i.Cond.(*ssa.Extract); ok && ext1.Block() == b && ext1.Index == 1 {
   226  				if ta, ok := ext1.Tuple.(*ssa.TypeAssert); ok && ta.Block() == b {
   227  					// hack: relies upon instruction ordering.
   228  					if ext0, ok := b.Instrs[n-3].(*ssa.Extract); ok {
   229  						return ext0, ta.X, ta.AssertedType
   230  					}
   231  				}
   232  			}
   233  		}
   234  	}
   235  	return
   236  }