Skip to content

Commit 9965a9b

Browse files
makenowjustMartin Verzilli
authored and
Martin Verzilli
committedSep 16, 2017
Semantic: disallow usage of return/break/next inside ensure (#4486)
* Semantic: refactor control statement inside ensure detection Not use Array for stack and add utility method `with_block_kind`, by #4486 (comment). Correct `return` statement error message to fit other `return` statement messages. * Remove URL from error messages #4486 (comment)
1 parent b14d8c7 commit 9965a9b

File tree

2 files changed

+231
-3
lines changed

2 files changed

+231
-3
lines changed
 

Diff for: ‎spec/compiler/semantic/exception_spec.cr

+178
Original file line numberDiff line numberDiff line change
@@ -476,4 +476,182 @@ describe "Semantic: exception" do
476476
program = result.program
477477
program.vars["foo"].type.should be(program.nilable program.int32)
478478
end
479+
480+
it "can't return from ensure (#4470)" do
481+
assert_error(%(
482+
def foo
483+
return 1
484+
ensure
485+
return 2
486+
end
487+
488+
foo
489+
), "can't return from ensure")
490+
end
491+
492+
it "can't return from block inside ensure (#4470)" do
493+
assert_error(%(
494+
def once
495+
yield
496+
end
497+
498+
def foo
499+
return 1
500+
ensure
501+
once do
502+
return 2
503+
end
504+
end
505+
506+
foo
507+
), "can't return from ensure")
508+
end
509+
510+
it "can't return from while inside ensure (#4470)" do
511+
assert_error(%(
512+
def foo
513+
return 1
514+
ensure
515+
while true
516+
return 2
517+
end
518+
end
519+
520+
foo
521+
), "can't return from ensure")
522+
end
523+
524+
it "can't use break inside while inside ensure (#4470)" do
525+
assert_error(%(
526+
while true
527+
begin
528+
break
529+
ensure
530+
break
531+
end
532+
end
533+
), "can't use break inside ensure")
534+
end
535+
536+
it "can use break inside while inside ensure (#4470)" do
537+
assert_type(%(
538+
while true
539+
begin
540+
break
541+
ensure
542+
while true
543+
break
544+
end
545+
end
546+
end
547+
)) { nil_type }
548+
end
549+
550+
it "can't use break inside block inside ensure (#4470)" do
551+
assert_error(%(
552+
def loop
553+
while true
554+
yield
555+
end
556+
end
557+
558+
loop do
559+
begin
560+
break
561+
ensure
562+
break
563+
end
564+
end
565+
), "can't use break inside ensure")
566+
end
567+
568+
it "can use break inside block inside ensure (#4470)" do
569+
assert_type(%(
570+
def loop
571+
while true
572+
yield
573+
end
574+
end
575+
576+
loop do
577+
begin
578+
break
579+
ensure
580+
loop do
581+
break
582+
end
583+
end
584+
end
585+
)) { nil_type }
586+
end
587+
588+
it "can't use next inside while inside ensure (#4470)" do
589+
assert_error(%(
590+
while true
591+
begin
592+
break
593+
ensure
594+
next
595+
end
596+
end
597+
), "can't use next inside ensure")
598+
end
599+
600+
it "can't use next inside block inside ensure (#4470)" do
601+
assert_error(%(
602+
def loop
603+
while true
604+
yield
605+
end
606+
end
607+
608+
loop do
609+
begin
610+
break
611+
ensure
612+
next
613+
end
614+
end
615+
), "can't use next inside ensure")
616+
end
617+
618+
it "can use next inside while inside ensure (#4470)" do
619+
assert_type(%(
620+
while true
621+
begin
622+
break
623+
ensure
624+
a = 0
625+
while a < 1
626+
a = 1
627+
next
628+
end
629+
end
630+
end
631+
)) { nil_type }
632+
end
633+
634+
it "can use next inside block inside ensure (#4470)" do
635+
assert_type(%(
636+
def loop
637+
while true
638+
yield
639+
end
640+
end
641+
642+
def once
643+
yield
644+
end
645+
646+
loop do
647+
begin
648+
break
649+
ensure
650+
once do
651+
next
652+
end
653+
end
654+
end
655+
)) { nil_type }
656+
end
479657
end

Diff for: ‎src/compiler/crystal/semantic/main_visitor.cr

+53-3
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,26 @@ module Crystal
6767
property is_initialize : Bool
6868
property exception_handler_vars : MetaVars? = nil
6969

70+
# It means the last block kind, that is one of `block`, `while` and
71+
# `ensure`. It is used to detect `break` or `next` from `ensure`.
72+
#
73+
# ```
74+
# begin
75+
# # `last_block_kind == nil`
76+
# ensure
77+
# # `last_block_kind == :ensure`
78+
# while true
79+
# # `last_block_kind == :while`
80+
# end
81+
# loop do
82+
# # `last_block_kind == :block`
83+
# end
84+
# # `last_block_kind == :ensure`
85+
# end
86+
# ```
87+
property last_block_kind : Symbol?
88+
property? inside_ensure : Bool = false
89+
7090
@unreachable = false
7191
@is_initialize = false
7292
@in_type_args = 0
@@ -1031,6 +1051,9 @@ module Crystal
10311051
block_visitor.path_lookup = path_lookup || current_type
10321052
block_visitor.block_nest = @block_nest
10331053

1054+
block_visitor.last_block_kind = :block
1055+
block_visitor.inside_ensure = inside_ensure?
1056+
10341057
node.body.accept block_visitor
10351058

10361059
@block_nest -= 1
@@ -1628,6 +1651,10 @@ module Crystal
16281651
end
16291652

16301653
def visit(node : Return)
1654+
if inside_ensure?
1655+
node.raise "can't return from ensure"
1656+
end
1657+
16311658
typed_def = @typed_def || node.raise("can't return from top level")
16321659

16331660
if typed_def.captured_block?
@@ -1985,7 +2012,10 @@ module Crystal
19852012
@block, old_block = nil, @block
19862013

19872014
@while_stack.push node
1988-
node.body.accept self
2015+
2016+
with_block_kind :while do
2017+
node.body.accept self
2018+
end
19892019

19902020
endless_while = node.cond.true_literal?
19912021
merge_while_vars node.cond, endless_while, before_cond_vars_copy, before_cond_vars, after_cond_vars, @vars, node.break_vars
@@ -2149,6 +2179,10 @@ module Crystal
21492179
end
21502180

21512181
def end_visit(node : Break)
2182+
if last_block_kind == :ensure
2183+
node.raise "can't use break inside ensure"
2184+
end
2185+
21522186
if block = @block
21532187
node.target = block.call.not_nil!
21542188

@@ -2174,6 +2208,10 @@ module Crystal
21742208
end
21752209

21762210
def end_visit(node : Next)
2211+
if last_block_kind == :ensure
2212+
node.raise "can't use next inside ensure"
2213+
end
2214+
21772215
if block = @block
21782216
node.target = block
21792217

@@ -2200,6 +2238,14 @@ module Crystal
22002238
@unreachable = true
22012239
end
22022240

2241+
def with_block_kind(kind)
2242+
old_block_kind, @last_block_kind = last_block_kind, kind
2243+
old_inside_ensure, @inside_ensure = @inside_ensure, @inside_ensure || kind == :ensure
2244+
yield
2245+
@last_block_kind = old_block_kind
2246+
@inside_ensure = old_inside_ensure
2247+
end
2248+
22032249
def visit(node : Primitive)
22042250
case node.name
22052251
when "binary"
@@ -2599,7 +2645,9 @@ module Crystal
25992645
merge_rescue_vars exception_handler_vars, all_rescue_vars
26002646

26012647
# And then accept the ensure part
2602-
node.ensure.try &.accept self
2648+
with_block_kind :ensure do
2649+
node.ensure.try &.accept self
2650+
end
26032651
end
26042652
end
26052653

@@ -2619,7 +2667,9 @@ module Crystal
26192667

26202668
before_ensure_vars = @vars.dup
26212669

2622-
node_ensure.accept self
2670+
with_block_kind :ensure do
2671+
node_ensure.accept self
2672+
end
26232673

26242674
@vars = after_handler_vars
26252675

0 commit comments

Comments
 (0)
Please sign in to comment.