Skip to content

Commit 5681f7f

Browse files
committed
implement readonly checker on HIR
1 parent 508b195 commit 5681f7f

File tree

4 files changed

+204
-0
lines changed

4 files changed

+204
-0
lines changed

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: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
// Copyright (C) 2020-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-abstract.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+
28+
namespace Rust {
29+
namespace HIR {
30+
31+
static std::map<HirId, int> assignment_map = {};
32+
33+
ReadonlyChecker::ReadonlyChecker ()
34+
: resolver (*Resolver::Resolver::get ()),
35+
mappings (Analysis::Mappings::get ()), in_mutable_context (false)
36+
{}
37+
38+
void
39+
ReadonlyChecker::go (HIR::Crate &crate)
40+
{
41+
for (auto &item : crate.get_items ())
42+
item->accept_vis (*this);
43+
}
44+
45+
void
46+
ReadonlyChecker::visit (AssignmentExpr &expr)
47+
{
48+
Expr &lhs = expr.get_lhs ();
49+
in_mutable_context = true;
50+
lhs.accept_vis (*this);
51+
in_mutable_context = false;
52+
}
53+
54+
void
55+
ReadonlyChecker::visit (PathInExpression &expr)
56+
{
57+
if (!in_mutable_context)
58+
return;
59+
60+
NodeId id = expr.get_mappings ().get_nodeid ();
61+
NodeId def_id;
62+
if (!resolver.lookup_resolved_name (id, &def_id))
63+
return;
64+
65+
auto hir_id = mappings.lookup_node_to_hir (def_id);
66+
if (!hir_id)
67+
return;
68+
69+
if (assignment_map.find (*hir_id) == assignment_map.end ())
70+
return;
71+
72+
assignment_map[*hir_id]++;
73+
74+
// Check if the local variable is mutable.
75+
auto maybe_pattern = mappings.lookup_hir_pattern (*hir_id);
76+
if (maybe_pattern
77+
&& maybe_pattern.value ()->get_pattern_type ()
78+
== HIR::Pattern::PatternType::IDENTIFIER)
79+
{
80+
auto ident_pattern
81+
= static_cast<HIR::IdentifierPattern *> (*maybe_pattern);
82+
if (!ident_pattern->is_mut () && assignment_map[*hir_id] > 1)
83+
rust_error_at (expr.get_locus (),
84+
"cannot assign to immutable variable");
85+
}
86+
87+
// Check if the static variable is mutable.
88+
auto maybe_static_item = mappings.lookup_hir_item (*hir_id);
89+
if (maybe_static_item
90+
&& maybe_static_item.value ()->get_item_kind ()
91+
== HIR::Item::ItemKind::Static)
92+
{
93+
auto static_item = static_cast<HIR::StaticItem *> (*maybe_static_item);
94+
if (!static_item->is_mut () && assignment_map[*hir_id] > 1)
95+
rust_error_at (expr.get_locus (),
96+
"cannot assign to mutable static variable");
97+
}
98+
}
99+
100+
// Check if the variables are initialized in the let statement.
101+
// If the pattern is a tuple, we need to check each element.
102+
void
103+
ReadonlyChecker::visit (LetStmt &stmt)
104+
{
105+
HIR::Pattern &pattern = stmt.get_pattern ();
106+
HirId pattern_id = pattern.get_mappings ().get_hirid ();
107+
assignment_map.insert ({pattern_id, 0});
108+
if (stmt.has_init_expr ())
109+
{
110+
assignment_map[pattern_id]++;
111+
}
112+
}
113+
114+
void
115+
ReadonlyChecker::visit (FieldAccessExpr &expr)
116+
{
117+
if (in_mutable_context)
118+
{
119+
// TODO: Add check for field access
120+
}
121+
}
122+
123+
void
124+
ReadonlyChecker::visit (TupleIndexExpr &expr)
125+
{
126+
if (in_mutable_context)
127+
{
128+
// TODO: Add check for tuple index
129+
}
130+
}
131+
132+
void
133+
ReadonlyChecker::visit (ArrayIndexExpr &expr)
134+
{
135+
if (in_mutable_context)
136+
{
137+
// TODO: Add check for array index
138+
}
139+
}
140+
141+
void
142+
ReadonlyChecker::visit (TupleExpr &expr)
143+
{
144+
if (in_mutable_context)
145+
{
146+
// TODO: Add check for tuple expression
147+
}
148+
}
149+
} // namespace HIR
150+
} // namespace Rust
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
// Copyright (C) 2020-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-expr.h"
20+
#include "rust-hir-full-decls.h"
21+
#include "rust-hir-path.h"
22+
#include "rust-hir-visitor.h"
23+
#include "rust-name-resolver.h"
24+
25+
namespace Rust {
26+
namespace HIR {
27+
class ReadonlyChecker : public DefaultHIRVisitor
28+
{
29+
public:
30+
ReadonlyChecker ();
31+
32+
void go (HIR::Crate &crate);
33+
34+
private:
35+
Resolver::Resolver &resolver;
36+
Analysis::Mappings &mappings;
37+
38+
// TODO: Replace with a stacked context.
39+
bool in_mutable_context;
40+
41+
void visit (AssignmentExpr &expr) override;
42+
void visit (PathInExpression &expr) override;
43+
void visit (FieldAccessExpr &expr) override;
44+
void visit (ArrayIndexExpr &expr) override;
45+
void visit (TupleExpr &expr) override;
46+
void visit (TupleIndexExpr &expr) override;
47+
void visit (LetStmt &stmt) override;
48+
};
49+
50+
} // namespace HIR
51+
} // namespace Rust

gcc/rust/rust-session-manager.cc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@
5656
#include "rust-borrow-checker.h"
5757
#include "rust-ast-validation.h"
5858
#include "rust-tyty-variance-analysis.h"
59+
#include "rust-readonly-check2.h"
5960

6061
#include "input.h"
6162
#include "selftest.h"
@@ -738,6 +739,7 @@ Session::compile_crate (const char *filename)
738739
Analysis::ScanDeadcode::Scan (hir);
739740
Analysis::UnusedVariables::Lint (ctx);
740741
Analysis::ReadonlyCheck::Lint (ctx);
742+
HIR::ReadonlyChecker ().go (hir);
741743

742744
// metadata
743745
bool specified_emit_metadata

0 commit comments

Comments
 (0)