1
0
Fork 0
mirror of https://github.com/ton-blockchain/ton synced 2025-03-09 15:40:10 +00:00
ton/tolk/pipe-detect-unreachable.cpp
tolk-vm f3e620f48c
[Tolk] Nullable types T? and null safety
This commit introduces nullable types `T?` that are
distinct from non-nullable `T`.
Example: `int?` (int or null) and `int` are different now.
Previously, `null` could be assigned to any primitive type.
Now, it can be assigned only to `T?`.

A non-null assertion operator `!` was also introduced,
similar to `!` in TypeScript and `!!` in Kotlin.

If `int?` still occupies 1 stack slot, `(int,int)?` and
other nullable tensors occupy N+1 slots, the last for
"null precedence". `v == null` actually compares that slot.
Assigning `(int,int)` to `(int,int)?` implicitly creates
a null presence slot. Assigning `null` to `(int,int)?` widens
this null value to 3 slots. This is called "type transitioning".

All stdlib functions prototypes have been updated to reflect
whether they return/accept a nullable or a strict value.

This commit also contains refactoring from `const FunctionData*`
to `FunctionPtr` and similar.
2025-02-28 16:41:41 +03:00

138 lines
5.6 KiB
C++

/*
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 <http://www.gnu.org/licenses/>.
*/
#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<ast_sequence>());
case ast_return_statement: return always_returns(v->as<ast_return_statement>());
case ast_throw_statement: return always_returns(v->as<ast_throw_statement>());
case ast_function_call: return always_returns(v->as<ast_function_call>());
case ast_repeat_statement: return always_returns(v->as<ast_repeat_statement>());
case ast_while_statement: return always_returns(v->as<ast_while_statement>());
case ast_do_while_statement: return always_returns(v->as<ast_do_while_statement>());
case ast_try_catch_statement: return always_returns(v->as<ast_try_catch_statement>());
case ast_if_statement: return always_returns(v->as<ast_if_statement>());
default:
// unhandled statements (like assert) and statement expressions
return false;
}
}
bool always_returns(V<ast_sequence> 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<ast_return_statement> v) {
// quite obvious: `return expr` interrupts control flow
return true;
}
static bool always_returns([[maybe_unused]] V<ast_throw_statement> 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<ast_function_call> 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<ast_repeat_statement> v) {
return always_returns(v->get_body());
}
bool always_returns(V<ast_while_statement> v) {
return always_returns(v->get_body());
}
bool always_returns(V<ast_do_while_statement> v) {
return always_returns(v->get_body());
}
bool always_returns(V<ast_try_catch_statement> v) {
return always_returns(v->get_try_body()) && always_returns(v->get_catch_body());
}
bool always_returns(V<ast_if_statement> v) {
return always_returns(v->get_if_body()) && always_returns(v->get_else_body());
}
public:
static bool should_visit_function(FunctionPtr fun_ref) {
return fun_ref->is_code_function() && !fun_ref->is_generic_function();
}
void start_visiting_function(FunctionPtr fun_ref, V<ast_function_declaration> v_function) {
bool control_flow_reaches_end = !always_returns(v_function->get_body()->as<ast_sequence>());
if (control_flow_reaches_end) {
fun_ref->mutate()->assign_is_implicit_return();
}
}
};
void pipeline_detect_unreachable_statements() {
visit_ast_of_all_functions<UnreachableStatementsDetectVisitor>();
}
void pipeline_detect_unreachable_statements(FunctionPtr fun_ref) {
UnreachableStatementsDetectVisitor visitor;
if (UnreachableStatementsDetectVisitor::should_visit_function(fun_ref)) {
visitor.start_visiting_function(fun_ref, fun_ref->ast_root->as<ast_function_declaration>());
}
}
} // namespace tolk