-// Copyright (C) 2021-2025 Free Software Foundation, Inc.
+// Copyright (C) 2025 Free Software Foundation, Inc.
// This file is part of GCC.
// <http://www.gnu.org/licenses/>.
#include "rust-readonly-check.h"
-#include "rust-tree.h"
-#include "rust-gcc.h"
-#include "print-tree.h"
+#include "rust-hir-expr.h"
+#include "rust-hir-node.h"
+#include "rust-hir-path.h"
+#include "rust-hir-map.h"
+#include "rust-hir-pattern.h"
+#include "rust-mapping-common.h"
+#include "rust-system.h"
+#include "rust-immutable-name-resolution-context.h"
+#include "rust-tyty.h"
namespace Rust {
-namespace Analysis {
+namespace HIR {
-static std::map<tree, int> assignment_map = {};
+static std::set<HirId> already_assigned_variables = {};
+
+ReadonlyChecker::ReadonlyChecker ()
+ : resolver (*Resolver::Resolver::get ()),
+ mappings (Analysis::Mappings::get ()),
+ context (*Resolver::TypeCheckContext::get ())
+{}
+
+void
+ReadonlyChecker::go (Crate &crate)
+{
+ for (auto &item : crate.get_items ())
+ item->accept_vis (*this);
+}
+
+void
+ReadonlyChecker::visit (AssignmentExpr &expr)
+{
+ Expr &lhs = expr.get_lhs ();
+ mutable_context.enter (expr.get_mappings ().get_hirid ());
+ lhs.accept_vis (*this);
+ mutable_context.exit ();
+}
-// ported over from c-family/c-warn.cc
void
-readonly_error (location_t loc, tree arg, enum lvalue_use use)
+ReadonlyChecker::visit (PathInExpression &expr)
{
- gcc_assert (use == lv_assign || use == lv_increment || use == lv_decrement
- || use == lv_asm);
- STRIP_ANY_LOCATION_WRAPPER (arg);
- /* Using this macro rather than (for example) arrays of messages
- ensures that all the format strings are checked at compile
- time. */
-#define READONLY_MSG(A, I, D, AS) \
- (use == lv_assign \
- ? (A) \
- : (use == lv_increment ? (I) : (use == lv_decrement ? (D) : (AS))))
- if (TREE_CODE (arg) == COMPONENT_REF)
+ if (!mutable_context.is_in_context ())
+ return;
+
+ NodeId ast_node_id = expr.get_mappings ().get_nodeid ();
+ NodeId def_id;
+
+ auto &nr_ctx
+ = Resolver2_0::ImmutableNameResolutionContext::get ().resolver ();
+ if (auto id = nr_ctx.lookup (ast_node_id))
+ def_id = *id;
+ else
+ return;
+
+ auto hir_id = mappings.lookup_node_to_hir (def_id);
+ if (!hir_id)
+ return;
+
+ // Check if the local variable is mutable.
+ auto maybe_pattern = mappings.lookup_hir_pattern (*hir_id);
+ if (maybe_pattern
+ && maybe_pattern.value ()->get_pattern_type ()
+ == HIR::Pattern::PatternType::IDENTIFIER)
+ check_variable (static_cast<IdentifierPattern *> (maybe_pattern.value ()),
+ expr.get_locus ());
+
+ // Check if the static item is mutable.
+ auto maybe_item = mappings.lookup_hir_item (*hir_id);
+ if (maybe_item
+ && maybe_item.value ()->get_item_kind () == HIR::Item::ItemKind::Static)
{
- if (TYPE_READONLY (TREE_TYPE (TREE_OPERAND (arg, 0))))
- error_at (loc,
- READONLY_MSG (G_ ("assignment of member "
- "%qD in read-only object"),
- G_ ("increment of member "
- "%qD in read-only object"),
- G_ ("decrement of member "
- "%qD in read-only object"),
- G_ ("member %qD in read-only object "
- "used as %<asm%> output")),
- TREE_OPERAND (arg, 1));
- else
- error_at (
- loc,
- READONLY_MSG (G_ ("assignment of read-only member %qD"),
- G_ ("increment of read-only member %qD"),
- G_ ("decrement of read-only member %qD"),
- G_ ("read-only member %qD used as %<asm%> output")),
- TREE_OPERAND (arg, 1));
+ auto static_item = static_cast<HIR::StaticItem *> (*maybe_item);
+ if (!static_item->is_mut ())
+ rust_error_at (expr.get_locus (),
+ "assignment of read-only location '%s'",
+ static_item->get_identifier ().as_string ().c_str ());
}
- else if (VAR_P (arg))
- error_at (loc,
- READONLY_MSG (G_ ("assignment of read-only variable %qD"),
- G_ ("increment of read-only variable %qD"),
- G_ ("decrement of read-only variable %qD"),
- G_ (
- "read-only variable %qD used as %<asm%> output")),
- arg);
- else if (TREE_CODE (arg) == PARM_DECL)
- error_at (loc,
- READONLY_MSG (G_ ("assignment of read-only parameter %qD"),
- G_ ("increment of read-only parameter %qD"),
- G_ ("decrement of read-only parameter %qD"),
- G_ (
- "read-only parameter %qD use as %<asm%> output")),
- arg);
- else if (TREE_CODE (arg) == RESULT_DECL)
+
+ // Check if the constant item is mutable.
+ if (maybe_item
+ && maybe_item.value ()->get_item_kind () == HIR::Item::ItemKind::Constant)
{
- error_at (loc,
- READONLY_MSG (G_ ("assignment of "
- "read-only named return value %qD"),
- G_ ("increment of "
- "read-only named return value %qD"),
- G_ ("decrement of "
- "read-only named return value %qD"),
- G_ ("read-only named return value %qD "
- "used as %<asm%>output")),
- arg);
+ auto const_item = static_cast<HIR::ConstantItem *> (*maybe_item);
+ rust_error_at (expr.get_locus (), "assignment of read-only location '%s'",
+ const_item->get_identifier ().as_string ().c_str ());
}
- else if (TREE_CODE (arg) == FUNCTION_DECL)
- error_at (loc,
- READONLY_MSG (G_ ("assignment of function %qD"),
- G_ ("increment of function %qD"),
- G_ ("decrement of function %qD"),
- G_ ("function %qD used as %<asm%> output")),
- arg);
- else
- error_at (loc,
- READONLY_MSG (G_ ("assignment of read-only location %qE"),
- G_ ("increment of read-only location %qE"),
- G_ ("decrement of read-only location %qE"),
- G_ (
- "read-only location %qE used as %<asm%> output")),
- arg);
}
-static void
-emit_error (tree *t, tree lhs, enum lvalue_use use)
+void
+ReadonlyChecker::check_variable (IdentifierPattern *pattern,
+ location_t assigned_loc)
{
- readonly_error (EXPR_LOCATION (*t), lhs, use);
- TREE_OPERAND (*t, 0) = error_mark_node;
+ if (!mutable_context.is_in_context ())
+ return;
+
+ TyTy::BaseType *type;
+ if (context.lookup_type (pattern->get_mappings ().get_hirid (), &type)
+ && is_mutable_type (type))
+ return;
+ if (pattern->is_mut ())
+ return;
+
+ auto hir_id = pattern->get_mappings ().get_hirid ();
+ if (already_assigned_variables.count (hir_id) > 0)
+ rust_error_at (assigned_loc, "assignment of read-only variable '%s'",
+ pattern->as_string ().c_str ());
+ already_assigned_variables.insert (hir_id);
}
-static void
-check_modify_expr (tree *t)
+void
+ReadonlyChecker::collect_assignment_identifier (IdentifierPattern &pattern,
+ bool has_init_expr)
{
- tree lhs = TREE_OPERAND (*t, 0);
- if (TREE_CODE (lhs) == ARRAY_REF || TREE_CODE (lhs) == COMPONENT_REF)
- lhs = TREE_OPERAND (lhs, 0);
-
- tree lhs_type = TREE_TYPE (lhs);
- if (TYPE_READONLY (lhs_type) || TREE_READONLY (lhs) || TREE_CONSTANT (lhs))
+ if (has_init_expr)
{
- if (TREE_CODE (lhs) != VAR_DECL)
- emit_error (t, lhs, lv_assign);
- else if (!DECL_ARTIFICIAL (lhs))
- {
- if (DECL_INITIAL (lhs) != NULL)
- emit_error (t, lhs, lv_assign);
- else
- {
- if (assignment_map.find (lhs) == assignment_map.end ())
- {
- assignment_map.insert ({lhs, 0});
- }
- assignment_map[lhs]++;
-
- if (assignment_map[lhs] > 1)
- emit_error (t, lhs, lv_assign);
- }
- }
+ HirId pattern_id = pattern.get_mappings ().get_hirid ();
+ already_assigned_variables.insert (pattern_id);
}
}
-static void
-check_decl (tree *t)
+void
+ReadonlyChecker::collect_assignment_tuple (TuplePattern &tuple_pattern,
+ bool has_init_expr)
{
- switch (TREE_CODE (*t))
+ switch (tuple_pattern.get_items ().get_item_type ())
{
- case MODIFY_EXPR:
- check_modify_expr (t);
+ case HIR::TuplePatternItems::ItemType::NO_REST:
+ {
+ auto &items = static_cast<HIR::TuplePatternItemsNoRest &> (
+ tuple_pattern.get_items ());
+ for (auto &sub : items.get_patterns ())
+ {
+ collect_assignment (*sub, has_init_expr);
+ }
+ }
+ break;
+ default:
break;
+ }
+}
+void
+ReadonlyChecker::collect_assignment (Pattern &pattern, bool has_init_expr)
+{
+ switch (pattern.get_pattern_type ())
+ {
+ case HIR::Pattern::PatternType::IDENTIFIER:
+ {
+ collect_assignment_identifier (static_cast<IdentifierPattern &> (
+ pattern),
+ has_init_expr);
+ }
+ break;
+ case HIR::Pattern::PatternType::TUPLE:
+ {
+ auto &tuple_pattern = static_cast<HIR::TuplePattern &> (pattern);
+ collect_assignment_tuple (tuple_pattern, has_init_expr);
+ }
+ break;
default:
break;
}
}
-static tree
-readonly_walk_fn (tree *t, int *, void *)
+void
+ReadonlyChecker::visit (LetStmt &stmt)
+{
+ HIR::Pattern &pattern = stmt.get_pattern ();
+ collect_assignment (pattern, stmt.has_init_expr ());
+}
+
+void
+ReadonlyChecker::visit (FieldAccessExpr &expr)
{
- check_decl (t);
- return NULL_TREE;
+ if (mutable_context.is_in_context ())
+ {
+ expr.get_receiver_expr ().accept_vis (*this);
+ }
}
void
-ReadonlyCheck::Lint (Compile::Context &ctx)
+ReadonlyChecker::visit (TupleIndexExpr &expr)
{
- assignment_map.clear ();
- for (auto &fndecl : ctx.get_func_decls ())
+ if (mutable_context.is_in_context ())
{
- for (tree p = DECL_ARGUMENTS (fndecl); p != NULL_TREE; p = DECL_CHAIN (p))
- {
- check_decl (&p);
- }
+ expr.get_tuple_expr ().accept_vis (*this);
+ }
+}
- walk_tree_without_duplicates (&DECL_SAVED_TREE (fndecl),
- &readonly_walk_fn, &ctx);
+void
+ReadonlyChecker::visit (ArrayIndexExpr &expr)
+{
+ if (mutable_context.is_in_context ())
+ {
+ expr.get_array_expr ().accept_vis (*this);
}
+}
- assignment_map.clear ();
- for (auto &var : ctx.get_var_decls ())
+void
+ReadonlyChecker::visit (TupleExpr &expr)
+{
+ if (mutable_context.is_in_context ())
{
- tree decl = var->get_decl ();
- check_decl (&decl);
+ // TODO: Add check for tuple expression
}
+}
- assignment_map.clear ();
- for (auto &const_decl : ctx.get_const_decls ())
+void
+ReadonlyChecker::visit (LiteralExpr &expr)
+{
+ if (mutable_context.is_in_context ())
{
- check_decl (&const_decl);
+ rust_error_at (expr.get_locus (), "assignment of read-only location");
}
}
-} // namespace Analysis
+void
+ReadonlyChecker::visit (DereferenceExpr &expr)
+{
+ if (!mutable_context.is_in_context ())
+ return;
+ TyTy::BaseType *to_deref_type;
+ auto to_deref = expr.get_expr ().get_mappings ().get_hirid ();
+ if (!context.lookup_type (to_deref, &to_deref_type))
+ return;
+ if (!is_mutable_type (to_deref_type))
+ rust_error_at (expr.get_locus (), "assignment of read-only location");
+}
+
+bool
+ReadonlyChecker::is_mutable_type (TyTy::BaseType *type)
+{
+ if (type->get_kind () == TyTy::TypeKind::REF)
+ return static_cast<TyTy::ReferenceType *> (type)->is_mutable ();
+ if (type->get_kind () == TyTy::TypeKind::POINTER)
+ return static_cast<TyTy::PointerType *> (type)->is_mutable ();
+ return false;
+}
+} // namespace HIR
} // namespace Rust
+++ /dev/null
-// Copyright (C) 2025 Free Software Foundation, Inc.
-
-// This file is part of GCC.
-
-// GCC 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 3, or (at your option) any later
-// version.
-
-// GCC 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 GCC; see the file COPYING3. If not see
-// <http://www.gnu.org/licenses/>.
-
-#include "rust-readonly-check2.h"
-#include "rust-hir-expr.h"
-#include "rust-hir-node.h"
-#include "rust-hir-path.h"
-#include "rust-hir-map.h"
-#include "rust-hir-pattern.h"
-#include "rust-mapping-common.h"
-#include "rust-system.h"
-#include "rust-immutable-name-resolution-context.h"
-#include "rust-tyty.h"
-
-namespace Rust {
-namespace HIR {
-
-static std::set<HirId> already_assigned_variables = {};
-
-ReadonlyChecker::ReadonlyChecker ()
- : resolver (*Resolver::Resolver::get ()),
- mappings (Analysis::Mappings::get ()),
- context (*Resolver::TypeCheckContext::get ())
-{}
-
-void
-ReadonlyChecker::go (Crate &crate)
-{
- for (auto &item : crate.get_items ())
- item->accept_vis (*this);
-}
-
-void
-ReadonlyChecker::visit (AssignmentExpr &expr)
-{
- Expr &lhs = expr.get_lhs ();
- mutable_context.enter (expr.get_mappings ().get_hirid ());
- lhs.accept_vis (*this);
- mutable_context.exit ();
-}
-
-void
-ReadonlyChecker::visit (PathInExpression &expr)
-{
- if (!mutable_context.is_in_context ())
- return;
-
- NodeId ast_node_id = expr.get_mappings ().get_nodeid ();
- NodeId def_id;
-
- auto &nr_ctx
- = Resolver2_0::ImmutableNameResolutionContext::get ().resolver ();
- if (auto id = nr_ctx.lookup (ast_node_id))
- def_id = *id;
- else
- return;
-
- auto hir_id = mappings.lookup_node_to_hir (def_id);
- if (!hir_id)
- return;
-
- // Check if the local variable is mutable.
- auto maybe_pattern = mappings.lookup_hir_pattern (*hir_id);
- if (maybe_pattern
- && maybe_pattern.value ()->get_pattern_type ()
- == HIR::Pattern::PatternType::IDENTIFIER)
- check_variable (static_cast<IdentifierPattern *> (maybe_pattern.value ()),
- expr.get_locus ());
-
- // Check if the static item is mutable.
- auto maybe_item = mappings.lookup_hir_item (*hir_id);
- if (maybe_item
- && maybe_item.value ()->get_item_kind () == HIR::Item::ItemKind::Static)
- {
- auto static_item = static_cast<HIR::StaticItem *> (*maybe_item);
- if (!static_item->is_mut ())
- rust_error_at (expr.get_locus (),
- "assignment of read-only location '%s'",
- static_item->get_identifier ().as_string ().c_str ());
- }
-
- // Check if the constant item is mutable.
- if (maybe_item
- && maybe_item.value ()->get_item_kind () == HIR::Item::ItemKind::Constant)
- {
- auto const_item = static_cast<HIR::ConstantItem *> (*maybe_item);
- rust_error_at (expr.get_locus (), "assignment of read-only location '%s'",
- const_item->get_identifier ().as_string ().c_str ());
- }
-}
-
-void
-ReadonlyChecker::check_variable (IdentifierPattern *pattern,
- location_t assigned_loc)
-{
- if (!mutable_context.is_in_context ())
- return;
-
- TyTy::BaseType *type;
- if (context.lookup_type (pattern->get_mappings ().get_hirid (), &type)
- && is_mutable_type (type))
- return;
- if (pattern->is_mut ())
- return;
-
- auto hir_id = pattern->get_mappings ().get_hirid ();
- if (already_assigned_variables.count (hir_id) > 0)
- rust_error_at (assigned_loc, "assignment of read-only variable '%s'",
- pattern->as_string ().c_str ());
- already_assigned_variables.insert (hir_id);
-}
-
-void
-ReadonlyChecker::collect_assignment_identifier (IdentifierPattern &pattern,
- bool has_init_expr)
-{
- if (has_init_expr)
- {
- HirId pattern_id = pattern.get_mappings ().get_hirid ();
- already_assigned_variables.insert (pattern_id);
- }
-}
-
-void
-ReadonlyChecker::collect_assignment_tuple (TuplePattern &tuple_pattern,
- bool has_init_expr)
-{
- switch (tuple_pattern.get_items ().get_item_type ())
- {
- case HIR::TuplePatternItems::ItemType::NO_REST:
- {
- auto &items = static_cast<HIR::TuplePatternItemsNoRest &> (
- tuple_pattern.get_items ());
- for (auto &sub : items.get_patterns ())
- {
- collect_assignment (*sub, has_init_expr);
- }
- }
- break;
- default:
- break;
- }
-}
-
-void
-ReadonlyChecker::collect_assignment (Pattern &pattern, bool has_init_expr)
-{
- switch (pattern.get_pattern_type ())
- {
- case HIR::Pattern::PatternType::IDENTIFIER:
- {
- collect_assignment_identifier (static_cast<IdentifierPattern &> (
- pattern),
- has_init_expr);
- }
- break;
- case HIR::Pattern::PatternType::TUPLE:
- {
- auto &tuple_pattern = static_cast<HIR::TuplePattern &> (pattern);
- collect_assignment_tuple (tuple_pattern, has_init_expr);
- }
- break;
- default:
- break;
- }
-}
-
-void
-ReadonlyChecker::visit (LetStmt &stmt)
-{
- HIR::Pattern &pattern = stmt.get_pattern ();
- collect_assignment (pattern, stmt.has_init_expr ());
-}
-
-void
-ReadonlyChecker::visit (FieldAccessExpr &expr)
-{
- if (mutable_context.is_in_context ())
- {
- expr.get_receiver_expr ().accept_vis (*this);
- }
-}
-
-void
-ReadonlyChecker::visit (TupleIndexExpr &expr)
-{
- if (mutable_context.is_in_context ())
- {
- expr.get_tuple_expr ().accept_vis (*this);
- }
-}
-
-void
-ReadonlyChecker::visit (ArrayIndexExpr &expr)
-{
- if (mutable_context.is_in_context ())
- {
- expr.get_array_expr ().accept_vis (*this);
- }
-}
-
-void
-ReadonlyChecker::visit (TupleExpr &expr)
-{
- if (mutable_context.is_in_context ())
- {
- // TODO: Add check for tuple expression
- }
-}
-
-void
-ReadonlyChecker::visit (LiteralExpr &expr)
-{
- if (mutable_context.is_in_context ())
- {
- rust_error_at (expr.get_locus (), "assignment of read-only location");
- }
-}
-
-void
-ReadonlyChecker::visit (DereferenceExpr &expr)
-{
- if (!mutable_context.is_in_context ())
- return;
- TyTy::BaseType *to_deref_type;
- auto to_deref = expr.get_expr ().get_mappings ().get_hirid ();
- if (!context.lookup_type (to_deref, &to_deref_type))
- return;
- if (!is_mutable_type (to_deref_type))
- rust_error_at (expr.get_locus (), "assignment of read-only location");
-}
-
-bool
-ReadonlyChecker::is_mutable_type (TyTy::BaseType *type)
-{
- if (type->get_kind () == TyTy::TypeKind::REF)
- return static_cast<TyTy::ReferenceType *> (type)->is_mutable ();
- if (type->get_kind () == TyTy::TypeKind::POINTER)
- return static_cast<TyTy::PointerType *> (type)->is_mutable ();
- return false;
-}
-} // namespace HIR
-} // namespace Rust