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
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
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
Most helpful comment
Yes, don't do that :-)