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