Skip to content

Commit 02d4db3

Browse files
author
Ary Borenszweig
committedDec 15, 2016
Fixed #3706: rescue in initialize can lead to uninitialized instance variables
1 parent 366aec3 commit 02d4db3

File tree

3 files changed

+42
-47
lines changed

3 files changed

+42
-47
lines changed
 

‎spec/compiler/semantic/exception_spec.cr

+30-46
Original file line numberDiff line numberDiff line change
@@ -355,52 +355,6 @@ describe "Semantic: exception" do
355355
)) { int32 }
356356
end
357357

358-
it "doesn't type instance variable if initialized inside begin and rescue raises" do
359-
assert_type(%(
360-
require "prelude"
361-
362-
class Foo
363-
def initialize
364-
begin
365-
@bar = 1
366-
rescue
367-
raise "OH NO"
368-
end
369-
end
370-
371-
def bar
372-
@bar
373-
end
374-
end
375-
376-
foo = Foo.new
377-
foo.bar
378-
)) { int32 }
379-
end
380-
381-
it "doesn't type instance variable if initialized inside begin and in rescue" do
382-
assert_type(%(
383-
require "prelude"
384-
385-
class Foo
386-
def initialize
387-
begin
388-
@bar = 1
389-
rescue
390-
@bar = 2
391-
end
392-
end
393-
394-
def bar
395-
@bar
396-
end
397-
end
398-
399-
foo = Foo.new
400-
foo.bar
401-
)) { int32 }
402-
end
403-
404358
it "correctly types #1988" do
405359
assert_type(%(
406360
begin
@@ -445,4 +399,34 @@ describe "Semantic: exception" do
445399
var
446400
)) { union_of(int32, string) }
447401
end
402+
403+
it "marks instance variable as nilable if assigned inside rescue inside initialize" do
404+
assert_error %(
405+
require "prelude"
406+
407+
def some_method
408+
if rand < 0.999999999
409+
raise "OH NO"
410+
else
411+
2
412+
end
413+
end
414+
415+
class Coco < Exception
416+
def initialize(@x : Foo)
417+
end
418+
end
419+
420+
class Foo
421+
def initialize
422+
@x = 1
Has conversations. Original line has conversations.
423+
rescue
424+
raise Coco.new(self)
425+
end
426+
end
427+
428+
Foo.new
429+
),
430+
"instance variable '@x' of Foo must be Int32, not Nil"
431+
end
448432
end

‎src/compiler/crystal/semantic/exception.cr

+2
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,8 @@ module Crystal
232232
io << colorize("instance variable '#{nil_reason.name}' was used before it was initialized in one of the 'initialize' methods, rendering it nilable").bold
233233
when :used_self_before_initialized
234234
io << colorize("'self' was used before initializing instance variable '#{nil_reason.name}', rendering it nilable").bold
235+
when :initialized_in_rescue
236+
io << colorize("instance variable '#{nil_reason.name}' is initialized inside a begin-rescue, so it can potentially be left uninitialized if an exception is raised and rescued").bold
235237
end
236238
end
237239

‎src/compiler/crystal/semantic/main_visitor.cr

+10-1
Original file line numberDiff line numberDiff line change
@@ -2490,6 +2490,15 @@ module Crystal
24902490
# was raised before assigning any of the vars.
24912491
exception_handler_vars.each do |name, var|
24922492
unless before_body_vars[name]?
2493+
# Instance variables inside the body must be marked as nil
2494+
if name.starts_with?('@')
2495+
ivar = scope.lookup_instance_var(name)
2496+
unless ivar.type.includes_type?(@program.nil_var)
2497+
ivar.nil_reason = NilReason.new(name, :initialized_in_rescue)
2498+
ivar.bind_to @program.nil_var
2499+
end
2500+
end
2501+
24932502
var.nil_if_read = true
24942503
end
24952504
end
@@ -2549,7 +2558,7 @@ module Crystal
25492558
end
25502559

25512560
# However, those previous variables can't be nil afterwards:
2552-
# if an exception was raised then we won't running the code
2561+
# if an exception was raised then we won't be running the code
25532562
# after the ensure clause, so variables don't matter. But if
25542563
# an exception was not raised then all variables were declared
25552564
# successfully.

0 commit comments

Comments
 (0)
Please sign in to comment.