Crystal: Undefined behavior when accessing an instance variable as default parameter

Created on 12 Oct 2016  路  6Comments  路  Source: crystal-lang/crystal

class A
  getter :a, :b

  def initialize(@a : Symbol, @b : Symbol = @a)
  end
end

a = A.new a: :xx
puts a.b
#output :skip
bug compiler topicsemantic

Most helpful comment

And I think the most probable solution we'll do is to disallow this (using instance variables in default values), it's pretty hard to implement.

All 6 comments

Thank you for reporting this!

That :skip looks mysterious, but it's just the symbol with number 0. This proves it:

class A
  getter :a, :b

  def initialize(@a : Int32, @b : Int32 = @a)
  end
end

a = A.new a: 1
puts a.b # => 0

Definitely a bug, though.

And I think the most probable solution we'll do is to disallow this (using instance variables in default values), it's pretty hard to implement.

Note that using just the param works. I agree to disable ivars as default. Maybe a hint in case the ivar is assigned with this pattern could be added.

class A
  getter :a, :b

  def initialize(@a : Int32, @b : Int32 = a)
  end
end

a = A.new a: 1
puts a.b # => 1

And I think the most probable solution we'll do is to disallow this (using instance variables in default values), it's pretty hard to implement.

@asterite why is it hard to implement ?

Can't we just detect an ivar access, and verify that it has been set before ?

class Foo
  @a : Int32
  @b : Int32
  @c : Int32

  # This is invalid, @c is used before initialization
  def initialize(@a,   @b = @c,   @c = 42)
  end

  # This is valid, @a has been initialized before
  def initialize(@a,   @b = @a,   @c = 42)
  end
end

When/If #6007 is merged, we'd need to be careful and properly use the __arg0 instead of @a when initializing @b, but that's doable I think.

Checked in 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

Ary's example now gives a different result:

class A
  getter :a, :b

  def initialize(@a : Int32, @b : Int32 = @a)
  end
end

a = A.new a: 1
puts a.b # => 0  (NO LONGER OUTPUTS 0)

Gives an error now

$ crystal test.cr
Showing last frame. Use --error-trace for full trace.

Error: instance variable '@a' of A must be Int32, not Nil

Instance variable '@a' was used before it was initialized in one of the 'initialize' methods, rendering it nilable

The original example shows the same error as shown above.

And Brian's example works as it did before and outputs 0.

Indeed, it seems to be fixed now, or at least it doesn't lead to incorrect code anymore.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

asterite picture asterite  路  60Comments

chocolateboy picture chocolateboy  路  87Comments

benoist picture benoist  路  59Comments

asterite picture asterite  路  78Comments

asterite picture asterite  路  139Comments