compiler: let a failed typeassert return the nil value

See the comment in the source for details.

The underlying type would be casted to the final one even before the
type is checked. This apparently led LLVM to think the type cast was OK,
so it speculatively dereferenced a pointer (while the underlying type
was an int). Speculatively dereferencing a pointer is fine when it is a
valid pointer, but when it is not it leads to a segfault (or worse).
This is what I saw, and it took me a while to figure out where it went
wrong.
Этот коммит содержится в:
Ayke van Laethem 2018-09-03 19:11:28 +02:00
родитель efdc2b8672
коммит c109ec0955
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: E97FF5335DFDFDED

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

@ -2122,6 +2122,7 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) {
return llvm.Value{}, err return llvm.Value{}, err
} }
if _, ok := expr.AssertedType.Underlying().(*types.Interface); ok { if _, ok := expr.AssertedType.Underlying().(*types.Interface); ok {
// TODO: check whether the type implements the interface.
return llvm.Value{}, errors.New("todo: assert on interface") return llvm.Value{}, errors.New("todo: assert on interface")
} }
assertedType, err := c.getLLVMType(expr.AssertedType) assertedType, err := c.getLLVMType(expr.AssertedType)
@ -2137,19 +2138,46 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) {
return llvm.Value{}, errors.New("interface typecodes do not fit in a 16-bit integer") return llvm.Value{}, errors.New("interface typecodes do not fit in a 16-bit integer")
} }
actualTypeNum := c.builder.CreateExtractValue(itf, 0, "interface.type") actualTypeNum := c.builder.CreateExtractValue(itf, 0, "interface.type")
valuePtr := c.builder.CreateExtractValue(itf, 1, "interface.value")
var value llvm.Value commaOk := c.builder.CreateICmp(llvm.IntEQ, llvm.ConstInt(llvm.Int16Type(), uint64(assertedTypeNum), false), actualTypeNum, "")
// Add 2 new basic blocks (that should get optimized away): one for the
// 'ok' case and one for all instructions following this type assert.
// This is necessary because we need to insert the casted value or the
// nil value based on whether the assert was successful. Casting before
// this check tells LLVM that it can use this value and may
// speculatively dereference pointers before the check. This can lead to
// a miscompilation resulting in a segfault at runtime.
// Additionally, this is even required by the Go spec: a failed
// typeassert should return a zero value, not an incorrectly casted
// value.
valueNil, err := getZeroValue(assertedType)
if err != nil {
return llvm.Value{}, err
}
prevBlock := c.builder.GetInsertBlock()
okBlock := c.ctx.AddBasicBlock(frame.fn.llvmFn, "typeassert.ok")
nextBlock := c.ctx.AddBasicBlock(frame.fn.llvmFn, "typeassert.next")
c.builder.CreateCondBr(commaOk, okBlock, nextBlock)
// Retrieve the value from the interface if the type assert was
// successful.
c.builder.SetInsertPointAtEnd(okBlock)
valuePtr := c.builder.CreateExtractValue(itf, 1, "typeassert.value.ptr")
var valueOk llvm.Value
if c.targetData.TypeAllocSize(assertedType) > c.targetData.TypeAllocSize(c.i8ptrType) { if c.targetData.TypeAllocSize(assertedType) > c.targetData.TypeAllocSize(c.i8ptrType) {
// Value was stored in an allocated buffer, load it from there. // Value was stored in an allocated buffer, load it from there.
valuePtrCast := c.builder.CreateBitCast(valuePtr, llvm.PointerType(assertedType, 0), "") valuePtrCast := c.builder.CreateBitCast(valuePtr, llvm.PointerType(assertedType, 0), "")
value = c.builder.CreateLoad(valuePtrCast, "") valueOk = c.builder.CreateLoad(valuePtrCast, "typeassert.value.ok")
} else { } else {
// Value was stored directly in the interface. // Value was stored directly in the interface.
switch assertedType.TypeKind() { switch assertedType.TypeKind() {
case llvm.IntegerTypeKind: case llvm.IntegerTypeKind:
value = c.builder.CreatePtrToInt(valuePtr, assertedType, "") valueOk = c.builder.CreatePtrToInt(valuePtr, assertedType, "typeassert.value.ok")
case llvm.PointerTypeKind: case llvm.PointerTypeKind:
value = c.builder.CreateBitCast(valuePtr, assertedType, "") valueOk = c.builder.CreateBitCast(valuePtr, assertedType, "typeassert.value.ok")
case llvm.StructTypeKind: case llvm.StructTypeKind:
// A bitcast would be useful here, but bitcast doesn't allow // A bitcast would be useful here, but bitcast doesn't allow
// aggregate types. So we'll bitcast it using an alloca. // aggregate types. So we'll bitcast it using an alloca.
@ -2157,16 +2185,20 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) {
mem := c.builder.CreateAlloca(c.i8ptrType, "") mem := c.builder.CreateAlloca(c.i8ptrType, "")
c.builder.CreateStore(valuePtr, mem) c.builder.CreateStore(valuePtr, mem)
memStructPtr := c.builder.CreateBitCast(mem, llvm.PointerType(assertedType, 0), "") memStructPtr := c.builder.CreateBitCast(mem, llvm.PointerType(assertedType, 0), "")
value = c.builder.CreateLoad(memStructPtr, "") valueOk = c.builder.CreateLoad(memStructPtr, "typeassert.value.ok")
default: default:
return llvm.Value{}, errors.New("todo: typeassert: bitcast small types") return llvm.Value{}, errors.New("todo: typeassert: bitcast small types")
} }
} }
// TODO: for interfaces, check whether the type implements the c.builder.CreateBr(nextBlock)
// interface.
commaOk := c.builder.CreateICmp(llvm.IntEQ, llvm.ConstInt(llvm.Int16Type(), uint64(assertedTypeNum), false), actualTypeNum, "") // Continue after the if statement.
c.builder.SetInsertPointAtEnd(nextBlock)
phi := c.builder.CreatePHI(assertedType, "typeassert.value")
phi.AddIncoming([]llvm.Value{valueNil, valueOk}, []llvm.BasicBlock{prevBlock, okBlock})
tuple := llvm.ConstStruct([]llvm.Value{llvm.Undef(assertedType), llvm.Undef(llvm.Int1Type())}, false) // create empty tuple tuple := llvm.ConstStruct([]llvm.Value{llvm.Undef(assertedType), llvm.Undef(llvm.Int1Type())}, false) // create empty tuple
tuple = c.builder.CreateInsertValue(tuple, value, 0, "") // insert value tuple = c.builder.CreateInsertValue(tuple, phi, 0, "") // insert value
tuple = c.builder.CreateInsertValue(tuple, commaOk, 1, "") // insert 'comma ok' boolean tuple = c.builder.CreateInsertValue(tuple, commaOk, 1, "") // insert 'comma ok' boolean
return tuple, nil return tuple, nil
case *ssa.UnOp: case *ssa.UnOp: