Skip to content

Implement readonly checker on HIR #3881

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions gcc/rust/Make-lang.in
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,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 \
Expand Down
253 changes: 253 additions & 0 deletions gcc/rust/checks/errors/rust-readonly-check2.cc
Original file line number Diff line number Diff line change
@@ -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
67 changes: 67 additions & 0 deletions gcc/rust/checks/errors/rust-readonly-check2.h
Original file line number Diff line number Diff line change
@@ -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 lvalue_use
{
lv_assign,
lv_increment,
lv_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
11 changes: 6 additions & 5 deletions gcc/rust/hir/tree/rust-hir-visitor.cc
Original file line number Diff line number Diff line change
Expand Up @@ -497,7 +497,7 @@ DefaultHIRVisitor::walk (IfExpr &expr)
void
DefaultHIRVisitor::walk (IfExprConseqElse &expr)
{
reinterpret_cast<IfExpr &> (expr).accept_vis (*this);
reinterpret_cast<IfExpr &> (expr).IfExpr::accept_vis (*this);
expr.get_else_block ().accept_vis (*this);
}

Expand Down Expand Up @@ -742,23 +742,23 @@ DefaultHIRVisitor::walk (EnumItem &item)
void
DefaultHIRVisitor::walk (EnumItemTuple &item_tuple)
{
reinterpret_cast<EnumItem &> (item_tuple).accept_vis (*this);
reinterpret_cast<EnumItem &> (item_tuple).EnumItem::accept_vis (*this);
for (auto &field : item_tuple.get_tuple_fields ())
field.get_field_type ().accept_vis (*this);
}

void
DefaultHIRVisitor::walk (EnumItemStruct &item_struct)
{
reinterpret_cast<EnumItem &> (item_struct).accept_vis (*this);
reinterpret_cast<EnumItem &> (item_struct).EnumItem::accept_vis (*this);
for (auto &field : item_struct.get_struct_fields ())
field.get_field_type ().accept_vis (*this);
}

void
DefaultHIRVisitor::walk (EnumItemDiscriminant &item)
{
reinterpret_cast<EnumItem &> (item).accept_vis (*this);
reinterpret_cast<EnumItem &> (item).EnumItem::accept_vis (*this);
item.get_discriminant_expression ().accept_vis (*this);
}

Expand Down Expand Up @@ -890,7 +890,8 @@ DefaultHIRVisitor::walk (ImplBlock &impl)
visit_outer_attrs (impl);
for (auto &generic : impl.get_generic_params ())
generic->accept_vis (*this);
impl.get_trait_ref ().accept_vis (*this);
if (impl.has_trait_ref ())
impl.get_trait_ref ().accept_vis (*this);
impl.get_type ().accept_vis (*this);
if (impl.has_where_clause ())
visit_where_clause (impl.get_where_clause ());
Expand Down
Loading