github.com/aykevl/tinygo@v0.5.0/compiler/asserts.go (about)

     1  package compiler
     2  
     3  // This file implements functions that do certain safety checks that are
     4  // required by the Go programming language.
     5  
     6  import (
     7  	"go/types"
     8  
     9  	"tinygo.org/x/go-llvm"
    10  )
    11  
    12  // emitLookupBoundsCheck emits a bounds check before doing a lookup into a
    13  // slice. This is required by the Go language spec: an index out of bounds must
    14  // cause a panic.
    15  func (c *Compiler) emitLookupBoundsCheck(frame *Frame, arrayLen, index llvm.Value, indexType types.Type) {
    16  	if frame.fn.IsNoBounds() {
    17  		// The //go:nobounds pragma was added to the function to avoid bounds
    18  		// checking.
    19  		return
    20  	}
    21  
    22  	if index.Type().IntTypeWidth() < arrayLen.Type().IntTypeWidth() {
    23  		// Sometimes, the index can be e.g. an uint8 or int8, and we have to
    24  		// correctly extend that type.
    25  		if indexType.(*types.Basic).Info()&types.IsUnsigned == 0 {
    26  			index = c.builder.CreateZExt(index, arrayLen.Type(), "")
    27  		} else {
    28  			index = c.builder.CreateSExt(index, arrayLen.Type(), "")
    29  		}
    30  	} else if index.Type().IntTypeWidth() > arrayLen.Type().IntTypeWidth() {
    31  		// The index is bigger than the array length type, so extend it.
    32  		arrayLen = c.builder.CreateZExt(arrayLen, index.Type(), "")
    33  	}
    34  
    35  	faultBlock := c.ctx.AddBasicBlock(frame.fn.LLVMFn, "lookup.outofbounds")
    36  	nextBlock := c.ctx.AddBasicBlock(frame.fn.LLVMFn, "lookup.next")
    37  	frame.blockExits[frame.currentBlock] = nextBlock // adjust outgoing block for phi nodes
    38  
    39  	// Now do the bounds check: index >= arrayLen
    40  	outOfBounds := c.builder.CreateICmp(llvm.IntUGE, index, arrayLen, "")
    41  	c.builder.CreateCondBr(outOfBounds, faultBlock, nextBlock)
    42  
    43  	// Fail: this is a nil pointer, exit with a panic.
    44  	c.builder.SetInsertPointAtEnd(faultBlock)
    45  	c.createRuntimeCall("lookuppanic", nil, "")
    46  	c.builder.CreateUnreachable()
    47  
    48  	// Ok: this is a valid pointer.
    49  	c.builder.SetInsertPointAtEnd(nextBlock)
    50  }
    51  
    52  // emitSliceBoundsCheck emits a bounds check before a slicing operation to make
    53  // sure it is within bounds.
    54  //
    55  // This function is both used for slicing a slice (low and high have their
    56  // normal meaning) and for creating a new slice, where 'capacity' means the
    57  // biggest possible slice capacity, 'low' means len and 'high' means cap. The
    58  // logic is the same in both cases.
    59  func (c *Compiler) emitSliceBoundsCheck(frame *Frame, capacity, low, high llvm.Value, lowType, highType *types.Basic) {
    60  	if frame.fn.IsNoBounds() {
    61  		// The //go:nobounds pragma was added to the function to avoid bounds
    62  		// checking.
    63  		return
    64  	}
    65  
    66  	// Extend the capacity integer to be at least as wide as low and high.
    67  	capacityType := capacity.Type()
    68  	if low.Type().IntTypeWidth() > capacityType.IntTypeWidth() {
    69  		capacityType = low.Type()
    70  	}
    71  	if high.Type().IntTypeWidth() > capacityType.IntTypeWidth() {
    72  		capacityType = high.Type()
    73  	}
    74  	if capacityType != capacity.Type() {
    75  		capacity = c.builder.CreateZExt(capacity, capacityType, "")
    76  	}
    77  
    78  	// Extend low and high to be the same size as capacity.
    79  	if low.Type().IntTypeWidth() < capacityType.IntTypeWidth() {
    80  		if lowType.Info()&types.IsUnsigned != 0 {
    81  			low = c.builder.CreateZExt(low, capacityType, "")
    82  		} else {
    83  			low = c.builder.CreateSExt(low, capacityType, "")
    84  		}
    85  	}
    86  	if high.Type().IntTypeWidth() < capacityType.IntTypeWidth() {
    87  		if highType.Info()&types.IsUnsigned != 0 {
    88  			high = c.builder.CreateZExt(high, capacityType, "")
    89  		} else {
    90  			high = c.builder.CreateSExt(high, capacityType, "")
    91  		}
    92  	}
    93  
    94  	faultBlock := c.ctx.AddBasicBlock(frame.fn.LLVMFn, "slice.outofbounds")
    95  	nextBlock := c.ctx.AddBasicBlock(frame.fn.LLVMFn, "slice.next")
    96  	frame.blockExits[frame.currentBlock] = nextBlock // adjust outgoing block for phi nodes
    97  
    98  	// Now do the bounds check: low > high || high > capacity
    99  	outOfBounds1 := c.builder.CreateICmp(llvm.IntUGT, low, high, "slice.lowhigh")
   100  	outOfBounds2 := c.builder.CreateICmp(llvm.IntUGT, high, capacity, "slice.highcap")
   101  	outOfBounds := c.builder.CreateOr(outOfBounds1, outOfBounds2, "slice.outofbounds")
   102  	c.builder.CreateCondBr(outOfBounds, faultBlock, nextBlock)
   103  
   104  	// Fail: this is a nil pointer, exit with a panic.
   105  	c.builder.SetInsertPointAtEnd(faultBlock)
   106  	c.createRuntimeCall("slicepanic", nil, "")
   107  	c.builder.CreateUnreachable()
   108  
   109  	// Ok: this is a valid pointer.
   110  	c.builder.SetInsertPointAtEnd(nextBlock)
   111  }
   112  
   113  // emitNilCheck checks whether the given pointer is nil, and panics if it is. It
   114  // has no effect in well-behaved programs, but makes sure no uncaught nil
   115  // pointer dereferences exist in valid Go code.
   116  func (c *Compiler) emitNilCheck(frame *Frame, ptr llvm.Value, blockPrefix string) {
   117  	// Check whether this is a nil pointer.
   118  	faultBlock := c.ctx.AddBasicBlock(frame.fn.LLVMFn, blockPrefix+".nil")
   119  	nextBlock := c.ctx.AddBasicBlock(frame.fn.LLVMFn, blockPrefix+".next")
   120  	frame.blockExits[frame.currentBlock] = nextBlock // adjust outgoing block for phi nodes
   121  
   122  	// Compare against nil.
   123  	var isnil llvm.Value
   124  	if ptr.Type().PointerAddressSpace() == 0 {
   125  		// Do the nil check using the isnil builtin, which marks the parameter
   126  		// as nocapture.
   127  		// The reason it has to go through a builtin, is that a regular icmp
   128  		// instruction may capture the pointer in LLVM semantics, see
   129  		// https://reviews.llvm.org/D60047 for details. Pointer capturing
   130  		// unfortunately breaks escape analysis, so we use this trick to let the
   131  		// functionattr pass know that this pointer doesn't really escape.
   132  		ptr = c.builder.CreateBitCast(ptr, c.i8ptrType, "")
   133  		isnil = c.createRuntimeCall("isnil", []llvm.Value{ptr}, "")
   134  	} else {
   135  		// Do the nil check using a regular icmp. This can happen with function
   136  		// pointers on AVR, which don't benefit from escape analysis anyway.
   137  		nilptr := llvm.ConstPointerNull(ptr.Type())
   138  		isnil = c.builder.CreateICmp(llvm.IntEQ, ptr, nilptr, "")
   139  	}
   140  	c.builder.CreateCondBr(isnil, faultBlock, nextBlock)
   141  
   142  	// Fail: this is a nil pointer, exit with a panic.
   143  	c.builder.SetInsertPointAtEnd(faultBlock)
   144  	c.createRuntimeCall("nilpanic", nil, "")
   145  	c.builder.CreateUnreachable()
   146  
   147  	// Ok: this is a valid pointer.
   148  	c.builder.SetInsertPointAtEnd(nextBlock)
   149  }