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

Reply via email to