Skip to content

Commit 79dae62

Browse files
aykevldeadprogram
authored andcommitted
compiler,runtime: check for channel size limits
This patch is a combination of two related changes: 1. The compiler now allows other types than `int` when specifying the size of a channel in a make(chan ..., size) call. 2. The compiler now checks for maximum allowed channel sizes. Such checks are trivially optimized out in the vast majority of cases as channel sizes are usually constant. I discovered this issue when trying out channels on AVR.
1 parent 1a7369a commit 79dae62

File tree

4 files changed

+94
-0
lines changed

4 files changed

+94
-0
lines changed

compiler/asserts.go

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ package compiler
44
// required by the Go programming language.
55

66
import (
7+
"fmt"
8+
"go/token"
79
"go/types"
810

911
"tinygo.org/x/go-llvm"
@@ -122,6 +124,77 @@ func (c *Compiler) emitSliceBoundsCheck(frame *Frame, capacity, low, high, max l
122124
c.builder.SetInsertPointAtEnd(nextBlock)
123125
}
124126

127+
// emitChanBoundsCheck emits a bounds check before creating a new channel to
128+
// check that the value is not too big for runtime.chanMake.
129+
func (c *Compiler) emitChanBoundsCheck(frame *Frame, elementSize uint64, bufSize llvm.Value, bufSizeType *types.Basic, pos token.Pos) {
130+
if frame.fn.IsNoBounds() {
131+
// The //go:nobounds pragma was added to the function to avoid bounds
132+
// checking.
133+
return
134+
}
135+
136+
// Check whether the bufSize parameter must be cast to a wider integer for
137+
// comparison.
138+
if bufSize.Type().IntTypeWidth() < c.uintptrType.IntTypeWidth() {
139+
if bufSizeType.Info()&types.IsUnsigned != 0 {
140+
// Unsigned, so zero-extend to uint type.
141+
bufSizeType = types.Typ[types.Uint]
142+
bufSize = c.builder.CreateZExt(bufSize, c.intType, "")
143+
} else {
144+
// Signed, so sign-extend to int type.
145+
bufSizeType = types.Typ[types.Int]
146+
bufSize = c.builder.CreateSExt(bufSize, c.intType, "")
147+
}
148+
}
149+
150+
// Calculate (^uintptr(0)) >> 1, which is the max value that fits in an
151+
// uintptr if uintptrs were signed.
152+
maxBufSize := llvm.ConstLShr(llvm.ConstNot(llvm.ConstInt(c.uintptrType, 0, false)), llvm.ConstInt(c.uintptrType, 1, false))
153+
if elementSize > maxBufSize.ZExtValue() {
154+
c.addError(pos, fmt.Sprintf("channel element type is too big (%v bytes)", elementSize))
155+
return
156+
}
157+
// Avoid divide-by-zero.
158+
if elementSize == 0 {
159+
elementSize = 1
160+
}
161+
// Make the maxBufSize actually the maximum allowed value (in number of
162+
// elements in the channel buffer).
163+
maxBufSize = llvm.ConstUDiv(maxBufSize, llvm.ConstInt(c.uintptrType, elementSize, false))
164+
165+
// Make sure maxBufSize has the same type as bufSize.
166+
if maxBufSize.Type() != bufSize.Type() {
167+
maxBufSize = llvm.ConstZExt(maxBufSize, bufSize.Type())
168+
}
169+
170+
bufSizeTooBig := c.builder.CreateICmp(llvm.IntUGE, bufSize, maxBufSize, "")
171+
// Check whether we can resolve this check at compile time.
172+
if !bufSizeTooBig.IsAConstantInt().IsNil() {
173+
val := bufSizeTooBig.ZExtValue()
174+
if val == 0 {
175+
// Everything is constant so the check does not have to be emitted
176+
// in IR. This avoids emitting some redundant IR in the vast
177+
// majority of cases.
178+
return
179+
}
180+
}
181+
182+
faultBlock := c.ctx.AddBasicBlock(frame.fn.LLVMFn, "chan.outofbounds")
183+
nextBlock := c.ctx.AddBasicBlock(frame.fn.LLVMFn, "chan.next")
184+
frame.blockExits[frame.currentBlock] = nextBlock // adjust outgoing block for phi nodes
185+
186+
// Now branch to the out-of-bounds or the regular block.
187+
c.builder.CreateCondBr(bufSizeTooBig, faultBlock, nextBlock)
188+
189+
// Fail: this channel is created with an invalid size parameter.
190+
c.builder.SetInsertPointAtEnd(faultBlock)
191+
c.createRuntimeCall("chanMakePanic", nil, "")
192+
c.builder.CreateUnreachable()
193+
194+
// Ok: this channel value is not too big.
195+
c.builder.SetInsertPointAtEnd(nextBlock)
196+
}
197+
125198
// emitNilCheck checks whether the given pointer is nil, and panics if it is. It
126199
// has no effect in well-behaved programs, but makes sure no uncaught nil
127200
// pointer dereferences exist in valid Go code.

compiler/channel.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,12 @@ func (c *Compiler) emitMakeChan(frame *Frame, expr *ssa.MakeChan) llvm.Value {
1515
elementSize := c.targetData.TypeAllocSize(c.getLLVMType(expr.Type().(*types.Chan).Elem()))
1616
elementSizeValue := llvm.ConstInt(c.uintptrType, elementSize, false)
1717
bufSize := c.getValue(frame, expr.Size)
18+
c.emitChanBoundsCheck(frame, elementSize, bufSize, expr.Size.Type().Underlying().(*types.Basic), expr.Pos())
19+
if bufSize.Type().IntTypeWidth() < c.uintptrType.IntTypeWidth() {
20+
bufSize = c.builder.CreateZExt(bufSize, c.uintptrType, "")
21+
} else if bufSize.Type().IntTypeWidth() > c.uintptrType.IntTypeWidth() {
22+
bufSize = c.builder.CreateTrunc(bufSize, c.uintptrType, "")
23+
}
1824
return c.createRuntimeCall("chanMake", []llvm.Value{elementSizeValue, bufSize}, "")
1925
}
2026

src/runtime/panic.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,11 @@ func slicePanic() {
5050
runtimePanic("slice out of range")
5151
}
5252

53+
// Panic when trying to create a new channel that is too big.
54+
func chanMakePanic() {
55+
runtimePanic("new channel is too big")
56+
}
57+
5358
func blockingPanic() {
5459
runtimePanic("trying to do blocking operation in exported function")
5560
}

testdata/channel.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,16 @@ func main() {
5454
n, ok = <-ch
5555
println("recv from closed channel:", n, ok)
5656

57+
// Test various channel size types.
58+
_ = make(chan int, int8(2))
59+
_ = make(chan int, int16(2))
60+
_ = make(chan int, int32(2))
61+
_ = make(chan int, int64(2))
62+
_ = make(chan int, uint8(2))
63+
_ = make(chan int, uint16(2))
64+
_ = make(chan int, uint32(2))
65+
_ = make(chan int, uint64(2))
66+
5767
// Test bigger values
5868
ch2 := make(chan complex128)
5969
wg.add(1)

0 commit comments

Comments
 (0)