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 unsigned 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 := b.CreateLShr(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 = b.CreateZExt(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.currentBlockInfo.exit = 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 }
277