aboutsummaryrefslogtreecommitdiffhomepage
path: root/compiler
diff options
context:
space:
mode:
authorAyke van Laethem <[email protected]>2020-03-10 21:58:34 +0100
committerRon Evans <[email protected]>2020-03-13 16:15:36 -0700
commit79dae62c781bc3fe6df76d5a609f62fcc7a43310 (patch)
tree5c660c32a9897d7de7c24e69f1b74516f41ee545 /compiler
parent1a7369af6e3abc7f95f867d42ed037a3ebea9c0f (diff)
downloadtinygo-79dae62c781bc3fe6df76d5a609f62fcc7a43310.tar.gz
tinygo-79dae62c781bc3fe6df76d5a609f62fcc7a43310.zip
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.
Diffstat (limited to 'compiler')
-rw-r--r--compiler/asserts.go73
-rw-r--r--compiler/channel.go6
2 files changed, 79 insertions, 0 deletions
diff --git a/compiler/asserts.go b/compiler/asserts.go
index ad79c0a13..ab519842a 100644
--- a/compiler/asserts.go
+++ b/compiler/asserts.go
@@ -4,6 +4,8 @@ package compiler
// required by the Go programming language.
import (
+ "fmt"
+ "go/token"
"go/types"
"tinygo.org/x/go-llvm"
@@ -122,6 +124,77 @@ func (c *Compiler) emitSliceBoundsCheck(frame *Frame, capacity, low, high, max l
c.builder.SetInsertPointAtEnd(nextBlock)
}
+// emitChanBoundsCheck emits a bounds check before creating a new channel to
+// check that the value is not too big for runtime.chanMake.
+func (c *Compiler) emitChanBoundsCheck(frame *Frame, elementSize uint64, bufSize llvm.Value, bufSizeType *types.Basic, pos token.Pos) {
+ if frame.fn.IsNoBounds() {
+ // The //go:nobounds pragma was added to the function to avoid bounds
+ // checking.
+ return
+ }
+
+ // Check whether the bufSize parameter must be cast to a wider integer for
+ // comparison.
+ if bufSize.Type().IntTypeWidth() < c.uintptrType.IntTypeWidth() {
+ if bufSizeType.Info()&types.IsUnsigned != 0 {
+ // Unsigned, so zero-extend to uint type.
+ bufSizeType = types.Typ[types.Uint]
+ bufSize = c.builder.CreateZExt(bufSize, c.intType, "")
+ } else {
+ // Signed, so sign-extend to int type.
+ bufSizeType = types.Typ[types.Int]
+ bufSize = c.builder.CreateSExt(bufSize, c.intType, "")
+ }
+ }
+
+ // Calculate (^uintptr(0)) >> 1, which is the max value that fits in an
+ // uintptr if uintptrs were signed.
+ maxBufSize := llvm.ConstLShr(llvm.ConstNot(llvm.ConstInt(c.uintptrType, 0, false)), llvm.ConstInt(c.uintptrType, 1, false))
+ if elementSize > maxBufSize.ZExtValue() {
+ c.addError(pos, fmt.Sprintf("channel element type is too big (%v bytes)", elementSize))
+ return
+ }
+ // Avoid divide-by-zero.
+ if elementSize == 0 {
+ elementSize = 1
+ }
+ // Make the maxBufSize actually the maximum allowed value (in number of
+ // elements in the channel buffer).
+ maxBufSize = llvm.ConstUDiv(maxBufSize, llvm.ConstInt(c.uintptrType, elementSize, false))
+
+ // Make sure maxBufSize has the same type as bufSize.
+ if maxBufSize.Type() != bufSize.Type() {
+ maxBufSize = llvm.ConstZExt(maxBufSize, bufSize.Type())
+ }
+
+ bufSizeTooBig := c.builder.CreateICmp(llvm.IntUGE, bufSize, maxBufSize, "")
+ // Check whether we can resolve this check at compile time.
+ if !bufSizeTooBig.IsAConstantInt().IsNil() {
+ val := bufSizeTooBig.ZExtValue()
+ if val == 0 {
+ // Everything is constant so the check does not have to be emitted
+ // in IR. This avoids emitting some redundant IR in the vast
+ // majority of cases.
+ return
+ }
+ }
+
+ faultBlock := c.ctx.AddBasicBlock(frame.fn.LLVMFn, "chan.outofbounds")
+ nextBlock := c.ctx.AddBasicBlock(frame.fn.LLVMFn, "chan.next")
+ frame.blockExits[frame.currentBlock] = nextBlock // adjust outgoing block for phi nodes
+
+ // Now branch to the out-of-bounds or the regular block.
+ c.builder.CreateCondBr(bufSizeTooBig, faultBlock, nextBlock)
+
+ // Fail: this channel is created with an invalid size parameter.
+ c.builder.SetInsertPointAtEnd(faultBlock)
+ c.createRuntimeCall("chanMakePanic", nil, "")
+ c.builder.CreateUnreachable()
+
+ // Ok: this channel value is not too big.
+ c.builder.SetInsertPointAtEnd(nextBlock)
+}
+
// emitNilCheck checks whether the given pointer is nil, and panics if it is. It
// has no effect in well-behaved programs, but makes sure no uncaught nil
// pointer dereferences exist in valid Go code.
diff --git a/compiler/channel.go b/compiler/channel.go
index ac16510c6..de7b12e29 100644
--- a/compiler/channel.go
+++ b/compiler/channel.go
@@ -15,6 +15,12 @@ func (c *Compiler) emitMakeChan(frame *Frame, expr *ssa.MakeChan) llvm.Value {
elementSize := c.targetData.TypeAllocSize(c.getLLVMType(expr.Type().(*types.Chan).Elem()))
elementSizeValue := llvm.ConstInt(c.uintptrType, elementSize, false)
bufSize := c.getValue(frame, expr.Size)
+ c.emitChanBoundsCheck(frame, elementSize, bufSize, expr.Size.Type().Underlying().(*types.Basic), expr.Pos())
+ if bufSize.Type().IntTypeWidth() < c.uintptrType.IntTypeWidth() {
+ bufSize = c.builder.CreateZExt(bufSize, c.uintptrType, "")
+ } else if bufSize.Type().IntTypeWidth() > c.uintptrType.IntTypeWidth() {
+ bufSize = c.builder.CreateTrunc(bufSize, c.uintptrType, "")
+ }
return c.createRuntimeCall("chanMake", []llvm.Value{elementSizeValue, bufSize}, "")
}