Crystal: Recursive tuple causes compiler crash in explicit as(Type) Cast

Created on 24 May 2017  路  2Comments  路  Source: crystal-lang/crystal

Hi,

Apart from https://github.com/crystal-lang/crystal/issues/4454 I was able to find another bug. This causes the compiler to crash.

Code snippet:

# Tuple as part of the alias breaks things:
alias Type = String | Hash(Type, Type) | Tuple(Type, Type)

# This works just fine:
# alias Type = String | Hash(Type, Type)

class Val
  getter raw : Type

  def initialize(@raw)
  end

  def first
    object = @raw
    if object.responds_to?(:first) #is_a?(Enumerable) - Same result
      object.first
    end
  end
end

pp Val.new("foo").first # One of them alone work fine
({"foo".as(Type) => "bar".as(Type)}) # But with both, the compiler crashes

Results in the following output:

Nil assertion failed
Error opening file 'crystal' with mode 'r': No such file or directory (Errno)
Failed to raise an exception: END_OF_STACK
[0x409dc7] __crystal_raise +503
[0x40eb0e] ???
[0x4278e8] ???
[0x41c369] ???
[0x41aa95] ???
[0x413a26] ???
[0x43c35b] main +17739
[0x7f841023f511] __libc_start_main +241
[0x4097da] _start +42
[0x0] ???

Just removing the actually unused Tuple(Type, Type) part fixes the issue. Also, commenting just one (or both) of the last two lines in the snippet fixes the issue too.

Interestingly, if we don't store the value inside the class, but pass it as argument to #first instead, the program works as expected again:

alias Type = String | Hash(Type, Type) | Tuple(Type, Type)

class Val
  def first(object)
    if object.responds_to?(:first) #is_a?(Enumerable) - Same result
      object.first
    end
  end
end

pp Val.new.first("foo")
({"foo".as(Type) => "bar".as(Type)})

Thanks

bug compiler

Most helpful comment

Seems that compiler isn't checking recursive structs properly since Tuple is a struct and crystal don't allow recursive structs

Maybe related #1056

All 2 comments

I've tried to implement the behaviour of Hash#first inside the method and create the tuple manually:

alias Type = String | Hash(Type, Type) | Tuple(Type, Type)

class Val
  getter raw : Type

  def initialize(@raw)
  end

  def first
    object = @raw
    if object.is_a?(Hash)
      {object.first_key.as(Type), object.first_value.as(Type)}
    end
  end
end

pp Val.new({"foo".as(Type) => "bar".as(Type)}).first

This compiles, but results in an invalid memory access:

Invalid memory access (signal 11) at address 0x4f0000
[0x464be5] *CallStack::print_backtrace:Int32 +117
[0x4590ad] __crystal_sigfault_handler +61
[0x4be768] sigfault_handler +40
[0x7f84919f03d0] ???
[0x45b34b] *Pointer(UInt8) +27
[0x46e793] *String#unsafe_byte_at<Int32>:UInt8 +35
[0x48145e] *Char::Reader#byte_at<Int32>:UInt32 +30
[0x480530] *Char::Reader#decode_current_char:Char +48
[0x4815c3] *Char::Reader#next_char:Char +51
[0x470dc4] *String#inspect<String::Builder>:String::Builder +468
[0x470bda] *String +74
[0x470b68] *String#pretty_print<PrettyPrint>:(Int32 | Nil) +24
[0x49d1e1] *PrettyPrint#list<String, Tuple(Type, Type), String>:Nil +993
[0x4a122f] *Tuple(Type, Type) +63
[0x49cc41] *PrettyPrint::format<(Tuple(Type, Type) | Nil), IO::FileDescriptor, Int32, String, Int32>:IO::FileDescriptor +177
[0x49cb88] *PrettyPrint::format:width:indent<(Tuple(Type, Type) | Nil), IO::FileDescriptor, Int32, Int32>:IO::FileDescriptor +88
[0x448c6e] ???
[0x458f89] main +41
[0x7f8491014830] __libc_start_main +240
[0x448369] _start +41
[0x0] ???

Seems that compiler isn't checking recursive structs properly since Tuple is a struct and crystal don't allow recursive structs

Maybe related #1056

Was this page helpful?
0 / 5 - 0 ratings

Related issues

asterite picture asterite  路  139Comments

benoist picture benoist  路  59Comments

akzhan picture akzhan  路  67Comments

straight-shoota picture straight-shoota  路  91Comments

HCLarsen picture HCLarsen  路  162Comments