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.
Этот коммит содержится в:
Ayke van Laethem 2020-03-10 21:58:34 +01:00 коммит произвёл Ron Evans
родитель 1a7369af6e
коммит 79dae62c78
4 изменённых файлов: 94 добавлений и 0 удалений

Просмотреть файл

@ -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.

Просмотреть файл

@ -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}, "")
}

Просмотреть файл

@ -50,6 +50,11 @@ func slicePanic() {
runtimePanic("slice out of range")
}
// Panic when trying to create a new channel that is too big.
func chanMakePanic() {
runtimePanic("new channel is too big")
}
func blockingPanic() {
runtimePanic("trying to do blocking operation in exported function")
}

10
testdata/channel.go предоставленный
Просмотреть файл

@ -54,6 +54,16 @@ func main() {
n, ok = <-ch
println("recv from closed channel:", n, ok)
// Test various channel size types.
_ = make(chan int, int8(2))
_ = make(chan int, int16(2))
_ = make(chan int, int32(2))
_ = make(chan int, int64(2))
_ = make(chan int, uint8(2))
_ = make(chan int, uint16(2))
_ = make(chan int, uint32(2))
_ = make(chan int, uint64(2))
// Test bigger values
ch2 := make(chan complex128)
wg.add(1)