https://gcc.gnu.org/g:04effdd023ea15463a5fd1c31e00ef61469489a9
commit r16-3012-g04effdd023ea15463a5fd1c31e00ef61469489a9 Author: Ryutaro Okada <1015ry...@gmail.com> Date: Mon Jul 21 09:27:08 2025 -0700 gccrs: Add read-only check on HIR gcc/rust/ChangeLog: * Make-lang.in (rust-readonly-check2.cc): Add read-only check on HIR * checks/errors/rust-readonly-check2.cc (ReadonlyChecker): Add read-only check on HIR * checks/errors/rust-readonly-check2.h (ReadonlyChecker): Add read-only check on HIR Signed-off-by: Ryutaro Okada <1015ry...@gmail.com> Diff: --- gcc/rust/Make-lang.in | 1 + gcc/rust/checks/errors/rust-readonly-check2.cc | 253 +++++++++++++++++++++++++ gcc/rust/checks/errors/rust-readonly-check2.h | 67 +++++++ 3 files changed, 321 insertions(+) diff --git a/gcc/rust/Make-lang.in b/gcc/rust/Make-lang.in index ffe3137dd704..8cdf6c983312 100644 --- a/gcc/rust/Make-lang.in +++ b/gcc/rust/Make-lang.in @@ -209,6 +209,7 @@ GRS_OBJS = \ rust/rust-lint-marklive.o \ rust/rust-lint-unused-var.o \ rust/rust-readonly-check.o \ + rust/rust-readonly-check2.o \ rust/rust-hir-type-check-path.o \ rust/rust-unsafe-checker.o \ rust/rust-hir-pattern-analysis.o \ diff --git a/gcc/rust/checks/errors/rust-readonly-check2.cc b/gcc/rust/checks/errors/rust-readonly-check2.cc new file mode 100644 index 000000000000..2fa92aee1659 --- /dev/null +++ b/gcc/rust/checks/errors/rust-readonly-check2.cc @@ -0,0 +1,253 @@ +// 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; + 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::MULTIPLE: + { + auto &items = static_cast<HIR::TuplePatternItemsMultiple &> ( + 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 (to_deref_type->get_kind () == TyTy::TypeKind::REF) + { + auto ref_type = static_cast<TyTy::ReferenceType *> (to_deref_type); + if (!ref_type->is_mutable ()) + rust_error_at (expr.get_locus (), "assignment of read-only location"); + } + if (to_deref_type->get_kind () == TyTy::TypeKind::POINTER) + { + auto ptr_type = static_cast<TyTy::PointerType *> (to_deref_type); + if (!ptr_type->is_mutable ()) + rust_error_at (expr.get_locus (), "assignment of read-only location"); + } +} +} // namespace HIR +} // namespace Rust diff --git a/gcc/rust/checks/errors/rust-readonly-check2.h b/gcc/rust/checks/errors/rust-readonly-check2.h new file mode 100644 index 000000000000..06af9dbd17c7 --- /dev/null +++ b/gcc/rust/checks/errors/rust-readonly-check2.h @@ -0,0 +1,67 @@ +// 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-hir-visitor.h" +#include "rust-name-resolver.h" +#include "rust-stacked-contexts.h" +#include "rust-hir-type-check.h" + +namespace Rust { +namespace HIR { +class ReadonlyChecker : public DefaultHIRVisitor +{ +public: + ReadonlyChecker (); + + void go (HIR::Crate &crate); + +private: + enum class lvalue_use + { + assign, + increment, + decrement, + }; + + Resolver::Resolver &resolver; + Analysis::Mappings &mappings; + Resolver::TypeCheckContext &context; + StackedContexts<HirId> mutable_context; + + using DefaultHIRVisitor::visit; + + virtual void visit (AssignmentExpr &expr) override; + virtual void visit (PathInExpression &expr) override; + virtual void visit (FieldAccessExpr &expr) override; + virtual void visit (ArrayIndexExpr &expr) override; + virtual void visit (TupleExpr &expr) override; + virtual void visit (TupleIndexExpr &expr) override; + virtual void visit (LetStmt &stmt) override; + virtual void visit (LiteralExpr &expr) override; + virtual void visit (DereferenceExpr &expr) override; + + void collect_assignment (Pattern &pattern, bool has_init_expr); + void collect_assignment_identifier (IdentifierPattern &pattern, + bool has_init_expr); + void collect_assignment_tuple (TuplePattern &pattern, bool has_init_expr); + + void check_variable (IdentifierPattern *pattern, location_t assigned_loc); +}; + +} // namespace HIR +} // namespace Rust \ No newline at end of file