From 96f4c874e911d24fea51587e329cafd5c3c58451 Mon Sep 17 00:00:00 2001 From: mpeterv Date: Tue, 21 Jul 2015 10:34:06 +0300 Subject: [PATCH] Produce less flow graph edges for literal loop conditions Improves local resolution and unreachable code detection around infinite loops like 'while true do ... end'. --- spec/check_spec.lua | 53 +++++++++++++++++++------ spec/linearize_spec.lua | 79 ++++++++++++++++++++++++++++++++++++-- src/luacheck/linearize.lua | 20 ++++++++-- 3 files changed, 134 insertions(+), 18 deletions(-) diff --git a/spec/check_spec.lua b/spec/check_spec.lua index b0fecc84..6e9fb1aa 100644 --- a/spec/check_spec.lua +++ b/spec/check_spec.lua @@ -120,7 +120,7 @@ for i in pairs{} do end {code = "113", name = "print", line = 9, column = 1, end_column = 5} }, check[[ local a -if true then +if ... then a = 2 else a = 3 @@ -352,7 +352,7 @@ goto fail {code = "511", line = 2, column = 1, end_column = 2} }, check[[ do return end -if true then return 6 end +if ... then return 6 end return 3 ]]) @@ -360,13 +360,13 @@ return 3 {code = "511", line = 7, column = 1, end_column = 2}, {code = "511", line = 13, column = 1, end_column = 6} }, check[[ -if false then +if ... then return 4 else return 6 end -if true then +if ... then return 7 else return 8 @@ -376,6 +376,37 @@ return 3 ]]) end) + it("detects unreachable code with literal conditions", function() + assert.same({ + {code = "511", line = 4, column = 1, end_column = 6} + }, check[[ +while true do + (...)() +end +return + ]]) + + assert.same({}, check[[ +repeat + if ... then + break + end +until false +return + ]]) + + assert.same({ + {code = "511", line = 6, column = 1, end_column = 6} + }, check[[ +repeat + if nil then + break + end +until false +return + ]]) + end) + it("detects accessing uninitialized variables", function() assert.same({ {code = "113", name = "get", line = 6, column = 8, end_column = 10}, @@ -383,7 +414,7 @@ return 3 }, check[[ local a -if true then +if ... then a = 5 else a = get(a) @@ -423,22 +454,22 @@ a, b = 1, 2, 3; (...)(a, b) it("detects empty blocks", function() assert.same({ {code = "541", line = 1, column = 1, end_column = 2}, - {code = "542", line = 3, column = 9, end_column = 12}, - {code = "542", line = 5, column = 14, end_column = 17}, + {code = "542", line = 3, column = 8, end_column = 11}, + {code = "542", line = 5, column = 12, end_column = 15}, {code = "542", line = 7, column = 1, end_column = 4} }, check[[ do end -if true then +if ... then -elseif false then +elseif ... then else end -while true do end -repeat until true +while ... do end +repeat until ... ]]) end) diff --git a/spec/linearize_spec.lua b/spec/linearize_spec.lua index 7c7a205d..f41dc223 100644 --- a/spec/linearize_spec.lua +++ b/spec/linearize_spec.lua @@ -131,13 +131,13 @@ do print(foo) end]])) assert.equal([[ 1: Local ... (2..8) 2: Noop -3: Eval True +3: Eval Id 4: Cjump -> 9 5: Local s (6..6) 6: Eval Call 7: Noop 8: Jump -> 3]], get_line_as_string([[ -while true do +while cond do local s = io.read() print(s) end]])) @@ -147,12 +147,12 @@ end]])) 2: Noop 3: Local s (4..5) 4: Eval Call -5: Eval False +5: Eval Id 6: Cjump -> 3]], get_line_as_string([[ repeat local s = io.read() print(s) -until false]])) +until cond]])) assert.equal([[ 1: Local ... (2..9) @@ -182,6 +182,50 @@ for k, v in pairs(t) do end]])) end) + it("linearizes loops with literal condition correctly", function() + assert.equal([[ +1: Local ... (2..6) +2: Noop +3: Eval Number +4: Eval Call +5: Noop +6: Jump -> 3]], get_line_as_string([[ +while 1 do + foo() +end]])) + + assert.equal([[ +1: Local ... (2..7) +2: Noop +3: Eval False +4: Jump -> 8 +5: Eval Call +6: Noop +7: Jump -> 3]], get_line_as_string([[ +while false do + foo() +end]])) + + assert.equal([[ +1: Local ... (2..4) +2: Noop +3: Eval Call +4: Eval True]], get_line_as_string([[ +repeat + foo() +until true]])) + + assert.equal([[ +1: Local ... (2..5) +2: Noop +3: Eval Call +4: Eval Nil +5: Jump -> 3]], get_line_as_string([[ +repeat + foo() +until nil]])) + end) + it("linearizes nested loops and breaks correctly", function() assert.equal([[ 1: Local ... (2..24) @@ -255,6 +299,33 @@ if cond() then end]])) end) + it("linearizes if with literal condition correctly", function() + assert.equal([[ +1: Local ... (2..14) +2: Noop +3: Eval True +4: Noop +5: Eval Call +6: Cjump -> 9 +7: Eval Call +8: Jump -> 14 +9: Eval False +10: Jump -> 13 +11: Eval Call +12: Jump -> 14 +13: Eval Call +14: Jump -> 15]], get_line_as_string([[ +if true then + if cond() then + stmts() + elseif false then + stmts() + else + stmts() + end +end]])) + end) + it("linearizes gotos correctly", function() assert.equal([[ 1: Local ... (2..13) diff --git a/src/luacheck/linearize.lua b/src/luacheck/linearize.lua index 18701d51..c0a741f1 100644 --- a/src/luacheck/linearize.lua +++ b/src/luacheck/linearize.lua @@ -247,6 +247,20 @@ function LinState:emit_goto(name, is_conditional, location) table.insert(self.scopes.top.gotos, new_goto(name, jump, location)) end +local tag_to_boolean = { + Nil = false, False = false, + True = true, Number = true, String = true, Table = true, Function = true +} + +-- Emits goto that jumps to ::name:: if bool(cond_node) == false. +function LinState:emit_cond_goto(name, cond_node) + local cond_bool = tag_to_boolean[cond_node.tag] + + if cond_bool ~= true then + self:emit_goto(name, cond_bool ~= false) + end +end + function LinState:emit_noop(node, loop_end) self:emit(new_noop_item(node, loop_end)) end @@ -278,7 +292,7 @@ function LinState:emit_stmt_While(node) self:enter_scope() self:register_label("do") self:emit_expr(node[1]) - self:emit_goto("break", true) + self:emit_cond_goto("break", node[1]) self:emit_block(node[2]) self:emit_noop(node, true) self:emit_goto("do") @@ -294,7 +308,7 @@ function LinState:emit_stmt_Repeat(node) self:emit_stmts(node[1]) self:emit_expr(node[2]) self:leave_scope() - self:emit_goto("do", true) + self:emit_cond_goto("do", node[2]) self:register_label("break") self:leave_scope() end @@ -346,7 +360,7 @@ function LinState:emit_stmt_If(node) for i = 1, #node - 1, 2 do self:enter_scope() self:emit_expr(node[i]) - self:emit_goto("else", true) + self:emit_cond_goto("else", node[i]) self:check_empty_block(node[i + 1]) self:emit_block(node[i + 1]) self:emit_goto("end")