github.com/tinygo-org/tinygo@v0.31.3-0.20240404173401-90b0bf646c27/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  	"fmt"
     8  	"go/token"
     9  	"go/types"
    10  
    11  	"golang.org/x/tools/go/ssa"
    12  	"tinygo.org/x/go-llvm"
    13  )
    14  
    15  // createLookupBoundsCheck emits a bounds check before doing a lookup into a
    16  // slice. This is required by the Go language spec: an index out of bounds must
    17  // cause a panic.
    18  // The caller should make sure that index is at least as big as arrayLen.
    19  func (b *builder) createLookupBoundsCheck(arrayLen, index llvm.Value) {
    20  	if b.info.nobounds {
    21  		// The //go:nobounds pragma was added to the function to avoid bounds
    22  		// checking.
    23  		return
    24  	}
    25  
    26  	// Extend arrayLen if it's too small.
    27  	if index.Type().IntTypeWidth() > arrayLen.Type().IntTypeWidth() {
    28  		// The index is bigger than the array length type, so extend it.
    29  		arrayLen = b.CreateZExt(arrayLen, index.Type(), "")
    30  	}
    31  
    32  	// Now do the bounds check: index >= arrayLen
    33  	outOfBounds := b.CreateICmp(llvm.IntUGE, index, arrayLen, "")
    34  	b.createRuntimeAssert(outOfBounds, "lookup", "lookupPanic")
    35  }
    36  
    37  // createSliceBoundsCheck emits a bounds check before a slicing operation to make
    38  // sure it is within bounds.
    39  //
    40  // This function is both used for slicing a slice (low and high have their
    41  // normal meaning) and for creating a new slice, where 'capacity' means the
    42  // biggest possible slice capacity, 'low' means len and 'high' means cap. The
    43  // logic is the same in both cases.
    44  func (b *builder) createSliceBoundsCheck(capacity, low, high, max llvm.Value, lowType, highType, maxType *types.Basic) {
    45  	if b.info.nobounds {
    46  		// The //go:nobounds pragma was added to the function to avoid bounds
    47  		// checking.
    48  		return
    49  	}
    50  
    51  	// Extend the capacity integer to be at least as wide as low and high.
    52  	capacityType := capacity.Type()
    53  	if low.Type().IntTypeWidth() > capacityType.IntTypeWidth() {
    54  		capacityType = low.Type()
    55  	}
    56  	if high.Type().IntTypeWidth() > capacityType.IntTypeWidth() {
    57  		capacityType = high.Type()
    58  	}
    59  	if max.Type().IntTypeWidth() > capacityType.IntTypeWidth() {
    60  		capacityType = max.Type()
    61  	}
    62  	if capacityType != capacity.Type() {
    63  		capacity = b.CreateZExt(capacity, capacityType, "")
    64  	}
    65  
    66  	// Extend low and high to be the same size as capacity.
    67  	low = b.extendInteger(low, lowType, capacityType)
    68  	high = b.extendInteger(high, highType, capacityType)
    69  	max = b.extendInteger(max, maxType, capacityType)
    70  
    71  	// Now do the bounds check: low > high || high > capacity
    72  	outOfBounds1 := b.CreateICmp(llvm.IntUGT, low, high, "slice.lowhigh")
    73  	outOfBounds2 := b.CreateICmp(llvm.IntUGT, high, max, "slice.highmax")
    74  	outOfBounds3 := b.CreateICmp(llvm.IntUGT, max, capacity, "slice.maxcap")
    75  	outOfBounds := b.CreateOr(outOfBounds1, outOfBounds2, "slice.lowmax")
    76  	outOfBounds = b.CreateOr(outOfBounds, outOfBounds3, "slice.lowcap")
    77  	b.createRuntimeAssert(outOfBounds, "slice", "slicePanic")
    78  }
    79  
    80  // createSliceToArrayPointerCheck adds a check for slice-to-array pointer
    81  // conversions. This conversion was added in Go 1.17. For details, see:
    82  // https://tip.golang.org/ref/spec#Conversions_from_slice_to_array_pointer
    83  func (b *builder) createSliceToArrayPointerCheck(sliceLen llvm.Value, arrayLen int64) {
    84  	// From the spec:
    85  	// > If the length of the slice is less than the length of the array, a
    86  	// > run-time panic occurs.
    87  	arrayLenValue := llvm.ConstInt(b.uintptrType, uint64(arrayLen), false)
    88  	isLess := b.CreateICmp(llvm.IntULT, sliceLen, arrayLenValue, "")
    89  	b.createRuntimeAssert(isLess, "slicetoarray", "sliceToArrayPointerPanic")
    90  }
    91  
    92  // createUnsafeSliceStringCheck inserts a runtime check used for unsafe.Slice
    93  // and unsafe.String. This function must panic if the ptr/len parameters are
    94  // invalid.
    95  func (b *builder) createUnsafeSliceStringCheck(name string, ptr, len llvm.Value, elementType llvm.Type, lenType *types.Basic) {
    96  	// From the documentation of unsafe.Slice and unsafe.String:
    97  	//   > At run time, if len is negative, or if ptr is nil and len is not
    98  	//   > zero, a run-time panic occurs.
    99  	// However, in practice, it is also necessary to check that the length is
   100  	// not too big that a GEP wouldn't be possible without wrapping the pointer.
   101  	// These two checks (non-negative and not too big) can be merged into one
   102  	// using an unsiged greater than.
   103  
   104  	// Make sure the len value is at least as big as a uintptr.
   105  	len = b.extendInteger(len, lenType, b.uintptrType)
   106  
   107  	// Determine the maximum slice size, and therefore the maximum value of the
   108  	// len parameter.
   109  	maxSize := b.maxSliceSize(elementType)
   110  	maxSizeValue := llvm.ConstInt(len.Type(), maxSize, false)
   111  
   112  	// Do the check. By using unsigned greater than for the length check, signed
   113  	// negative values are also checked (which are very large numbers when
   114  	// interpreted as signed values).
   115  	zero := llvm.ConstInt(len.Type(), 0, false)
   116  	lenOutOfBounds := b.CreateICmp(llvm.IntUGT, len, maxSizeValue, "")
   117  	ptrIsNil := b.CreateICmp(llvm.IntEQ, ptr, llvm.ConstNull(ptr.Type()), "")
   118  	lenIsNotZero := b.CreateICmp(llvm.IntNE, len, zero, "")
   119  	assert := b.CreateAnd(ptrIsNil, lenIsNotZero, "")
   120  	assert = b.CreateOr(assert, lenOutOfBounds, "")
   121  	b.createRuntimeAssert(assert, name, "unsafeSlicePanic")
   122  }
   123  
   124  // createChanBoundsCheck creates a bounds check before creating a new channel to
   125  // check that the value is not too big for runtime.chanMake.
   126  func (b *builder) createChanBoundsCheck(elementSize uint64, bufSize llvm.Value, bufSizeType *types.Basic, pos token.Pos) {
   127  	if b.info.nobounds {
   128  		// The //go:nobounds pragma was added to the function to avoid bounds
   129  		// checking.
   130  		return
   131  	}
   132  
   133  	// Make sure bufSize is at least as big as maxBufSize (an uintptr).
   134  	bufSize = b.extendInteger(bufSize, bufSizeType, b.uintptrType)
   135  
   136  	// Calculate (^uintptr(0)) >> 1, which is the max value that fits in an
   137  	// uintptr if uintptrs were signed.
   138  	maxBufSize := llvm.ConstLShr(llvm.ConstNot(llvm.ConstInt(b.uintptrType, 0, false)), llvm.ConstInt(b.uintptrType, 1, false))
   139  	if elementSize > maxBufSize.ZExtValue() {
   140  		b.addError(pos, fmt.Sprintf("channel element type is too big (%v bytes)", elementSize))
   141  		return
   142  	}
   143  	// Avoid divide-by-zero.
   144  	if elementSize == 0 {
   145  		elementSize = 1
   146  	}
   147  	// Make the maxBufSize actually the maximum allowed value (in number of
   148  	// elements in the channel buffer).
   149  	maxBufSize = b.CreateUDiv(maxBufSize, llvm.ConstInt(b.uintptrType, elementSize, false), "")
   150  
   151  	// Make sure maxBufSize has the same type as bufSize.
   152  	if maxBufSize.Type() != bufSize.Type() {
   153  		maxBufSize = llvm.ConstZExt(maxBufSize, bufSize.Type())
   154  	}
   155  
   156  	// Do the check for a too large (or negative) buffer size.
   157  	bufSizeTooBig := b.CreateICmp(llvm.IntUGE, bufSize, maxBufSize, "")
   158  	b.createRuntimeAssert(bufSizeTooBig, "chan", "chanMakePanic")
   159  }
   160  
   161  // createNilCheck checks whether the given pointer is nil, and panics if it is.
   162  // It has no effect in well-behaved programs, but makes sure no uncaught nil
   163  // pointer dereferences exist in valid Go code.
   164  func (b *builder) createNilCheck(inst ssa.Value, ptr llvm.Value, blockPrefix string) {
   165  	// Check whether we need to emit this check at all.
   166  	if !ptr.IsAGlobalValue().IsNil() {
   167  		return
   168  	}
   169  
   170  	switch inst := inst.(type) {
   171  	case *ssa.Alloc:
   172  		// An alloc is never nil.
   173  		return
   174  	case *ssa.FreeVar:
   175  		// A free variable is allocated in a parent function and is thus never
   176  		// nil.
   177  		return
   178  	case *ssa.IndexAddr:
   179  		// This pointer is the result of an index operation into a slice or
   180  		// array. Such slices/arrays are already bounds checked so the pointer
   181  		// must be a valid (non-nil) pointer. No nil checking is necessary.
   182  		return
   183  	case *ssa.Convert:
   184  		// This is a pointer that comes from a conversion from unsafe.Pointer.
   185  		// Don't do nil checking because this is unsafe code and the code should
   186  		// know what it is doing.
   187  		// Note: all *ssa.Convert instructions that result in a pointer must
   188  		// come from unsafe.Pointer. Testing here for unsafe.Pointer to be sure.
   189  		if inst.X.Type() == types.Typ[types.UnsafePointer] {
   190  			return
   191  		}
   192  	}
   193  
   194  	// Compare against nil.
   195  	// We previously used a hack to make sure this wouldn't break escape
   196  	// analysis, but this is not necessary anymore since
   197  	// https://reviews.llvm.org/D60047 has been merged.
   198  	nilptr := llvm.ConstPointerNull(ptr.Type())
   199  	isnil := b.CreateICmp(llvm.IntEQ, ptr, nilptr, "")
   200  
   201  	// Emit the nil check in IR.
   202  	b.createRuntimeAssert(isnil, blockPrefix, "nilPanic")
   203  }
   204  
   205  // createNegativeShiftCheck creates an assertion that panics if the given shift value is negative.
   206  // This function assumes that the shift value is signed.
   207  func (b *builder) createNegativeShiftCheck(shift llvm.Value) {
   208  	if b.info.nobounds {
   209  		// Function disabled bounds checking - skip shift check.
   210  		return
   211  	}
   212  
   213  	// isNegative = shift < 0
   214  	isNegative := b.CreateICmp(llvm.IntSLT, shift, llvm.ConstInt(shift.Type(), 0, false), "")
   215  	b.createRuntimeAssert(isNegative, "shift", "negativeShiftPanic")
   216  }
   217  
   218  // createDivideByZeroCheck asserts that y is not zero. If it is, a runtime panic
   219  // will be emitted. This follows the Go specification which says that a divide
   220  // by zero must cause a run time panic.
   221  func (b *builder) createDivideByZeroCheck(y llvm.Value) {
   222  	if b.info.nobounds {
   223  		return
   224  	}
   225  
   226  	// isZero = y == 0
   227  	isZero := b.CreateICmp(llvm.IntEQ, y, llvm.ConstInt(y.Type(), 0, false), "")
   228  	b.createRuntimeAssert(isZero, "divbyzero", "divideByZeroPanic")
   229  }
   230  
   231  // createRuntimeAssert is a common function to create a new branch on an assert
   232  // bool, calling an assert func if the assert value is true (1).
   233  func (b *builder) createRuntimeAssert(assert llvm.Value, blockPrefix, assertFunc string) {
   234  	// Check whether we can resolve this check at compile time.
   235  	if !assert.IsAConstantInt().IsNil() {
   236  		val := assert.ZExtValue()
   237  		if val == 0 {
   238  			// Everything is constant so the check does not have to be emitted
   239  			// in IR. This avoids emitting some redundant IR.
   240  			return
   241  		}
   242  	}
   243  
   244  	// Put the fault block at the end of the function and the next block at the
   245  	// current insert position.
   246  	faultBlock := b.ctx.AddBasicBlock(b.llvmFn, blockPrefix+".throw")
   247  	nextBlock := b.insertBasicBlock(blockPrefix + ".next")
   248  	b.blockExits[b.currentBlock] = nextBlock // adjust outgoing block for phi nodes
   249  
   250  	// Now branch to the out-of-bounds or the regular block.
   251  	b.CreateCondBr(assert, faultBlock, nextBlock)
   252  
   253  	// Fail: the assert triggered so panic.
   254  	b.SetInsertPointAtEnd(faultBlock)
   255  	b.createRuntimeCall(assertFunc, nil, "")
   256  	b.CreateUnreachable()
   257  
   258  	// Ok: assert didn't trigger so continue normally.
   259  	b.SetInsertPointAtEnd(nextBlock)
   260  }
   261  
   262  // extendInteger extends the value to at least targetType using a zero or sign
   263  // extend. The resulting value is not truncated: it may still be bigger than
   264  // targetType.
   265  func (b *builder) extendInteger(value llvm.Value, valueType types.Type, targetType llvm.Type) llvm.Value {
   266  	if value.Type().IntTypeWidth() < targetType.IntTypeWidth() {
   267  		if valueType.Underlying().(*types.Basic).Info()&types.IsUnsigned != 0 {
   268  			// Unsigned, so zero-extend to the target type.
   269  			value = b.CreateZExt(value, targetType, "")
   270  		} else {
   271  			// Signed, so sign-extend to the target type.
   272  			value = b.CreateSExt(value, targetType, "")
   273  		}
   274  	}
   275  	return value
   276  }