Skip to content

Commit fa88250

Browse files
sakupan102CohenArthur
authored andcommitted
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 dd7803d commit fa88250

File tree

3 files changed

+321
-0
lines changed

3 files changed

+321
-0
lines changed

gcc/rust/Make-lang.in

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,7 @@ GRS_OBJS = \
209209
rust/rust-lint-marklive.o \
210210
rust/rust-lint-unused-var.o \
211211
rust/rust-readonly-check.o \
212+
rust/rust-readonly-check2.o \
212213
rust/rust-hir-type-check-path.o \
213214
rust/rust-unsafe-checker.o \
214215
rust/rust-hir-pattern-analysis.o \
Lines changed: 253 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,253 @@
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+
namespace Rust {
31+
namespace HIR {
32+
33+
static std::set<HirId> already_assigned_variables = {};
34+
35+
ReadonlyChecker::ReadonlyChecker ()
36+
: resolver (*Resolver::Resolver::get ()),
37+
mappings (Analysis::Mappings::get ()),
38+
context (*Resolver::TypeCheckContext::get ())
39+
{}
40+
41+
void
42+
ReadonlyChecker::go (Crate &crate)
43+
{
44+
for (auto &item : crate.get_items ())
45+
item->accept_vis (*this);
46+
}
47+
48+
void
49+
ReadonlyChecker::visit (AssignmentExpr &expr)
50+
{
51+
Expr &lhs = expr.get_lhs ();
52+
mutable_context.enter (expr.get_mappings ().get_hirid ());
53+
lhs.accept_vis (*this);
54+
mutable_context.exit ();
55+
}
56+
57+
void
58+
ReadonlyChecker::visit (PathInExpression &expr)
59+
{
60+
if (!mutable_context.is_in_context ())
61+
return;
62+
63+
NodeId ast_node_id = expr.get_mappings ().get_nodeid ();
64+
NodeId def_id;
65+
66+
auto &nr_ctx
67+
= Resolver2_0::ImmutableNameResolutionContext::get ().resolver ();
68+
if (auto id = nr_ctx.lookup (ast_node_id))
69+
def_id = *id;
70+
else
71+
return;
72+
73+
auto hir_id = mappings.lookup_node_to_hir (def_id);
74+
if (!hir_id)
75+
return;
76+
77+
// Check if the local variable is mutable.
78+
auto maybe_pattern = mappings.lookup_hir_pattern (*hir_id);
79+
if (maybe_pattern
80+
&& maybe_pattern.value ()->get_pattern_type ()
81+
== HIR::Pattern::PatternType::IDENTIFIER)
82+
check_variable (static_cast<IdentifierPattern *> (maybe_pattern.value ()),
83+
expr.get_locus ());
84+
85+
// Check if the static item is mutable.
86+
auto maybe_item = mappings.lookup_hir_item (*hir_id);
87+
if (maybe_item
88+
&& maybe_item.value ()->get_item_kind () == HIR::Item::ItemKind::Static)
89+
{
90+
auto static_item = static_cast<HIR::StaticItem *> (*maybe_item);
91+
if (!static_item->is_mut ())
92+
rust_error_at (expr.get_locus (),
93+
"assignment of read-only location '%s'",
94+
static_item->get_identifier ().as_string ().c_str ());
95+
}
96+
97+
// Check if the constant item is mutable.
98+
if (maybe_item
99+
&& maybe_item.value ()->get_item_kind () == HIR::Item::ItemKind::Constant)
100+
{
101+
auto const_item = static_cast<HIR::ConstantItem *> (*maybe_item);
102+
rust_error_at (expr.get_locus (), "assignment of read-only location '%s'",
103+
const_item->get_identifier ().as_string ().c_str ());
104+
}
105+
}
106+
107+
void
108+
ReadonlyChecker::check_variable (IdentifierPattern *pattern,
109+
location_t assigned_loc)
110+
{
111+
if (!mutable_context.is_in_context ())
112+
return;
113+
if (pattern->is_mut ())
114+
return;
115+
116+
auto hir_id = pattern->get_mappings ().get_hirid ();
117+
if (already_assigned_variables.count (hir_id) > 0)
118+
rust_error_at (assigned_loc, "assignment of read-only variable '%s'",
119+
pattern->as_string ().c_str ());
120+
already_assigned_variables.insert (hir_id);
121+
}
122+
123+
void
124+
ReadonlyChecker::collect_assignment_identifier (IdentifierPattern &pattern,
125+
bool has_init_expr)
126+
{
127+
if (has_init_expr)
128+
{
129+
HirId pattern_id = pattern.get_mappings ().get_hirid ();
130+
already_assigned_variables.insert (pattern_id);
131+
}
132+
}
133+
134+
void
135+
ReadonlyChecker::collect_assignment_tuple (TuplePattern &tuple_pattern,
136+
bool has_init_expr)
137+
{
138+
switch (tuple_pattern.get_items ().get_item_type ())
139+
{
140+
case HIR::TuplePatternItems::ItemType::MULTIPLE:
141+
{
142+
auto &items = static_cast<HIR::TuplePatternItemsMultiple &> (
143+
tuple_pattern.get_items ());
144+
for (auto &sub : items.get_patterns ())
145+
{
146+
collect_assignment (*sub, has_init_expr);
147+
}
148+
}
149+
break;
150+
default:
151+
break;
152+
}
153+
}
154+
155+
void
156+
ReadonlyChecker::collect_assignment (Pattern &pattern, bool has_init_expr)
157+
{
158+
switch (pattern.get_pattern_type ())
159+
{
160+
case HIR::Pattern::PatternType::IDENTIFIER:
161+
{
162+
collect_assignment_identifier (static_cast<IdentifierPattern &> (
163+
pattern),
164+
has_init_expr);
165+
}
166+
break;
167+
case HIR::Pattern::PatternType::TUPLE:
168+
{
169+
auto &tuple_pattern = static_cast<HIR::TuplePattern &> (pattern);
170+
collect_assignment_tuple (tuple_pattern, has_init_expr);
171+
}
172+
break;
173+
default:
174+
break;
175+
}
176+
}
177+
178+
void
179+
ReadonlyChecker::visit (LetStmt &stmt)
180+
{
181+
HIR::Pattern &pattern = stmt.get_pattern ();
182+
collect_assignment (pattern, stmt.has_init_expr ());
183+
}
184+
185+
void
186+
ReadonlyChecker::visit (FieldAccessExpr &expr)
187+
{
188+
if (mutable_context.is_in_context ())
189+
{
190+
expr.get_receiver_expr ().accept_vis (*this);
191+
}
192+
}
193+
194+
void
195+
ReadonlyChecker::visit (TupleIndexExpr &expr)
196+
{
197+
if (mutable_context.is_in_context ())
198+
{
199+
expr.get_tuple_expr ().accept_vis (*this);
200+
}
201+
}
202+
203+
void
204+
ReadonlyChecker::visit (ArrayIndexExpr &expr)
205+
{
206+
if (mutable_context.is_in_context ())
207+
{
208+
expr.get_array_expr ().accept_vis (*this);
209+
}
210+
}
211+
212+
void
213+
ReadonlyChecker::visit (TupleExpr &expr)
214+
{
215+
if (mutable_context.is_in_context ())
216+
{
217+
// TODO: Add check for tuple expression
218+
}
219+
}
220+
221+
void
222+
ReadonlyChecker::visit (LiteralExpr &expr)
223+
{
224+
if (mutable_context.is_in_context ())
225+
{
226+
rust_error_at (expr.get_locus (), "assignment of read-only location");
227+
}
228+
}
229+
230+
void
231+
ReadonlyChecker::visit (DereferenceExpr &expr)
232+
{
233+
if (!mutable_context.is_in_context ())
234+
return;
235+
TyTy::BaseType *to_deref_type;
236+
auto to_deref = expr.get_expr ().get_mappings ().get_hirid ();
237+
if (!context.lookup_type (to_deref, &to_deref_type))
238+
return;
239+
if (to_deref_type->get_kind () == TyTy::TypeKind::REF)
240+
{
241+
auto ref_type = static_cast<TyTy::ReferenceType *> (to_deref_type);
242+
if (!ref_type->is_mutable ())
243+
rust_error_at (expr.get_locus (), "assignment of read-only location");
244+
}
245+
if (to_deref_type->get_kind () == TyTy::TypeKind::POINTER)
246+
{
247+
auto ptr_type = static_cast<TyTy::PointerType *> (to_deref_type);
248+
if (!ptr_type->is_mutable ())
249+
rust_error_at (expr.get_locus (), "assignment of read-only location");
250+
}
251+
}
252+
} // namespace HIR
253+
} // 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 class lvalue_use
35+
{
36+
assign,
37+
increment,
38+
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)