Skip to content

Commit ec0556a

Browse files
committed
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 <1015ryu88@gmail.com>
1 parent 293b374 commit ec0556a

3 files changed

Lines changed: 329 additions & 0 deletions

File tree

gcc/rust/Make-lang.in

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,7 @@ GRS_OBJS = \
205205
rust/rust-lint-marklive.o \
206206
rust/rust-lint-unused-var.o \
207207
rust/rust-readonly-check.o \
208+
rust/rust-readonly-check2.o \
208209
rust/rust-hir-type-check-path.o \
209210
rust/rust-unsafe-checker.o \
210211
rust/rust-hir-pattern-analysis.o \
Lines changed: 261 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,261 @@
1+
// Copyright (C) 2025 Free Software Foundation, Inc.
2+
3+
// This file is part of GCC.
4+
5+
// GCC is free software; you can redistribute it and/or modify it under
6+
// the terms of the GNU General Public License as published by the Free
7+
// Software Foundation; either version 3, or (at your option) any later
8+
// version.
9+
10+
// GCC is distributed in the hope that it will be useful, but WITHOUT ANY
11+
// WARRANTY; without even the implied warranty of MERCHANTABILITY or
12+
// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
13+
// for more details.
14+
15+
// You should have received a copy of the GNU General Public License
16+
// along with GCC; see the file COPYING3. If not see
17+
// <http://www.gnu.org/licenses/>.
18+
19+
#include "rust-readonly-check2.h"
20+
#include "rust-hir-expr.h"
21+
#include "rust-hir-node.h"
22+
#include "rust-hir-path.h"
23+
#include "rust-hir-map.h"
24+
#include "rust-hir-pattern.h"
25+
#include "rust-mapping-common.h"
26+
#include "rust-system.h"
27+
#include "rust-immutable-name-resolution-context.h"
28+
#include "rust-tyty.h"
29+
30+
// for flag_name_resolution_2_0
31+
#include "options.h"
32+
33+
namespace Rust {
34+
namespace HIR {
35+
36+
static std::set<HirId> already_assigned_variables = {};
37+
38+
ReadonlyChecker::ReadonlyChecker ()
39+
: resolver (*Resolver::Resolver::get ()),
40+
mappings (Analysis::Mappings::get ()),
41+
context (*Resolver::TypeCheckContext::get ())
42+
{}
43+
44+
void
45+
ReadonlyChecker::go (Crate &crate)
46+
{
47+
for (auto &item : crate.get_items ())
48+
item->accept_vis (*this);
49+
}
50+
51+
void
52+
ReadonlyChecker::visit (AssignmentExpr &expr)
53+
{
54+
Expr &lhs = expr.get_lhs ();
55+
mutable_context.enter (expr.get_mappings ().get_hirid ());
56+
lhs.accept_vis (*this);
57+
mutable_context.exit ();
58+
}
59+
60+
void
61+
ReadonlyChecker::visit (PathInExpression &expr)
62+
{
63+
if (!mutable_context.is_in_context ())
64+
return;
65+
66+
NodeId ast_node_id = expr.get_mappings ().get_nodeid ();
67+
NodeId def_id;
68+
if (flag_name_resolution_2_0)
69+
{
70+
auto &nr_ctx
71+
= Resolver2_0::ImmutableNameResolutionContext::get ().resolver ();
72+
73+
if (auto id = nr_ctx.lookup (ast_node_id))
74+
def_id = *id;
75+
else
76+
return;
77+
}
78+
else if (!resolver.lookup_resolved_name (ast_node_id, &def_id))
79+
return;
80+
81+
auto hir_id = mappings.lookup_node_to_hir (def_id);
82+
if (!hir_id)
83+
return;
84+
85+
// Check if the local variable is mutable.
86+
auto maybe_pattern = mappings.lookup_hir_pattern (*hir_id);
87+
if (maybe_pattern
88+
&& maybe_pattern.value ()->get_pattern_type ()
89+
== HIR::Pattern::PatternType::IDENTIFIER)
90+
check_variable (static_cast<IdentifierPattern *> (maybe_pattern.value ()),
91+
expr.get_locus ());
92+
93+
// Check if the static item is mutable.
94+
auto maybe_item = mappings.lookup_hir_item (*hir_id);
95+
if (maybe_item
96+
&& maybe_item.value ()->get_item_kind () == HIR::Item::ItemKind::Static)
97+
{
98+
auto static_item = static_cast<HIR::StaticItem *> (*maybe_item);
99+
if (!static_item->is_mut ())
100+
rust_error_at (expr.get_locus (),
101+
"assignment of read-only location '%s'",
102+
static_item->get_identifier ().as_string ().c_str ());
103+
}
104+
105+
// Check if the constant item is mutable.
106+
if (maybe_item
107+
&& maybe_item.value ()->get_item_kind () == HIR::Item::ItemKind::Constant)
108+
{
109+
auto const_item = static_cast<HIR::ConstantItem *> (*maybe_item);
110+
rust_error_at (expr.get_locus (), "assignment of read-only location '%s'",
111+
const_item->get_identifier ().as_string ().c_str ());
112+
}
113+
}
114+
115+
void
116+
ReadonlyChecker::check_variable (IdentifierPattern *pattern,
117+
location_t assigned_loc)
118+
{
119+
if (!mutable_context.is_in_context ())
120+
return;
121+
if (pattern->is_mut ())
122+
return;
123+
124+
auto hir_id = pattern->get_mappings ().get_hirid ();
125+
if (already_assigned_variables.count (hir_id) > 0)
126+
rust_error_at (assigned_loc, "assignment of read-only variable '%s'",
127+
pattern->as_string ().c_str ());
128+
already_assigned_variables.insert (hir_id);
129+
}
130+
131+
void
132+
ReadonlyChecker::collect_assignment_identifier (IdentifierPattern &pattern,
133+
bool has_init_expr)
134+
{
135+
if (has_init_expr)
136+
{
137+
HirId pattern_id = pattern.get_mappings ().get_hirid ();
138+
already_assigned_variables.insert (pattern_id);
139+
}
140+
}
141+
142+
void
143+
ReadonlyChecker::collect_assignment_tuple (TuplePattern &tuple_pattern,
144+
bool has_init_expr)
145+
{
146+
switch (tuple_pattern.get_items ().get_item_type ())
147+
{
148+
case HIR::TuplePatternItems::ItemType::MULTIPLE:
149+
{
150+
auto &items = static_cast<HIR::TuplePatternItemsMultiple &> (
151+
tuple_pattern.get_items ());
152+
for (auto &sub : items.get_patterns ())
153+
{
154+
collect_assignment (*sub, has_init_expr);
155+
}
156+
}
157+
break;
158+
default:
159+
break;
160+
}
161+
}
162+
163+
void
164+
ReadonlyChecker::collect_assignment (Pattern &pattern, bool has_init_expr)
165+
{
166+
switch (pattern.get_pattern_type ())
167+
{
168+
case HIR::Pattern::PatternType::IDENTIFIER:
169+
{
170+
collect_assignment_identifier (static_cast<IdentifierPattern &> (
171+
pattern),
172+
has_init_expr);
173+
}
174+
break;
175+
case HIR::Pattern::PatternType::TUPLE:
176+
{
177+
auto &tuple_pattern = static_cast<HIR::TuplePattern &> (pattern);
178+
collect_assignment_tuple (tuple_pattern, has_init_expr);
179+
}
180+
break;
181+
default:
182+
break;
183+
}
184+
}
185+
186+
void
187+
ReadonlyChecker::visit (LetStmt &stmt)
188+
{
189+
HIR::Pattern &pattern = stmt.get_pattern ();
190+
collect_assignment (pattern, stmt.has_init_expr ());
191+
}
192+
193+
void
194+
ReadonlyChecker::visit (FieldAccessExpr &expr)
195+
{
196+
if (mutable_context.is_in_context ())
197+
{
198+
expr.get_receiver_expr ().accept_vis (*this);
199+
}
200+
}
201+
202+
void
203+
ReadonlyChecker::visit (TupleIndexExpr &expr)
204+
{
205+
if (mutable_context.is_in_context ())
206+
{
207+
expr.get_tuple_expr ().accept_vis (*this);
208+
}
209+
}
210+
211+
void
212+
ReadonlyChecker::visit (ArrayIndexExpr &expr)
213+
{
214+
if (mutable_context.is_in_context ())
215+
{
216+
expr.get_array_expr ().accept_vis (*this);
217+
}
218+
}
219+
220+
void
221+
ReadonlyChecker::visit (TupleExpr &expr)
222+
{
223+
if (mutable_context.is_in_context ())
224+
{
225+
// TODO: Add check for tuple expression
226+
}
227+
}
228+
229+
void
230+
ReadonlyChecker::visit (LiteralExpr &expr)
231+
{
232+
if (mutable_context.is_in_context ())
233+
{
234+
rust_error_at (expr.get_locus (), "assignment of read-only location");
235+
}
236+
}
237+
238+
void
239+
ReadonlyChecker::visit (DereferenceExpr &expr)
240+
{
241+
if (!mutable_context.is_in_context ())
242+
return;
243+
TyTy::BaseType *to_deref_type;
244+
auto to_deref = expr.get_expr ().get_mappings ().get_hirid ();
245+
if (!context.lookup_type (to_deref, &to_deref_type))
246+
return;
247+
if (to_deref_type->get_kind () == TyTy::TypeKind::REF)
248+
{
249+
auto ref_type = static_cast<TyTy::ReferenceType *> (to_deref_type);
250+
if (!ref_type->is_mutable ())
251+
rust_error_at (expr.get_locus (), "assignment of read-only location");
252+
}
253+
if (to_deref_type->get_kind () == TyTy::TypeKind::POINTER)
254+
{
255+
auto ptr_type = static_cast<TyTy::PointerType *> (to_deref_type);
256+
if (!ptr_type->is_mutable ())
257+
rust_error_at (expr.get_locus (), "assignment of read-only location");
258+
}
259+
}
260+
} // namespace HIR
261+
} // namespace Rust
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
// Copyright (C) 2025 Free Software Foundation, Inc.
2+
3+
// This file is part of GCC.
4+
5+
// GCC is free software; you can redistribute it and/or modify it under
6+
// the terms of the GNU General Public License as published by the Free
7+
// Software Foundation; either version 3, or (at your option) any later
8+
// version.
9+
10+
// GCC is distributed in the hope that it will be useful, but WITHOUT ANY
11+
// WARRANTY; without even the implied warranty of MERCHANTABILITY or
12+
// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
13+
// for more details.
14+
15+
// You should have received a copy of the GNU General Public License
16+
// along with GCC; see the file COPYING3. If not see
17+
// <http://www.gnu.org/licenses/>.
18+
19+
#include "rust-hir-visitor.h"
20+
#include "rust-name-resolver.h"
21+
#include "rust-stacked-contexts.h"
22+
#include "rust-hir-type-check.h"
23+
24+
namespace Rust {
25+
namespace HIR {
26+
class ReadonlyChecker : public DefaultHIRVisitor
27+
{
28+
public:
29+
ReadonlyChecker ();
30+
31+
void go (HIR::Crate &crate);
32+
33+
private:
34+
enum lvalue_use
35+
{
36+
lv_assign,
37+
lv_increment,
38+
lv_decrement,
39+
};
40+
41+
Resolver::Resolver &resolver;
42+
Analysis::Mappings &mappings;
43+
Resolver::TypeCheckContext &context;
44+
StackedContexts<HirId> mutable_context;
45+
46+
using DefaultHIRVisitor::visit;
47+
48+
virtual void visit (AssignmentExpr &expr) override;
49+
virtual void visit (PathInExpression &expr) override;
50+
virtual void visit (FieldAccessExpr &expr) override;
51+
virtual void visit (ArrayIndexExpr &expr) override;
52+
virtual void visit (TupleExpr &expr) override;
53+
virtual void visit (TupleIndexExpr &expr) override;
54+
virtual void visit (LetStmt &stmt) override;
55+
virtual void visit (LiteralExpr &expr) override;
56+
virtual void visit (DereferenceExpr &expr) override;
57+
58+
void collect_assignment (Pattern &pattern, bool has_init_expr);
59+
void collect_assignment_identifier (IdentifierPattern &pattern,
60+
bool has_init_expr);
61+
void collect_assignment_tuple (TuplePattern &pattern, bool has_init_expr);
62+
63+
void check_variable (IdentifierPattern *pattern, location_t assigned_loc);
64+
};
65+
66+
} // namespace HIR
67+
} // namespace Rust

0 commit comments

Comments
 (0)