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 }