LCOV - code coverage report
Current view: top level - gcc/rust/checks/errors - rust-readonly-check.cc (source / functions) Coverage Total Hit
Test: gcc.info Lines: 94.4 % 125 118
Test Date: 2026-02-28 14:20:25 Functions: 100.0 % 16 16
Legend: Lines:     hit not hit

            Line data    Source code
       1              : // Copyright (C) 2025-2026 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-check.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         4028 : ReadonlyChecker::ReadonlyChecker ()
      36         4028 :   : resolver (*Resolver::Resolver::get ()),
      37         4028 :     mappings (Analysis::Mappings::get ()),
      38         8056 :     context (*Resolver::TypeCheckContext::get ())
      39         4028 : {}
      40              : 
      41              : void
      42         4028 : ReadonlyChecker::go (Crate &crate)
      43              : {
      44        20761 :   for (auto &item : crate.get_items ())
      45        16733 :     item->accept_vis (*this);
      46         4028 : }
      47              : 
      48              : void
      49         2401 : ReadonlyChecker::visit (AssignmentExpr &expr)
      50              : {
      51         2401 :   Expr &lhs = expr.get_lhs ();
      52         2401 :   mutable_context.enter (expr.get_mappings ().get_hirid ());
      53         2401 :   lhs.accept_vis (*this);
      54         2401 :   mutable_context.exit ();
      55         2401 : }
      56              : 
      57              : void
      58        27269 : ReadonlyChecker::visit (PathInExpression &expr)
      59              : {
      60        27269 :   if (!mutable_context.is_in_context ())
      61        24924 :     return;
      62              : 
      63         2345 :   NodeId ast_node_id = expr.get_mappings ().get_nodeid ();
      64         2345 :   NodeId def_id;
      65              : 
      66         2345 :   auto &nr_ctx
      67         2345 :     = Resolver2_0::ImmutableNameResolutionContext::get ().resolver ();
      68         2345 :   if (auto id = nr_ctx.lookup (ast_node_id))
      69         2345 :     def_id = *id;
      70              :   else
      71            0 :     return;
      72              : 
      73         2345 :   auto hir_id = mappings.lookup_node_to_hir (def_id);
      74         2345 :   if (!hir_id)
      75              :     return;
      76              : 
      77              :   // Check if the local variable is mutable.
      78         2345 :   auto maybe_pattern = mappings.lookup_hir_pattern (*hir_id);
      79         2345 :   if (maybe_pattern
      80         2345 :       && maybe_pattern.value ()->get_pattern_type ()
      81              :            == HIR::Pattern::PatternType::IDENTIFIER)
      82         2280 :     check_variable (static_cast<IdentifierPattern *> (maybe_pattern.value ()),
      83              :                     expr.get_locus ());
      84              : 
      85              :   // Check if the static item is mutable.
      86         2345 :   auto maybe_item = mappings.lookup_hir_item (*hir_id);
      87         2345 :   if (maybe_item
      88         2345 :       && maybe_item.value ()->get_item_kind () == HIR::Item::ItemKind::Static)
      89              :     {
      90            0 :       auto static_item = static_cast<HIR::StaticItem *> (*maybe_item);
      91            0 :       if (!static_item->is_mut ())
      92            0 :         rust_error_at (expr.get_locus (),
      93              :                        "assignment of read-only location '%s'",
      94            0 :                        static_item->get_identifier ().as_string ().c_str ());
      95              :     }
      96              : 
      97              :   // Check if the constant item is mutable.
      98         2345 :   if (maybe_item
      99         2345 :       && maybe_item.value ()->get_item_kind () == HIR::Item::ItemKind::Constant)
     100              :     {
     101            1 :       auto const_item = static_cast<HIR::ConstantItem *> (*maybe_item);
     102            1 :       rust_error_at (expr.get_locus (), "assignment of read-only location '%s'",
     103            2 :                      const_item->get_identifier ().as_string ().c_str ());
     104              :     }
     105              : }
     106              : 
     107              : void
     108         2280 : ReadonlyChecker::check_variable (IdentifierPattern *pattern,
     109              :                                  location_t assigned_loc)
     110              : {
     111         2280 :   if (!mutable_context.is_in_context ())
     112         1331 :     return;
     113              : 
     114         2280 :   TyTy::BaseType *type;
     115         2280 :   if (context.lookup_type (pattern->get_mappings ().get_hirid (), &type)
     116         2280 :       && is_mutable_type (type))
     117              :     return;
     118         1579 :   if (pattern->is_mut ())
     119              :     return;
     120              : 
     121          949 :   auto hir_id = pattern->get_mappings ().get_hirid ();
     122          949 :   if (already_assigned_variables.count (hir_id) > 0)
     123            5 :     rust_error_at (assigned_loc, "assignment of read-only variable '%s'",
     124           10 :                    pattern->to_string ().c_str ());
     125          949 :   already_assigned_variables.insert (hir_id);
     126              : }
     127              : 
     128              : void
     129        12184 : ReadonlyChecker::collect_assignment_identifier (IdentifierPattern &pattern,
     130              :                                                 bool has_init_expr)
     131              : {
     132        12184 :   if (has_init_expr)
     133              :     {
     134        11174 :       HirId pattern_id = pattern.get_mappings ().get_hirid ();
     135        11174 :       already_assigned_variables.insert (pattern_id);
     136              :     }
     137        12184 : }
     138              : 
     139              : void
     140          283 : ReadonlyChecker::collect_assignment_tuple (TuplePattern &tuple_pattern,
     141              :                                            bool has_init_expr)
     142              : {
     143          283 :   switch (tuple_pattern.get_items ().get_item_type ())
     144              :     {
     145          281 :     case HIR::TuplePatternItems::ItemType::NO_REST:
     146          281 :       {
     147          281 :         auto &items_no_rest = static_cast<HIR::TuplePatternItemsNoRest &> (
     148          281 :           tuple_pattern.get_items ());
     149          848 :         for (auto &sub : items_no_rest.get_patterns ())
     150              :           {
     151          567 :             collect_assignment (*sub, has_init_expr);
     152              :           }
     153              :       }
     154              :       break;
     155            2 :     case HIR::TuplePatternItems::ItemType::HAS_REST:
     156            2 :       {
     157            2 :         auto &items_has_rest = static_cast<HIR::TuplePatternItemsHasRest &> (
     158            2 :           tuple_pattern.get_items ());
     159            4 :         for (auto &sub : items_has_rest.get_lower_patterns ())
     160            2 :           collect_assignment (*sub, has_init_expr);
     161            4 :         for (auto &sub : items_has_rest.get_upper_patterns ())
     162            2 :           collect_assignment (*sub, has_init_expr);
     163              :       }
     164              :       break;
     165              :     default:
     166              :       break;
     167              :     }
     168          283 : }
     169              : 
     170              : void
     171        12738 : ReadonlyChecker::collect_assignment (Pattern &pattern, bool has_init_expr)
     172              : {
     173        12738 :   switch (pattern.get_pattern_type ())
     174              :     {
     175        12184 :     case HIR::Pattern::PatternType::IDENTIFIER:
     176        12184 :       {
     177        12184 :         collect_assignment_identifier (static_cast<IdentifierPattern &> (
     178              :                                          pattern),
     179              :                                        has_init_expr);
     180              :       }
     181        12184 :       break;
     182          283 :     case HIR::Pattern::PatternType::TUPLE:
     183          283 :       {
     184          283 :         auto &tuple_pattern = static_cast<HIR::TuplePattern &> (pattern);
     185          283 :         collect_assignment_tuple (tuple_pattern, has_init_expr);
     186              :       }
     187          283 :       break;
     188              :     default:
     189              :       break;
     190              :     }
     191        12738 : }
     192              : 
     193              : void
     194        12167 : ReadonlyChecker::visit (LetStmt &stmt)
     195              : {
     196        12167 :   HIR::Pattern &pattern = stmt.get_pattern ();
     197        12167 :   collect_assignment (pattern, stmt.has_init_expr ());
     198        12167 : }
     199              : 
     200              : void
     201         3293 : ReadonlyChecker::visit (FieldAccessExpr &expr)
     202              : {
     203         3293 :   if (mutable_context.is_in_context ())
     204              :     {
     205          800 :       expr.get_receiver_expr ().accept_vis (*this);
     206              :     }
     207         3293 : }
     208              : 
     209              : void
     210          528 : ReadonlyChecker::visit (TupleIndexExpr &expr)
     211              : {
     212          528 :   if (mutable_context.is_in_context ())
     213              :     {
     214            8 :       expr.get_tuple_expr ().accept_vis (*this);
     215              :     }
     216          528 : }
     217              : 
     218              : void
     219           68 : ReadonlyChecker::visit (ArrayIndexExpr &expr)
     220              : {
     221           68 :   if (mutable_context.is_in_context ())
     222              :     {
     223           10 :       expr.get_array_expr ().accept_vis (*this);
     224              :     }
     225           68 : }
     226              : 
     227              : void
     228          322 : ReadonlyChecker::visit (TupleExpr &expr)
     229              : {
     230          322 :   if (mutable_context.is_in_context ())
     231              :     {
     232              :       // TODO: Add check for tuple expression
     233              :     }
     234          322 : }
     235              : 
     236              : void
     237         7253 : ReadonlyChecker::visit (LiteralExpr &expr)
     238              : {
     239         7253 :   if (mutable_context.is_in_context ())
     240              :     {
     241            1 :       rust_error_at (expr.get_locus (), "assignment of read-only location");
     242              :     }
     243         7253 : }
     244              : 
     245              : void
     246         3700 : ReadonlyChecker::visit (DereferenceExpr &expr)
     247              : {
     248         3700 :   if (!mutable_context.is_in_context ())
     249         3645 :     return;
     250           55 :   TyTy::BaseType *to_deref_type;
     251           55 :   auto to_deref = expr.get_expr ().get_mappings ().get_hirid ();
     252           55 :   if (!context.lookup_type (to_deref, &to_deref_type))
     253              :     return;
     254           55 :   if (!is_mutable_type (to_deref_type))
     255            0 :     rust_error_at (expr.get_locus (), "assignment of read-only location");
     256              : }
     257              : 
     258              : bool
     259         2335 : ReadonlyChecker::is_mutable_type (TyTy::BaseType *type)
     260              : {
     261         2335 :   if (type->get_kind () == TyTy::TypeKind::REF)
     262          847 :     return static_cast<TyTy::ReferenceType *> (type)->is_mutable ();
     263         1488 :   if (type->get_kind () == TyTy::TypeKind::POINTER)
     264            0 :     return static_cast<TyTy::PointerType *> (type)->is_mutable ();
     265              :   return false;
     266              : }
     267              : } // namespace HIR
     268              : } // namespace Rust
        

Generated by: LCOV version 2.4-beta

LCOV profile is generated on x86_64 machine using following configure options: configure --disable-bootstrap --enable-coverage=opt --enable-languages=c,c++,fortran,go,jit,lto,rust,m2 --enable-host-shared. GCC test suite is run with the built compiler.