/* This file is part of TON Blockchain source code. TON Blockchain is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. TON Blockchain is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with TON Blockchain. If not, see . */ #include "tolk.h" #include "ast.h" #include "ast-visitor.h" /* * This pipe does two things: * 1) detects unreachable code and prints warnings about it * example: `fun main() { if(1){return;}else{return;} var x = 0; }` — var is unreachable * 2) if control flow reaches end of function, store a flag to insert an implicit return * example: `fun main() { assert(...); }` — has an implicit `return ()` statement before a brace * * Note, that it does not delete unreachable code, only prints warnings. * Actual deleting is done much later (in "legacy" part), after AST is converted to Op. * * Note, that it's not CFG, it's just a shallow reachability detection. * In the future, a true CFG should be introduced. For instance, in order to have nullable types, * I'll need to implement smart casts. Then I'll think of a complicated granular control flow graph, * considering data flow and exceptions (built before type inferring, of course), * and detecting unreachable code will be a part of it. */ namespace tolk { class UnreachableStatementsDetectVisitor final { bool always_returns(AnyV v) { switch (v->type) { case ast_sequence: return always_returns(v->as()); case ast_return_statement: return always_returns(v->as()); case ast_throw_statement: return always_returns(v->as()); case ast_function_call: return always_returns(v->as()); case ast_repeat_statement: return always_returns(v->as()); case ast_while_statement: return always_returns(v->as()); case ast_do_while_statement: return always_returns(v->as()); case ast_try_catch_statement: return always_returns(v->as()); case ast_if_statement: return always_returns(v->as()); default: // unhandled statements (like assert) and statement expressions return false; } } bool always_returns(V v) { bool always = false; for (AnyV item : v->get_items()) { if (always && item->type != ast_empty_statement) { item->loc.show_warning("unreachable code"); break; } always |= always_returns(item); } return always; } static bool always_returns([[maybe_unused]] V v) { // quite obvious: `return expr` interrupts control flow return true; } static bool always_returns([[maybe_unused]] V v) { // todo `throw excNo` currently does not interrupt control flow // (in other words, `throw 1; something` - something is reachable) // the reason is that internally it's transformed to a call of built-in function __throw(), // which is a regular function, like __throw_if() or loadInt() // to fix this later on, it should be deeper, introducing Op::_Throw for example, // to make intermediate representations and stack optimizer also be aware that after it there is unreachable return false; } static bool always_returns([[maybe_unused]] V v) { // neither annotations like @noreturn nor auto-detection of always-throwing functions also doesn't exist // in order to do this in the future, it should be handled not only at AST/CFG level, // but inside Op and low-level optimizer (at least if reachability detection is not moved out of there) // see comments for `throw` above, similar to this case return false; } bool always_returns(V v) { return always_returns(v->get_body()); } bool always_returns(V v) { return always_returns(v->get_body()); } bool always_returns(V v) { return always_returns(v->get_body()); } bool always_returns(V v) { return always_returns(v->get_try_body()) && always_returns(v->get_catch_body()); } bool always_returns(V v) { return always_returns(v->get_if_body()) && always_returns(v->get_else_body()); } public: static bool should_visit_function(const FunctionData* fun_ref) { return fun_ref->is_code_function() && !fun_ref->is_generic_function(); } void start_visiting_function(const FunctionData* fun_ref, V v_function) { bool control_flow_reaches_end = !always_returns(v_function->get_body()->as()); if (control_flow_reaches_end) { fun_ref->mutate()->assign_is_implicit_return(); } } }; void pipeline_detect_unreachable_statements() { visit_ast_of_all_functions(); } void pipeline_detect_unreachable_statements(const FunctionData* fun_ref) { UnreachableStatementsDetectVisitor visitor; if (UnreachableStatementsDetectVisitor::should_visit_function(fun_ref)) { visitor.start_visiting_function(fun_ref, fun_ref->ast_root->as()); } } } // namespace tolk