Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

compiler crash on interface assert #3788

Closed
awmorgan opened this issue Jun 12, 2023 · 6 comments
Closed

compiler crash on interface assert #3788

awmorgan opened this issue Jun 12, 2023 · 6 comments
Labels
bug Something isn't working

Comments

@awmorgan
Copy link

awmorgan commented Jun 12, 2023

I originally noticed this issue while investigating another issue: #3771 (comment)

this is a small reproducer:

/Volumes/git/tinygo/x (dev)*$ uname -v
Darwin Kernel Version 20.6.0: Thu Mar  9 20:39:26 PST 2023; root:xnu-7195.141.49.700.6~1/RELEASE_X86_64
/Volumes/git/tinygo/x (dev)*$ 
/Volumes/git/tinygo/x (dev)*$ go version
go version go1.20.5 darwin/amd64
/Volumes/git/tinygo/x (dev)*$ 
/Volumes/git/tinygo/x (dev)*$ tinygo version
tinygo version 0.28.1 darwin/amd64 (using go version go1.20.5 and LLVM version 15.0.7)
/Volumes/git/tinygo/x (dev)*$ 
/Volumes/git/tinygo/x (dev)*$ cat main.go 
package main

func main() {
        // _ = any(0).(interface{ x() }) // this is ok
}

func init() {
        _ = any(0).(interface{ x() }) // this crashes the compiler
}
/Volumes/git/tinygo/x (dev)*$ tinygo build .
panic: interp: offset out of range

goroutine 66 [running]:
github.com/tinygo-org/tinygo/interp.pointerValue.addOffset(...)
        /Volumes/git/tinygo/interp/memory.go:523
github.com/tinygo-org/tinygo/interp.(*runner).run(0xc0010c0b40, 0xc000f75db0, {0xc00072f110, 0x1, 0x0?}, 0xc00111ba10, {0xc0007160d0, 0x8})
        /Volumes/git/tinygo/interp/interpreter.go:413 +0x78c8
github.com/tinygo-org/tinygo/interp.(*runner).run(0xc0010c0b40, 0xc000f75d60, {0x0, 0x0, 0x10001ba50?}, 0x0, {0x10041ef36, 0x4})
        /Volumes/git/tinygo/interp/interpreter.go:512 +0x7d45
github.com/tinygo-org/tinygo/interp.RunFunc({0xc0001ea120?}, 0xc001503ce0?, 0x40?)
        /Volumes/git/tinygo/interp/interp.go:238 +0x3a5
github.com/tinygo-org/tinygo/builder.Build.func3(0xc001115560)
        /Volumes/git/tinygo/builder/build.go:437 +0xcf9
github.com/tinygo-org/tinygo/builder.runJob(0xc001115560, 0x0?)
        /Volumes/git/tinygo/builder/jobs.go:222 +0x4f
created by github.com/tinygo-org/tinygo/builder.runJobs
        /Volumes/git/tinygo/builder/jobs.go:123 +0x5be
/Volumes/git/tinygo/x (dev)*$ 
@awmorgan
Copy link
Author

The out of range panic is because addOffset is being passed a -8 from -int64(r.pointerSize)

			case strings.HasSuffix(callFn.name, ".$typeassert"):
				if r.debug {
					fmt.Fprintln(os.Stderr, indent+"interface assert:", operands[1:])
				}

				// Load various values for the interface implements check below.
				typecodePtr, err := operands[1].asPointer(r)
				if err != nil {
					return nil, mem, r.errorAt(inst, err)
				}
				methodSetPtr, err := mem.load(typecodePtr.addOffset(-int64(r.pointerSize)), r.pointerSize).asPointer(r)
				if err != nil {
					return nil, mem, r.errorAt(inst, err)
				}

here is some info from debugging this:

/Volumes/git/tinygo/x (dev)*$ dlv exec ../tinygo  -- build .
Type 'help' for list of commands.
(dlv) c
> [unrecovered-panic] runtime.fatalpanic() /usr/local/go/src/runtime/panic.go:1145 (hits goroutine(23):1 total:1) (PC: 0x100040400)
Warning: debugging optimized function
        runtime.curg._panic.arg: interface {}(string) "interp: offset out of range"
  1140: // fatalpanic implements an unrecoverable panic. It is like fatalthrow, except
  1141: // that if msgs != nil, fatalpanic also prints panic messages and decrements
  1142: // runningPanicDefers once main is blocked from exiting.
  1143: //
  1144: //go:nosplit
=>1145: func fatalpanic(msgs *_panic) {
  1146:         pc := getcallerpc()
  1147:         sp := getcallersp()
  1148:         gp := getg()
  1149:         var docrash bool
  1150:         // Switch to the system stack to avoid any stack growth, which
(dlv) bt
0  0x0000000100040400 in runtime.fatalpanic
   at /usr/local/go/src/runtime/panic.go:1145
1  0x000000010003fb8c in runtime.gopanic
   at /usr/local/go/src/runtime/panic.go:987
2  0x000000010049f20f in github.com/tinygo-org/tinygo/interp.pointerValue.addOffset
   at /Volumes/git/tinygo/interp/memory.go:523
3  0x00000001004940d1 in github.com/tinygo-org/tinygo/interp.(*runner).run
   at /Volumes/git/tinygo/interp/interpreter.go:413
4  0x000000010049602e in github.com/tinygo-org/tinygo/interp.(*runner).run
   at /Volumes/git/tinygo/interp/interpreter.go:512
5  0x000000010048a45b in github.com/tinygo-org/tinygo/interp.RunFunc
   at /Volumes/git/tinygo/interp/interp.go:238
6  0x00000001004ed9fb in github.com/tinygo-org/tinygo/builder.Build.func3
   at /Volumes/git/tinygo/builder/build.go:437
7  0x0000000100500b3b in github.com/tinygo-org/tinygo/builder.runJob
   at /Volumes/git/tinygo/builder/jobs.go:222
8  0x0000000100500199 in github.com/tinygo-org/tinygo/builder.runJobs.func2
   at /Volumes/git/tinygo/builder/jobs.go:123
9  0x0000000100074be1 in runtime.goexit
   at /usr/local/go/src/runtime/asm_amd64.s:1598
(dlv) frame 2
> [unrecovered-panic] runtime.fatalpanic() /usr/local/go/src/runtime/panic.go:1145 (hits goroutine(23):1 total:1) (PC: 0x100040400)
Warning: debugging optimized function
Frame 2: /Volumes/git/tinygo/interp/memory.go:523 (PC: 10049f20f)
   518: // offset to the pointer. It also checks that the offset doesn't overflow the
   519: // maximum offset size (which is 4GB).
   520: func (v pointerValue) addOffset(offset int64) pointerValue {
   521:         result := pointerValue{v.pointer + uint64(offset)}
   522:         if checks && v.index() != result.index() {
=> 523:                 panic("interp: offset out of range")
   524:         }
   525:         return result
   526: }
   527:
   528: func (v pointerValue) len(r *runner) uint32 {
(dlv) locals
result = github.com/tinygo-org/tinygo/interp.pointerValue {pointer: 12884901880}
(dlv) p offset
-8
(dlv) up
> [unrecovered-panic] runtime.fatalpanic() /usr/local/go/src/runtime/panic.go:1145 (hits goroutine(23):1 total:1) (PC: 0x100040400)
Warning: debugging optimized function
Frame 3: /Volumes/git/tinygo/interp/interpreter.go:413 (PC: 1004940d1)
   408:                                 // Load various values for the interface implements check below.
   409:                                 typecodePtr, err := operands[1].asPointer(r)
   410:                                 if err != nil {
   411:                                         return nil, mem, r.errorAt(inst, err)
   412:                                 }
=> 413:                                 methodSetPtr, err := mem.load(typecodePtr.addOffset(-int64(r.pointerSize)), r.pointerSize).asPointer(r)
   414:                                 if err != nil {
   415:                                         return nil, mem, r.errorAt(inst, err)
   416:                                 }
   417:                                 methodSet := mem.get(methodSetPtr.index()).llvmGlobal.Initializer()
   418:                                 numMethods := int(r.builder.CreateExtractValue(methodSet, 0, "").ZExtValue())
(dlv) locals
mem = github.com/tinygo-org/tinygo/interp.memoryView {r: ("*github.com/tinygo-org/tinygo/interp.runner")(0xc00063e3c0), parent: ("*github.com/tinygo-org/tinygo/interp.memoryView")(0xc00063ac00), objects: map[uint32]github.com/tinygo-org/tinygo/interp.object nil,...+1 more}
locals = []github.com/tinygo-org/tinygo/interp.value len: 7, cap: 7, [...]
runtimeBlocks = map[int]struct {} nil
bb = ("*github.com/tinygo-org/tinygo/interp.basicBlock")(0xc00063ac90)
currentBB = 0
lastBB = -1
operands = []github.com/tinygo-org/tinygo/interp.value len: 2, cap: 2, [...]
startRTInsts = 0
instIndex = 0
inst = github.com/tinygo-org/tinygo/interp.instruction {opcode: Call (45), localIndex: 1, operands: []github.com/tinygo-org/tinygo/interp.value len: 2, cap: 2, [...],...+2 more}
isRuntimeInst = false
(err) = error nil
fnPtr = github.com/tinygo-org/tinygo/interp.pointerValue {pointer: 8589934592}
callFn = ("*github.com/tinygo-org/tinygo/interp.function")(0xc000654230)
err = error nil
typecodePtr = github.com/tinygo-org/tinygo/interp.pointerValue {pointer: 12884901888}
(dlv) 

@dgryski
Copy link
Member

dgryski commented Jun 14, 2023

/cc @aykevl

@awmorgan
Copy link
Author

awmorgan commented Jun 18, 2023

@dgryski @aykevl I did some more investigation on this issue. It appears that the issue is caused by the type not having a method set.

package main

func main() {
}

type myint int

func init() {
	// _ = any(0).(any) // compiler crashes
	_ = any(myint(0)).(any) // compiler crashes if x() is commented below, but ok if x() is uncommented below
}

// func (i myint) x() {}

The panicking operation is typecodePtr.addOffset(-int64(r.pointerSize)) in this code:

			case strings.HasSuffix(callFn.name, ".$typeassert"):
				if r.debug {
					fmt.Fprintln(os.Stderr, indent+"interface assert:", operands[1:])
				}

				// Load various values for the interface implements check below.
				typecodePtr, err := operands[1].asPointer(r)
				if err != nil {
					return nil, mem, r.errorAt(inst, err)
				}
				methodSetPtr, err := mem.load(typecodePtr.addOffset(-int64(r.pointerSize)), r.pointerSize).asPointer(r)
				if err != nil {
					return nil, mem, r.errorAt(inst, err)
				}
				methodSet := mem.get(methodSetPtr.index()).llvmGlobal.Initializer()

I think the fix is to check if the type has a method set before trying to get the pointer to the method set that doesn't exist, but I don't know how to do this.

How can I check for a valid method set in this code?

I noticed that methods is an empty string after calling getMethodsString here:

> github.com/tinygo-org/tinygo/compiler.(*compilerContext).getInterfaceImplementsFunc() /Volumes/git/tinygo/compiler/interface.go:775 (PC: 0x1004680cf)
   770:         if llvmFn.IsNil() {
   771:                 llvmFnType := llvm.FunctionType(c.ctx.Int1Type(), []llvm.Type{c.i8ptrType}, false)
   772:                 llvmFn = llvm.AddFunction(c.mod, fnName, llvmFnType)
   773:                 c.addStandardDeclaredAttributes(llvmFn)
   774:                 methods := c.getMethodsString(assertedType.Underlying().(*types.Interface))
=> 775:                 llvmFn.AddFunctionAttr(c.ctx.CreateStringAttribute("tinygo-methods", methods))
   776:         }
   777:         return llvmFn
   778: }
   779:
   780: // getInvokeFunction returns the thunk to call the given interface method. The
(dlv) p methods
""
(dlv) 

awmorgan added a commit to awmorgan/tinygo that referenced this issue Jun 18, 2023
…rg#3788)

This commit refines the logic of type assertion for handling interfaces.
Previously, the logic only checked if the asserted type was an interface,
leading to a compiler panic when the interface did not have any methods.

The updated logic correctly handles type assertion by checking if the asserted
interface type has methods. Moreover, if the type only implements the empty
interface (i.e., has no methods), it is now correctly converted into a runtime
type assert call.
@dapchen
Copy link

dapchen commented Nov 2, 2023

hit this issue as well.

@deadprogram
Copy link
Member

Completed with v0.31.0 so now closing. Thank you!

@deadprogram deadprogram removed the next-release Will be part of next release label Feb 28, 2024
@marco6
Copy link

marco6 commented Mar 5, 2024

Hi @deadprogram! I think it actually wasn't fixed as the original bug is still there in version 0.31.0. Given a file containing:

// main.go

package main

func init() {
	_ = any(0).(interface{ x() }) // this crashes the compiler
}

func main() {}

Running you get:

$ tinygo version
tinygo version 0.31.1 linux/amd64 (using go version go1.21.1 and LLVM version 17.0.1)
$ tinygo build .
# main
<path>/main.go:6:13: interp: offset -8 out of range for object <3>
  %0 = call i1 @"interface:{main.x:func:{}{}}.$typeassert"(ptr @"reflect/types.type:basic:int"), !dbg !26

traceback:
<path>/main.go:6:13:
  %0 = call i1 @"interface:{main.x:func:{}{}}.$typeassert"(ptr @"reflect/types.type:basic:int"), !dbg !26
<path>:
  call void @"main.init#1"(ptr undef), !dbg !26

Right now, only the case of:

_ = any(0).(any) // this crashes the compiler

got fixed. Can we reopen this issue?

Last, I opened some time ago a fix to this issue. Could you take a look?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants