Crystal: Invalid memory access with self-referential constant

Created on 25 Apr 2018  路  13Comments  路  Source: crystal-lang/crystal

This segfaults at runtime upon program start:

class Bad
  def initialize(@parent : Bad)
  end
end

ARR = [ONE] of Bad
ONE = Bad.new(ARR.last)

puts ONE if false # Doesn't need to execute but must be present for codegen

https://carc.in/#/r/3xzi

Output:

Invalid memory access (signal 11) at address 0x4
[0x55e0ba9a6686] *CallStack::print_backtrace:Int32 +118
[0x55e0ba99b49b] __crystal_sigfault_handler +75
[0x7fcbf597add0] ???
[0x55e0ba9e1f31] *Array(Bad) +1
[0x55e0ba99b57e] ~ONE:init +14
[0x55e0ba99b567] ~ONE:read +39
[0x55e0ba99b4f6] ~ARR:init +70
[0x55e0ba98e8b4] __crystal_main +1604
[0x55e0ba99b716] *_crystal_main<Int32, Pointer(Pointer(UInt8))>:Nil +6
[0x55e0ba9e25e6] *Crystal::main_user_code<Int32, Pointer(Pointer(UInt8))>:Nil +6
[0x55e0ba9e24a2] *Crystal::main<Int32, Pointer(Pointer(UInt8))>:Int32 +50
[0x55e0ba999846] main +6
[0x7fcbf4d59f4a] __libc_start_main +234
[0x55e0ba98e18a] _start +42
[0x0] ???

Version:

$  crystal --version
Crystal 0.24.2 (2018-03-19)

LLVM: 6.0.0
Default target: x86_64-pc-linux-gnu
bug compiler

Most helpful comment

Yes, don't do that :-)

All 13 comments

It's basically the same, but only fails when accessing the const:

FOO = BAR || ""
BAR = FOO

puts FOO

Yes, don't do that :-)

Note that detecting such recursions might be impossible, so I'd close this. Spotting such recursions in constants is very simple.

@ezrast Did you accidentally made the recursion, or was it just to see what happens?

@asterite I was implementing a simple state machine where each state was a constant, and contained within it all possible state transitions, including next possible state. Basically it was like this:

class TokenType
  def initialize(@regex : Regex, @next_state : LexerState? = nil)
  end

  IDENTIFIER = new(/[a-z_][a-zA-Z_]*/)
  LEFT_QUOTE = new(/"/, LexerState::QUOTE_STATE)
  STRING_BODY = new(/[^"]/)
  RIGHT_QUOTE = new(/"/, LexerState::MAIN_STATE)
end

class LexerState
  def initialize(@possible_tokens : Array(TokenType))
  end

  MAIN_STATE = new([
    TokenType::IDENTIFIER,
    TokenType::LEFT_QUOTE,
  ])
  QUOTE_STATE = new([
    TokenType::STRING_BODY,
    TokenType::RIGHT_QUOTE
  ])
end

p TokenType::RIGHT_QUOTE

Additionally, if you swap the order of the two classes, then RIGHT_QUOTE is accessible but has the wrong contents. That seems like it should be fixable at least: https://carc.in/#/r/3zgz

At the very least this should generate a stack overflow segfault between the constant initializer functions at runtime instead of just put bad data in the constants.

I think an accurate runtime error can be produced. We just need yet another boolean flag associated with constants that tell whether they are being initialized, and raise an exception or given an error when trying to initialize them and this flag is already true.

Another thing is that they should probably be initialized with a mutex, but that's another story.

@asterite yeah that's work too. Reliable stack overflow detection and messaging for normal stack overflows would be nice too...

Is it viable to just require that constants be initialized before they're used?

We (attempt to) do that, that mechanism just has a bug.

To be clear, I meant before they're used in the source code, e.g. this code would not compile:

BAR = FOO
FOO = 12

Simplified example in the comment above gives no compilation errors.


Crystal 0.31.1 on MacOS

$ crystal --version
Crystal 0.31.1 (2019-10-02)

LLVM: 8.0.1
Default target: x86_64-apple-macosx

The original example now raises Recursion while initializing class variables and/or constants.

Fixed by #8172

Was this page helpful?
0 / 5 - 0 ratings