Rather interestingly, deleting some of the code here (block for bug0, bug1) will cause the crash to disappear.
Crystal 0.35.1 [5999ae29b] (2020-06-19)
LLVM: 8.0.0
Default target: x86_64-unknown-linux-gnu
Fedora 33
require "option_parser"
thunk = nil
parser = OptionParser.new do |parser|
parser.on("bug0", "") do
parser.unknown_args do |args|
thunk = ->do
Process::Status.new(0)
end
end
end
parser.on("bug1", "") do
parser.unknown_args do |args|
thunk = ->do
end
end
end
parser.on("bug", "") do
parser.unknown_args do |_, after_dash|
thunk = ->do
Process.exec("ls", after_dash)
end
puts thunk
puts "never get here"
end
end
end
parser.parse
$ crystal run bug.cr -- bug -- hi
Invalid memory access (signal 11) at address 0x39a00000001
[0x4614e6] *Exception::CallStack::print_backtrace:Int32 +118
[0x4543c5] __crystal_sigfault_handler +341
Umm if this contains Process.exec, it'd be more surprising if it didn't crash. Any way to exclude that?
Reduced:
def capture(&block)
block
end
thunk = ->{ 1 }
a = capture do
thunk = ->{ 1.0 }
end
thunk = ->{ raise "OH NO" }
puts thunk
No idea why it happens.
@asterite 's example for me always crashes at the specific address 0x23500000235, same on carc.in.
So weird.
Umm if this contains Process.exec, it'd be more surprising if it didn't crash. Any way to exclude that?
I rather thought the sometimes-NoReturn aspect of the Proc would be the most interesting part, but I guess not given @asterite's reduction
That 0x235 is 100% the typeid.
I reduced it a bit more (and changed the snippet). Yeah, I'm almost sure that number is a type ID or something.
Further reduced:
puts typeof(true ? ->{ raise "" } : true ? ->{ 1 } : ->{ 1.0 }) # => Proc(Float64) | Proc(Int32)
puts true ? ->{ raise "" } : true ? ->{ 1 } : ->{ 1.0 }
It seems
Proc(*T, NoReturn) to be a subtype of Proc(*T, R) for any other R;Proc(NoReturn) < Proc(Float64) and Proc(NoReturn) < Proc(Int32), therefore Proc(NoReturn) < Union(Proc(Float64), Proc(Int32));Proc(NoReturn) has the same binary layout as a Union(Proc(Float64), Proc(Int32)), which cannot be true because only the latter has a type ID.(On a side note I thought Pointer(Void) would be subject to the same issues, but luckily that doesn't happen.)
@HertzDevil Thanks for the explanation! I think I know how to fix it, then. We shouldn't be forming unions here. Instead, one should be able to pass a Proc that returns NoReturn to something that expect a Proc that returns T (with the same argument types). We already do something similar for Procs that returns Nil.
I'll fix this soon.
Most helpful comment
Further reduced:
It seems
Proc(*T, NoReturn)to be a subtype ofProc(*T, R)for any otherR;Proc(NoReturn) < Proc(Float64)andProc(NoReturn) < Proc(Int32), thereforeProc(NoReturn) < Union(Proc(Float64), Proc(Int32));Proc(NoReturn)has the same binary layout as aUnion(Proc(Float64), Proc(Int32)), which cannot be true because only the latter has a type ID.(On a side note I thought
Pointer(Void)would be subject to the same issues, but luckily that doesn't happen.)