From eab8d279c79b1f0f0d70875d5b77c9160250b51f Mon Sep 17 00:00:00 2001 From: Vineeth Kashyap Date: Thu, 19 Sep 2024 10:40:07 -0400 Subject: [PATCH] [compiler-v2][lint] Needless mutable reference (#14651) --- .../move/move-compiler-v2/src/lint_common.rs | 1 + .../src/pipeline/lint_processor.rs | 11 +- .../needless_mutable_reference.rs | 241 +++++++++++++++++ .../needless_deref_ref_warn.exp | 10 + .../needless_ref_in_field_access_warn.exp | 34 +++ .../needless_mutable_reference_warn.exp | 150 +++++++++++ .../needless_mutable_reference_warn.move | 255 ++++++++++++++++++ 7 files changed, 700 insertions(+), 2 deletions(-) create mode 100644 third_party/move/move-compiler-v2/src/pipeline/lint_processor/needless_mutable_reference.rs create mode 100644 third_party/move/move-compiler-v2/tests/lints/stackless_bytecode_lints/needless_mutable_reference_warn.exp create mode 100644 third_party/move/move-compiler-v2/tests/lints/stackless_bytecode_lints/needless_mutable_reference_warn.move diff --git a/third_party/move/move-compiler-v2/src/lint_common.rs b/third_party/move/move-compiler-v2/src/lint_common.rs index f8e92596f7257..63858546906a9 100644 --- a/third_party/move/move-compiler-v2/src/lint_common.rs +++ b/third_party/move/move-compiler-v2/src/lint_common.rs @@ -21,6 +21,7 @@ pub enum LintChecker { BlocksInConditions, NeedlessBool, NeedlessDerefRef, + NeedlessMutableReference, NeedlessRefDeref, NeedlessRefInFieldAccess, SimplerNumericExpression, diff --git a/third_party/move/move-compiler-v2/src/pipeline/lint_processor.rs b/third_party/move/move-compiler-v2/src/pipeline/lint_processor.rs index a82e0d9b8b2eb..4d8ba5dd82125 100644 --- a/third_party/move/move-compiler-v2/src/pipeline/lint_processor.rs +++ b/third_party/move/move-compiler-v2/src/pipeline/lint_processor.rs @@ -6,10 +6,14 @@ //! The lint checks also assume that all the correctness checks have already been performed. mod avoid_copy_on_identity_comparison; +mod needless_mutable_reference; use crate::{ lint_common::{lint_skips_from_attributes, LintChecker}, - pipeline::lint_processor::avoid_copy_on_identity_comparison::AvoidCopyOnIdentityComparison, + pipeline::lint_processor::{ + avoid_copy_on_identity_comparison::AvoidCopyOnIdentityComparison, + needless_mutable_reference::NeedlessMutableReference, + }, }; use move_compiler::shared::known_attributes::LintAttribute; use move_model::model::{FunctionEnv, GlobalEnv, Loc}; @@ -72,7 +76,10 @@ impl FunctionTargetProcessor for LintProcessor { impl LintProcessor { /// Returns the default pipeline of stackless bytecode linters to run. fn get_default_linter_pipeline() -> Vec> { - vec![Box::new(AvoidCopyOnIdentityComparison {})] + vec![ + Box::new(AvoidCopyOnIdentityComparison {}), + Box::new(NeedlessMutableReference {}), + ] } /// Returns a filtered pipeline of stackless bytecode linters to run. diff --git a/third_party/move/move-compiler-v2/src/pipeline/lint_processor/needless_mutable_reference.rs b/third_party/move/move-compiler-v2/src/pipeline/lint_processor/needless_mutable_reference.rs new file mode 100644 index 0000000000000..c3758742d36a9 --- /dev/null +++ b/third_party/move/move-compiler-v2/src/pipeline/lint_processor/needless_mutable_reference.rs @@ -0,0 +1,241 @@ +// Copyright (c) Aptos Foundation +// SPDX-License-Identifier: Apache-2.0 + +//! This module implements a stackless-bytecode linter that checks for mutable references +//! that are never used mutably, and suggests to use immutable references instead. +//! For example, if a mutable reference is never written to or passed as a mutable reference +//! parameter to a function call, it can be replaced with an immutable reference. +//! +//! Currently, we only track mutable references that are: +//! - function parameters, +//! - obtained via `&mut` or `borrow_global_mut`. + +use crate::{lint_common::LintChecker, pipeline::lint_processor::StacklessBytecodeLinter}; +use move_model::{ + ast::TempIndex, + model::{GlobalEnv, Loc, Parameter}, +}; +use move_stackless_bytecode::{ + function_target::FunctionTarget, + stackless_bytecode::{Bytecode, Operation}, +}; +use std::collections::{BTreeMap, BTreeSet}; + +/// Track "mutable" usages of certain mutable references in a function. +/// Currently, the tracking is performed conservatively, in a flow-insensitive +/// manner, to minimize perceived false positives. +#[derive(Default)] +struct MutableReferenceUsageTracker { + /// Keys are temps which are origins of certain mutable references. + /// Each key is mapped to a location where it acquires the mutable reference. + /// The origins tracked currently are: + /// - function parameters that are mutable references. + /// - mutable references acquired through `&mut` or `borrow_global_mut`. + /// The list above can be extended in the future. + origins: BTreeMap, + /// Derived edges from mutable references. + /// A derived edge y -> x is created in the following cases: + /// - `x = y;`, where y: &mut + /// - `x = &mut y.f;` + /// Each origin also has an entry in `derived_edges` (usually mapping to an + /// empty set, unless an origin is also derived). + derived_edges: BTreeMap>, + /// The set of mutable references that are known to be used mutably, either directly + /// or through a derived edge. To be used mutably, the mutable reference is: + /// - written to via `WriteRef`, or + /// - passed as an argument to a function call's mutable reference parameter. + mutably_used: BTreeSet, +} + +impl MutableReferenceUsageTracker { + /// For the `target` function, get locations we can warn about needless mutable references. + pub fn get_needless_mutable_refs(target: &FunctionTarget) -> Vec { + let mut tracker = Self::get_tracker_from_params(target); + for instr in target.get_bytecode() { + tracker.update(target, instr); + } + tracker.get_mutably_unused_locations() + } + + /// Get an initial tracker from function parameters. + fn get_tracker_from_params(target: &FunctionTarget) -> Self { + let mut tracker = Self::default(); + for (origin, loc) in Self::get_mut_reference_params(target) { + tracker.add_origin(origin, loc); + } + tracker + } + + /// Get locations where origins are not used mutably. + fn get_mutably_unused_locations(self) -> Vec { + self.origins + .into_iter() + .filter_map(|(t, loc)| { + if !self.mutably_used.contains(&t) { + Some(loc) + } else { + None + } + }) + .collect() + } + + /// Get mutable reference function parameters, along with their location info. + fn get_mut_reference_params(target: &FunctionTarget) -> BTreeMap { + target + .func_env + .get_parameters_ref() + .iter() + .enumerate() + .filter_map(|(i, Parameter(_, ty, loc))| { + if ty.is_mutable_reference() { + // Note: we assume that parameters are laid out as the initial temps. + Some((i, loc.clone())) + } else { + None + } + }) + .collect() + } + + /// Update the tracker given `instr`. + fn update(&mut self, target: &FunctionTarget, instr: &Bytecode) { + self.update_origins(target, instr); + self.update_derived_edges(target, instr); + self.update_mutably_used(target.global_env(), instr); + } + + /// Update origins given `instr`. + fn update_origins(&mut self, target: &FunctionTarget, instr: &Bytecode) { + use Bytecode::*; + use Operation::*; + // We currently track `&mut` and `borrow_global_mut` as origins. + if let Call(id, dsts, BorrowLoc | BorrowGlobal(..), _, _) = instr { + debug_assert!(dsts.len() == 1); + if target.get_local_type(dsts[0]).is_mutable_reference() { + // The location of whichever instruction appears first for the origin + // is used for reporting. + self.add_origin(dsts[0], target.get_bytecode_loc(*id)); + } + } + } + + /// Update derived edges given `instr`. + fn update_derived_edges(&mut self, target: &FunctionTarget, instr: &Bytecode) { + use Bytecode::*; + use Operation::*; + match instr { + Assign(_, dst, src, _) => { + if self.node_exists(*src) { + self.add_derived_edge(*dst, *src); + } + }, + Call(_, dsts, BorrowField(..) | BorrowVariantField(..), srcs, ..) => { + debug_assert!(srcs.len() == 1 && dsts.len() == 1); + if self.node_exists(srcs[0]) + && target.get_local_type(dsts[0]).is_mutable_reference() + { + self.add_derived_edge(dsts[0], srcs[0]); + } + }, + _ => {}, + } + } + + /// Update mutable usages given `instr`. + fn update_mutably_used(&mut self, env: &GlobalEnv, instr: &Bytecode) { + use Bytecode::*; + use Operation::*; + match instr { + Call(_, _, WriteRef, srcs, _) => { + self.set_and_propagate_mutably_used(srcs[0]); + }, + Call(_, _, Function(mid, fid, _), srcs, _) => { + let callee_env = env.get_function_qid(mid.qualified(*fid)); + callee_env + .get_parameter_types() + .iter() + .enumerate() + .filter(|(_, ty)| ty.is_mutable_reference()) + .map(|(i, _)| srcs[i]) + .for_each(|src| self.set_and_propagate_mutably_used(src)); + }, + _ => {}, + } + } + + /// Add an origin to the tracker. + fn add_origin(&mut self, origin: TempIndex, loc: Loc) { + self.origins.entry(origin).or_insert(loc); + self.derived_edges.entry(origin).or_default(); + } + + /// Check if a node exists in the tracker. + fn node_exists(&self, node: TempIndex) -> bool { + self.derived_edges.contains_key(&node) + } + + /// Add a derived edge to the tracker. + fn add_derived_edge(&mut self, from: TempIndex, to: TempIndex) { + self.derived_edges.entry(from).or_default().insert(to); + self.propagate_mutably_used(from, to); + } + + /// Propagate mutably used information from `from` to `to`. + fn propagate_mutably_used(&mut self, from: TempIndex, to: TempIndex) { + if self.mutably_used.contains(&from) { + self.set_and_propagate_mutably_used(to); + } + } + + /// Set a mutable reference as mutably used. + /// Propagate this information transitively through derived edges. + /// Propagation is stopped early if a node is already marked as mutably used. + fn set_and_propagate_mutably_used(&mut self, node: TempIndex) { + let mut mutably_used = std::mem::take(&mut self.mutably_used); + self.set_and_propagate_mutably_used_helper(node, &mut mutably_used); + self.mutably_used = mutably_used; + } + + /// Helper function for `set_and_propagate_mutably_used`. + /// Note that `self` is lacking the `mutably_used` field for the duration of + /// this method (and it is instead passed separately and explicitly). + fn set_and_propagate_mutably_used_helper( + &self, + node: TempIndex, + mutably_used: &mut BTreeSet, + ) { + if !mutably_used.insert(node) { + // Stop early if a node is already marked as mutably used. + return; + } + if let Some(parents) = self.derived_edges.get(&node) { + for parent in parents { + self.set_and_propagate_mutably_used_helper(*parent, mutably_used); + } + } + } +} + +/// Linter for detecting needless mutable references. +pub struct NeedlessMutableReference {} + +impl StacklessBytecodeLinter for NeedlessMutableReference { + fn get_lint_checker(&self) -> LintChecker { + LintChecker::NeedlessMutableReference + } + + fn check(&self, target: &FunctionTarget) { + let needless_mutable_refs = MutableReferenceUsageTracker::get_needless_mutable_refs(target); + for loc in needless_mutable_refs { + if loc.is_inlined() { + continue; + } + self.warning( + target.global_env(), + &loc, + "Needless mutable reference or borrow: consider using immutable reference or borrow instead", + ); + } + } +} diff --git a/third_party/move/move-compiler-v2/tests/lints/model_ast_lints/needless_deref_ref_warn.exp b/third_party/move/move-compiler-v2/tests/lints/model_ast_lints/needless_deref_ref_warn.exp index 4c0e855df81b0..218d94dfe989c 100644 --- a/third_party/move/move-compiler-v2/tests/lints/model_ast_lints/needless_deref_ref_warn.exp +++ b/third_party/move/move-compiler-v2/tests/lints/model_ast_lints/needless_deref_ref_warn.exp @@ -129,4 +129,14 @@ warning: [lint] Needless pair of `*` and `&` operators: consider removing them = To suppress this warning, annotate the function/module with the attribute `#[lint::skip(needless_deref_ref)]`. +Diagnostics: +warning: [lint] Needless mutable reference or borrow: consider using immutable reference or borrow instead + ┌─ tests/lints/model_ast_lints/needless_deref_ref_warn.move:35:15 + │ +35 │ *&mut borrow_global_mut(addr).y + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^ + │ + = To suppress this warning, annotate the function/module with the attribute `#[lint::skip(needless_mutable_reference)]`. + + ============ bytecode verification succeeded ======== diff --git a/third_party/move/move-compiler-v2/tests/lints/model_ast_lints/needless_ref_in_field_access_warn.exp b/third_party/move/move-compiler-v2/tests/lints/model_ast_lints/needless_ref_in_field_access_warn.exp index b637f3baae6a3..a0d938626a2eb 100644 --- a/third_party/move/move-compiler-v2/tests/lints/model_ast_lints/needless_ref_in_field_access_warn.exp +++ b/third_party/move/move-compiler-v2/tests/lints/model_ast_lints/needless_ref_in_field_access_warn.exp @@ -177,4 +177,38 @@ warning: [lint] Needless `&mut` taken for field access: consider removing `&mut` = To suppress this warning, annotate the function/module with the attribute `#[lint::skip(needless_ref_in_field_access)]`. +Diagnostics: +warning: [lint] Needless mutable reference or borrow: consider using immutable reference or borrow instead + ┌─ tests/lints/model_ast_lints/needless_ref_in_field_access_warn.move:51:9 + │ +51 │ (&mut make_S()).y.a + (&mut (&mut s).y).a + │ ^^^^^^^^^^^^^^^ + │ + = To suppress this warning, annotate the function/module with the attribute `#[lint::skip(needless_mutable_reference)]`. + +warning: [lint] Needless mutable reference or borrow: consider using immutable reference or borrow instead + ┌─ tests/lints/model_ast_lints/needless_ref_in_field_access_warn.move:51:37 + │ +51 │ (&mut make_S()).y.a + (&mut (&mut s).y).a + │ ^^^^^^^^ + │ + = To suppress this warning, annotate the function/module with the attribute `#[lint::skip(needless_mutable_reference)]`. + +warning: [lint] Needless mutable reference or borrow: consider using immutable reference or borrow instead + ┌─ tests/lints/model_ast_lints/needless_ref_in_field_access_warn.move:90:18 + │ +90 │ (&e).0 + (&mut e).0 + │ ^^^^^^^^ + │ + = To suppress this warning, annotate the function/module with the attribute `#[lint::skip(needless_mutable_reference)]`. + +warning: [lint] Needless mutable reference or borrow: consider using immutable reference or borrow instead + ┌─ tests/lints/model_ast_lints/needless_ref_in_field_access_warn.move:125:18 + │ +125 │ (&s).x + (&mut s).x + │ ^^^^^^^^ + │ + = To suppress this warning, annotate the function/module with the attribute `#[lint::skip(needless_mutable_reference)]`. + + ============ bytecode verification succeeded ======== diff --git a/third_party/move/move-compiler-v2/tests/lints/stackless_bytecode_lints/needless_mutable_reference_warn.exp b/third_party/move/move-compiler-v2/tests/lints/stackless_bytecode_lints/needless_mutable_reference_warn.exp new file mode 100644 index 0000000000000..6c2805c244b0c --- /dev/null +++ b/third_party/move/move-compiler-v2/tests/lints/stackless_bytecode_lints/needless_mutable_reference_warn.exp @@ -0,0 +1,150 @@ + +Diagnostics: +warning: [lint] Needless pair of `*` and `&mut` operators: consider removing them + ┌─ tests/lints/stackless_bytecode_lints/needless_mutable_reference_warn.move:200:9 + │ +200 │ *&mut s.x + │ ^^^^^^^^^ + │ + = To suppress this warning, annotate the function/module with the attribute `#[lint::skip(needless_deref_ref)]`. + +warning: [lint] Needless pair of `*` and `&mut` operators: consider removing them + ┌─ tests/lints/stackless_bytecode_lints/needless_mutable_reference_warn.move:204:9 + │ +204 │ *&mut s + │ ^^^^^^^ + │ + = To suppress this warning, annotate the function/module with the attribute `#[lint::skip(needless_deref_ref)]`. + +warning: [lint] Needless pair of `*` and `&mut` operators: consider removing them + ┌─ tests/lints/stackless_bytecode_lints/needless_mutable_reference_warn.move:212:9 + │ +212 │ *&mut u + │ ^^^^^^^ + │ + = To suppress this warning, annotate the function/module with the attribute `#[lint::skip(needless_deref_ref)]`. + + +Diagnostics: +warning: Unused assignment to `x`. Consider removing or prefixing with an underscore: `_x` + ┌─ tests/lints/stackless_bytecode_lints/needless_mutable_reference_warn.move:175:9 + │ +175 │ x = y; // Produces a cycle in the `derived_edges`. + │ ^^^^^ + + +Diagnostics: +warning: [lint] Needless mutable reference or borrow: consider using immutable reference or borrow instead + ┌─ tests/lints/stackless_bytecode_lints/needless_mutable_reference_warn.move:14:17 + │ +14 │ let y = &mut x; + │ ^^^^^^ + │ + = To suppress this warning, annotate the function/module with the attribute `#[lint::skip(needless_mutable_reference)]`. + +warning: [lint] Needless mutable reference or borrow: consider using immutable reference or borrow instead + ┌─ tests/lints/stackless_bytecode_lints/needless_mutable_reference_warn.move:24:17 + │ +24 │ let y = &mut x; + │ ^^^^^^ + │ + = To suppress this warning, annotate the function/module with the attribute `#[lint::skip(needless_mutable_reference)]`. + +warning: [lint] Needless mutable reference or borrow: consider using immutable reference or borrow instead + ┌─ tests/lints/stackless_bytecode_lints/needless_mutable_reference_warn.move:41:20 + │ +41 │ fun test3_warn(s: &mut S): u64 { + │ ^ + │ + = To suppress this warning, annotate the function/module with the attribute `#[lint::skip(needless_mutable_reference)]`. + +warning: [lint] Needless mutable reference or borrow: consider using immutable reference or borrow instead + ┌─ tests/lints/stackless_bytecode_lints/needless_mutable_reference_warn.move:109:17 + │ +109 │ let r = borrow_global_mut(addr); + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^ + │ + = To suppress this warning, annotate the function/module with the attribute `#[lint::skip(needless_mutable_reference)]`. + +warning: [lint] Needless mutable reference or borrow: consider using immutable reference or borrow instead + ┌─ tests/lints/stackless_bytecode_lints/needless_mutable_reference_warn.move:114:17 + │ +114 │ let r = borrow_global_mut(addr); + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^ + │ + = To suppress this warning, annotate the function/module with the attribute `#[lint::skip(needless_mutable_reference)]`. + +warning: [lint] Needless mutable reference or borrow: consider using immutable reference or borrow instead + ┌─ tests/lints/stackless_bytecode_lints/needless_mutable_reference_warn.move:119:21 + │ +119 │ fun test_warn_3(s: &mut S, p: bool, addr: address): u64 acquires S { + │ ^ + │ + = To suppress this warning, annotate the function/module with the attribute `#[lint::skip(needless_mutable_reference)]`. + +warning: [lint] Needless mutable reference or borrow: consider using immutable reference or borrow instead + ┌─ tests/lints/stackless_bytecode_lints/needless_mutable_reference_warn.move:120:19 + │ +120 │ let ref = borrow_global_mut(addr); + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^ + │ + = To suppress this warning, annotate the function/module with the attribute `#[lint::skip(needless_mutable_reference)]`. + +warning: [lint] Needless mutable reference or borrow: consider using immutable reference or borrow instead + ┌─ tests/lints/stackless_bytecode_lints/needless_mutable_reference_warn.move:153:22 + │ +153 │ a = &mut e1.0; + │ ^^ + │ + = To suppress this warning, annotate the function/module with the attribute `#[lint::skip(needless_mutable_reference)]`. + +warning: [lint] Needless mutable reference or borrow: consider using immutable reference or borrow instead + ┌─ tests/lints/stackless_bytecode_lints/needless_mutable_reference_warn.move:155:22 + │ +155 │ a = &mut e2.0; + │ ^^ + │ + = To suppress this warning, annotate the function/module with the attribute `#[lint::skip(needless_mutable_reference)]`. + +warning: [lint] Needless mutable reference or borrow: consider using immutable reference or borrow instead + ┌─ tests/lints/stackless_bytecode_lints/needless_mutable_reference_warn.move:160:21 + │ +160 │ fun test_warn_2(a: &mut E, b: &mut E) { + │ ^ + │ + = To suppress this warning, annotate the function/module with the attribute `#[lint::skip(needless_mutable_reference)]`. + +warning: [lint] Needless mutable reference or borrow: consider using immutable reference or borrow instead + ┌─ tests/lints/stackless_bytecode_lints/needless_mutable_reference_warn.move:160:32 + │ +160 │ fun test_warn_2(a: &mut E, b: &mut E) { + │ ^ + │ + = To suppress this warning, annotate the function/module with the attribute `#[lint::skip(needless_mutable_reference)]`. + +warning: [lint] Needless mutable reference or borrow: consider using immutable reference or borrow instead + ┌─ tests/lints/stackless_bytecode_lints/needless_mutable_reference_warn.move:173:28 + │ +173 │ public fun test_warn_3(x: &mut u64) { + │ ^ + │ + = To suppress this warning, annotate the function/module with the attribute `#[lint::skip(needless_mutable_reference)]`. + +warning: [lint] Needless mutable reference or borrow: consider using immutable reference or borrow instead + ┌─ tests/lints/stackless_bytecode_lints/needless_mutable_reference_warn.move:242:17 + │ +242 │ let u = &mut s; + │ ^^^^^^ + │ + = To suppress this warning, annotate the function/module with the attribute `#[lint::skip(needless_mutable_reference)]`. + +warning: [lint] Needless mutable reference or borrow: consider using immutable reference or borrow instead + ┌─ tests/lints/stackless_bytecode_lints/needless_mutable_reference_warn.move:252:17 + │ +252 │ let u = &mut s; + │ ^^^^^^ + │ + = To suppress this warning, annotate the function/module with the attribute `#[lint::skip(needless_mutable_reference)]`. + + +============ bytecode verification succeeded ======== diff --git a/third_party/move/move-compiler-v2/tests/lints/stackless_bytecode_lints/needless_mutable_reference_warn.move b/third_party/move/move-compiler-v2/tests/lints/stackless_bytecode_lints/needless_mutable_reference_warn.move new file mode 100644 index 0000000000000..980467bf51f68 --- /dev/null +++ b/third_party/move/move-compiler-v2/tests/lints/stackless_bytecode_lints/needless_mutable_reference_warn.move @@ -0,0 +1,255 @@ +module 0xc0ffee::m { + fun consume_mut(x: &mut u64) { + *x = 10; + } + + fun consume_immut(_x: &u64) {} + + fun test1_no_warn(x: u64) { + let y = &mut x; + *y = 10; + } + + fun test1_warn(x: u64): u64 { + let y = &mut x; + *y + } + + fun test2_no_warn(x: u64) { + let y = &mut x; + consume_mut(y); + } + + fun test2_warn(x: u64) { + let y = &mut x; + consume_immut(y); + } + + struct S { + x: u64, + y: U, + } + + struct U { + u: u64, + } + + fun test3_no_warn(s: &mut S) { + s.y.u = 42; + } + + fun test3_warn(s: &mut S): u64 { + s.y.u + } + + fun test4_no_warn(s: &mut S): u64 { + s.y.u = 42; + s.x + } + + fun test5_no_warn(s: &mut S) { + let t = s; + consume_mut(&mut t.y.u); + } + + fun test6_no_warn() { + let x = 1; + let z = 2; + let y = &mut x; + consume_immut(y); + y = &mut z; + *y = 5; + } +} + + +module 0xc0ffee::n { + struct R has key, store { + x: u64 + } + + struct S has key { + r: R + } + + public fun test_no_warn_1(addr: address) acquires R { + let r = borrow_global_mut(addr); + r.x = 5; + } + + public fun test_no_warn_2(addr: address): u64 acquires R { + let r = borrow_global_mut(addr); + let y = &mut r.x; + *y = 5; + *y + } + + public fun test_no_warn_3(addr: address) acquires S { + let s = borrow_global_mut(addr); + let y = &mut s.r; + y.x = 5; + } + + fun helper_mut(r: &mut R) { + r.x = 5; + } + + fun helper_immut(_r: &R) {} + + public fun test_no_warn_4(addr: address, p: bool) acquires R { + let r = borrow_global_mut(addr); + if (p) { + helper_mut(r); + } else { + helper_immut(r); + } + } + + public fun test_warn_1(addr: address) acquires R { + let r = borrow_global_mut(addr); + helper_immut(r); + } + + public fun test_warn_2(addr: address): u64 acquires R { + let r = borrow_global_mut(addr); + let y = r.x; + y + } + + fun test_warn_3(s: &mut S, p: bool, addr: address): u64 acquires S { + let ref = borrow_global_mut(addr); + if (p) { + ref = s; + }; + ref.r.x + } + + fun test_no_warn_5(p: bool, a: address, s: &S): u64 acquires S { + if (p) { + s = borrow_global(a); + }; + let y = &s.r; + y.x + } +} + +module 0xc0ffee::o { + enum E has drop { + A(u64), + B(u64), + } + + fun test_no_warn_1(e1: E, e2: E) { + let a = &mut e1; + let b = &mut a.0; + *b = 5; + b = &mut e2.0; + *b = 1; + } + + fun test_warn_1(e1: E, e2: E, p: bool): u64 { + let a; + if (p) { + a = &mut e1.0; + } else { + a = &mut e2.0; + }; + *a + } + + fun test_warn_2(a: &mut E, b: &mut E) { + let x = 1; + loop { + let temp = a; + a = b; + b = temp; + x = x + 1; + if (x == 10) { + break; + } + } + } + + public fun test_warn_3(x: &mut u64) { + let y = x; + x = y; // Produces a cycle in the `derived_edges`. + } + + public fun test_no_warn_3(x: &mut u64) { + let y = x; + x = y; + *x = 5; + } + + #[lint::skip(needless_mutable_reference)] + fun test_no_warn_4(e1: E, e2: E, p: bool): u64 { + let a; + if (p) { + a = &mut e1.0; + } else { + a = &mut e2.0; + }; + *a + } + + struct S has drop { + x: u64, + } + + fun test_warn_5(s: S): u64 { + *&mut s.x + } + + fun test_no_warn_5(s: S): S { + *&mut s + } + + struct U has copy, drop { + x: u64, + } + + fun test_no_warn_6(u: U): U { + *&mut u + } + + // This should produce a warning once `vector::borrow_mut` is added to origins. + fun test_warn_7(): u64 { + let x = vector[1, 5, 5]; + let y = &mut x[0]; + *y + } +} + +module 0xc0ffee::more_struct_tests { + struct S has copy, drop { + x: u64 + } + + fun consume_mut(s: &mut S) { + s.x = 10; + } + + fun consume_immut(s: &S): u64 { + s.x + } + + fun test1_no_warn(s: S) { + let u = &mut s; + *u = S { x: 10 }; + } + + fun test1_warn(s: S): S { + let u = &mut s; + *u + } + + fun test2_no_warn(s: S) { + let u = &mut s; + consume_mut(u); + } + + fun test2_warn(s: S) { + let u = &mut s; + consume_immut(u); + } +}