-
Notifications
You must be signed in to change notification settings - Fork 11.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[Linter] Redundant ref deref (#16491)
## Description This lint rule detects and reports unnecessary temporary borrow operations followed by a dereference and a local borrow in Move code. It aims to improve code efficiency by suggesting direct usage of expressions without redundant operations. Implementation The lint rule is implemented as follows: A temporary borrow (TempBorrow) Followed by a dereference (Dereference) Where the dereferenced expression is either a BorrowLocal, Borrow, or another TempBorrow ## Test plan Added more use case including false positive, false negative case ## Release notes - [ ] Protocol: - [ ] Nodes (Validators and Full nodes): - [ ] Indexer: - [ ] JSON-RPC: - [ ] GraphQL: - [ ] CLI: - [ ] Rust SDK: --------- Co-authored-by: jamedzung <[email protected]> Co-authored-by: Lu Zhang <[email protected]> Co-authored-by: Cameron Swords <[email protected]>
- Loading branch information
1 parent
6b23159
commit aa2ca1d
Showing
22 changed files
with
1,297 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
155 changes: 155 additions & 0 deletions
155
external-crates/move/crates/move-compiler/src/linters/redundant_ref_deref.rs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,155 @@ | ||
// Copyright (c) The Move Contributors | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
// Implements lint rule for Move code to detect redundant ref/deref patterns. | ||
// It identifies and reports unnecessary temporary borrow followed by a deref, suggesting either | ||
// removal or conversion to `copy`. | ||
|
||
use crate::linters::StyleCodes; | ||
use crate::{ | ||
diag, | ||
diagnostics::WarningFilters, | ||
shared::CompilationEnv, | ||
typing::{ | ||
ast::{self as T, Exp, UnannotatedExp_ as TE}, | ||
visitor::{TypingVisitorConstructor, TypingVisitorContext}, | ||
}, | ||
}; | ||
|
||
pub struct RedundantRefDerefVisitor; | ||
|
||
pub struct Context<'a> { | ||
env: &'a mut CompilationEnv, | ||
} | ||
|
||
impl TypingVisitorConstructor for RedundantRefDerefVisitor { | ||
type Context<'a> = Context<'a>; | ||
|
||
fn context<'a>(env: &'a mut CompilationEnv, _program: &T::Program) -> Self::Context<'a> { | ||
Context { env } | ||
} | ||
} | ||
|
||
impl TypingVisitorContext for Context<'_> { | ||
fn add_warning_filter_scope(&mut self, filter: WarningFilters) { | ||
self.env.add_warning_filter_scope(filter) | ||
} | ||
fn pop_warning_filter_scope(&mut self) { | ||
self.env.pop_warning_filter_scope() | ||
} | ||
|
||
fn visit_exp_custom(&mut self, exp: &Exp) -> bool { | ||
self.check_redundant_ref_deref(exp); | ||
false | ||
} | ||
} | ||
|
||
impl Context<'_> { | ||
// Check for &* pattern | ||
fn check_redundant_ref_deref(&mut self, exp: &Exp) { | ||
let TE::Dereference(deref_exp) = &exp.exp.value else { | ||
return; | ||
}; | ||
// This is a carve-out to handle cases like `&(s.value), which generate a field borrow and | ||
// dereference to perform a `copy`. In those cases, the location information is reused for | ||
// both, meaning it was generated by typing, and thus not subject to warnings. | ||
// TODO(cswords): In the future, we should mark which of these are generated during typing | ||
// to handle paths. | ||
if exp.exp.loc == deref_exp.exp.loc { | ||
return; | ||
} | ||
match &deref_exp.exp.value { | ||
TE::TempBorrow(_, inner) if is_simple_deref_ref_exp(inner) => self.env.add_diag(diag!( | ||
StyleCodes::RedundantRefDeref.diag_info(), | ||
( | ||
exp.exp.loc, | ||
"Redundant borrow-dereference detected. \ | ||
Remove this borrow-deref and use the expression directly." | ||
) | ||
)), | ||
TE::TempBorrow(_, inner) if all_deref_borrow(inner) => self.env.add_diag(diag!( | ||
StyleCodes::RedundantRefDeref.diag_info(), | ||
( | ||
exp.exp.loc, | ||
"Redundant borrow-dereference detected. \ | ||
Use the inner expression directly." | ||
) | ||
)), | ||
TE::Borrow(false, _, _) if exp.exp.loc != deref_exp.exp.loc => { | ||
self.env.add_diag(diag!( | ||
StyleCodes::RedundantRefDeref.diag_info(), | ||
( | ||
exp.exp.loc, | ||
"Redundant borrow-dereference detected. \ | ||
Use the field access directly." | ||
) | ||
)) | ||
} | ||
TE::Borrow(_, _, _) | TE::BorrowLocal(_, _) => self.env.add_diag(diag!( | ||
StyleCodes::RedundantRefDeref.diag_info(), | ||
( | ||
exp.exp.loc, | ||
"Redundant borrow-dereference detected. \ | ||
Replace this borrow-deref with 'copy'." | ||
) | ||
)), | ||
_ => (), | ||
} | ||
} | ||
} | ||
|
||
/// Indicates if the expression is of the form `[&*]+....e` | ||
fn all_deref_borrow(exp: &Exp) -> bool { | ||
let TE::Dereference(deref_exp) = &exp.exp.value else { | ||
return false; | ||
}; | ||
match &deref_exp.exp.value { | ||
TE::TempBorrow(_, inner) => all_deref_borrow(inner), | ||
TE::Borrow(_, _, _) | TE::BorrowLocal(_, _) => true, | ||
_ => false, | ||
} | ||
} | ||
|
||
/// Indicates if the expression at hand is of a form where `&*&` can always be reduces to `&`, such | ||
/// as function calls and constants. | ||
fn is_simple_deref_ref_exp(exp: &Exp) -> bool { | ||
match &exp.exp.value { | ||
TE::Value(_) => true, | ||
TE::Constant(_, _) => true, | ||
TE::ErrorConstant { .. } => true, | ||
TE::ModuleCall(_) => true, | ||
TE::Vector(_, _, _, _) => true, | ||
TE::Copy { .. } => true, | ||
|
||
TE::Cast(inner, _) | TE::Annotate(inner, _) => is_simple_deref_ref_exp(inner), | ||
|
||
TE::Move { .. } => false, // Copy case | ||
TE::Use(_) => todo!(), | ||
TE::IfElse(_, _, _) | ||
| TE::Match(_, _) | ||
| TE::VariantMatch(_, _, _) | ||
| TE::Loop { .. } | ||
| TE::NamedBlock(_, _) | ||
| TE::Block(_) | ||
| TE::Dereference(_) | ||
| TE::UnaryExp(_, _) | ||
| TE::BinopExp(_, _, _, _) | ||
| TE::Pack(_, _, _, _) | ||
| TE::PackVariant(_, _, _, _, _) | ||
| TE::ExpList(_) | ||
| TE::Borrow(_, _, _) | ||
| TE::TempBorrow(_, _) | ||
| TE::BorrowLocal(_, _) => false, | ||
// These are already errors | ||
TE::Unit { .. } | ||
| TE::Builtin(_, _) | ||
| TE::While(_, _, _) | ||
| TE::Assign(_, _, _) | ||
| TE::Return(_) | ||
| TE::Abort(_) | ||
| TE::Mutate(_, _) | ||
| TE::Give(_, _) | ||
| TE::Continue(_) | ||
| TE::UnresolvedError => false, | ||
} | ||
} |
10 changes: 10 additions & 0 deletions
10
external-crates/move/crates/move-compiler/tests/linter/correct_redundant_ref_deref.move
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
module 0x42::M { | ||
struct MyResource has copy, drop{ | ||
value: u64, | ||
} | ||
|
||
public fun test_borrow_deref_ref() { | ||
let resource = MyResource { value: 10 }; | ||
let _ref1 = &resource; | ||
} | ||
} |
172 changes: 172 additions & 0 deletions
172
external-crates/move/crates/move-compiler/tests/linter/incorrect_redundant_ref_deref.exp
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,172 @@ | ||
warning[Lint W01009]: redundant reference/dereference | ||
┌─ tests/linter/incorrect_redundant_ref_deref.move:10:21 | ||
│ | ||
10 │ let _ref = &*(&resource); // Redundant borrow-dereference | ||
│ ^^^^^^^^^^^^ Redundant borrow-dereference detected. Replace this borrow-deref with 'copy'. | ||
│ | ||
= This warning can be suppressed with '#[allow(lint(redundant_ref_deref))]' applied to the 'module' or module member ('const', 'fun', or 'struct') | ||
|
||
warning[Lint W01009]: redundant reference/dereference | ||
┌─ tests/linter/incorrect_redundant_ref_deref.move:15:25 | ||
│ | ||
15 │ let _ref = &mut *(&mut resource); // Redundant mutable borrow-dereference | ||
│ ^^^^^^^^^^^^^^^^ Redundant borrow-dereference detected. Replace this borrow-deref with 'copy'. | ||
│ | ||
= This warning can be suppressed with '#[allow(lint(redundant_ref_deref))]' applied to the 'module' or module member ('const', 'fun', or 'struct') | ||
|
||
warning[Lint W01009]: redundant reference/dereference | ||
┌─ tests/linter/incorrect_redundant_ref_deref.move:20:22 | ||
│ | ||
20 │ let _value = *(&resource.value); // Redundant dereference of field borrow | ||
│ ^^^^^^^^^^^^^^^^^^ Redundant borrow-dereference detected. Use the field access directly. | ||
│ | ||
= This warning can be suppressed with '#[allow(lint(redundant_ref_deref))]' applied to the 'module' or module member ('const', 'fun', or 'struct') | ||
|
||
warning[Lint W01009]: redundant reference/dereference | ||
┌─ tests/linter/incorrect_redundant_ref_deref.move:57:21 | ||
│ | ||
57 │ let _ref = &*(&*(&resource)); // Triple nested borrow-dereference, might be missed | ||
│ ^^^^^^^^^^^^^^^^ Redundant borrow-dereference detected. Use the inner expression directly. | ||
│ | ||
= This warning can be suppressed with '#[allow(lint(redundant_ref_deref))]' applied to the 'module' or module member ('const', 'fun', or 'struct') | ||
|
||
warning[Lint W01009]: redundant reference/dereference | ||
┌─ tests/linter/incorrect_redundant_ref_deref.move:57:24 | ||
│ | ||
57 │ let _ref = &*(&*(&resource)); // Triple nested borrow-dereference, might be missed | ||
│ ^^^^^^^^^^^^ Redundant borrow-dereference detected. Replace this borrow-deref with 'copy'. | ||
│ | ||
= This warning can be suppressed with '#[allow(lint(redundant_ref_deref))]' applied to the 'module' or module member ('const', 'fun', or 'struct') | ||
|
||
error[E04010]: cannot infer type | ||
┌─ tests/linter/incorrect_redundant_ref_deref.move:68:13 | ||
│ | ||
68 │ let _value = *((&resource).value); // Complex expression, might be missed | ||
│ ^^^^^^ Could not infer this type. Try adding an annotation | ||
|
||
error[E04007]: incompatible types | ||
┌─ tests/linter/incorrect_redundant_ref_deref.move:68:22 | ||
│ | ||
3 │ value: u64, | ||
│ --- Given: 'u64' | ||
· | ||
68 │ let _value = *((&resource).value); // Complex expression, might be missed | ||
│ ^^^^^^^^^^^^^^^^^^^^ | ||
│ │ | ||
│ Invalid dereference. | ||
│ Expected: '&_' | ||
|
||
error[E04010]: cannot infer type | ||
┌─ tests/linter/incorrect_redundant_ref_deref.move:68:22 | ||
│ | ||
68 │ let _value = *((&resource).value); // Complex expression, might be missed | ||
│ ^^^^^^^^^^^^^^^^^^^^ Could not infer this type. Try adding an annotation | ||
|
||
warning[Lint W01009]: redundant reference/dereference | ||
┌─ tests/linter/incorrect_redundant_ref_deref.move:75:21 | ||
│ | ||
75 │ let _ref = &*&resource.value; // Redundant borrow-dereference on field | ||
│ ^^^^^^^^^^^^^^^^ Redundant borrow-dereference detected. Use the field access directly. | ||
│ | ||
= This warning can be suppressed with '#[allow(lint(redundant_ref_deref))]' applied to the 'module' or module member ('const', 'fun', or 'struct') | ||
|
||
warning[Lint W01009]: redundant reference/dereference | ||
┌─ tests/linter/incorrect_redundant_ref_deref.move:80:21 | ||
│ | ||
80 │ let _ref = &*&(&resource).value; // Nested redundant borrow-dereference on field | ||
│ ^^^^^^^^^^^^^^^^^^^ Redundant borrow-dereference detected. Use the field access directly. | ||
│ | ||
= This warning can be suppressed with '#[allow(lint(redundant_ref_deref))]' applied to the 'module' or module member ('const', 'fun', or 'struct') | ||
|
||
warning[Lint W01009]: redundant reference/dereference | ||
┌─ tests/linter/incorrect_redundant_ref_deref.move:86:21 | ||
│ | ||
86 │ let _ref = &*&0; // Redundant borrow-dereference on literal | ||
│ ^^^ Redundant borrow-dereference detected. Remove this borrow-deref and use the expression directly. | ||
│ | ||
= This warning can be suppressed with '#[allow(lint(redundant_ref_deref))]' applied to the 'module' or module member ('const', 'fun', or 'struct') | ||
|
||
warning[Lint W01009]: redundant reference/dereference | ||
┌─ tests/linter/incorrect_redundant_ref_deref.move:94:21 | ||
│ | ||
94 │ let _ref = &*&get_resource(); // Redundant borrow-dereference on function call result | ||
│ ^^^^^^^^^^^^^^^^ Redundant borrow-dereference detected. Remove this borrow-deref and use the expression directly. | ||
│ | ||
= This warning can be suppressed with '#[allow(lint(redundant_ref_deref))]' applied to the 'module' or module member ('const', 'fun', or 'struct') | ||
|
||
warning[Lint W01009]: redundant reference/dereference | ||
┌─ tests/linter/incorrect_redundant_ref_deref.move:107:21 | ||
│ | ||
107 │ let _ref = &*&(&*&resource.value); // Multiple redundant borrows on field | ||
│ ^^^^^^^^^^^^^^^^^^^^^ Redundant borrow-dereference detected. Use the inner expression directly. | ||
│ | ||
= This warning can be suppressed with '#[allow(lint(redundant_ref_deref))]' applied to the 'module' or module member ('const', 'fun', or 'struct') | ||
|
||
error[E04004]: expected a single non-reference type | ||
┌─ tests/linter/incorrect_redundant_ref_deref.move:107:22 | ||
│ | ||
107 │ let _ref = &*&(&*&resource.value); // Multiple redundant borrows on field | ||
│ ^^^^^^^^^^^^^^^^^^^^ | ||
│ │ │ | ||
│ │ Expected a single non-reference type, but found: '&u64' | ||
│ Invalid borrow | ||
|
||
warning[Lint W01009]: redundant reference/dereference | ||
┌─ tests/linter/incorrect_redundant_ref_deref.move:107:25 | ||
│ | ||
107 │ let _ref = &*&(&*&resource.value); // Multiple redundant borrows on field | ||
│ ^^^^^^^^^^^^^^^^ Redundant borrow-dereference detected. Use the field access directly. | ||
│ | ||
= This warning can be suppressed with '#[allow(lint(redundant_ref_deref))]' applied to the 'module' or module member ('const', 'fun', or 'struct') | ||
|
||
warning[W09002]: unused variable | ||
┌─ tests/linter/incorrect_redundant_ref_deref.move:111:13 | ||
│ | ||
111 │ let mut resource = MyResource { value: 10 }; | ||
│ ^^^ Unused local variable 'mut'. Consider removing or prefixing with an underscore: '_mut' | ||
│ | ||
= This warning can be suppressed with '#[allow(unused_variable)]' applied to the 'module' or module member ('const', 'fun', or 'struct') | ||
|
||
error[E01002]: unexpected token | ||
┌─ tests/linter/incorrect_redundant_ref_deref.move:111:17 | ||
│ | ||
111 │ let mut resource = MyResource { value: 10 }; | ||
│ ^^^^^^^^ | ||
│ │ | ||
│ Unexpected 'resource' | ||
│ Expected ';' | ||
|
||
error[E04010]: cannot infer type | ||
┌─ tests/linter/incorrect_redundant_ref_deref.move:112:21 | ||
│ | ||
112 │ let _ref = &*&mut *&resource; // Mixed mutable and immutable redundant borrows | ||
│ ^^^^^^^^^^^^^^^^ Could not infer this type. Try adding an annotation | ||
|
||
error[E04010]: cannot infer type | ||
┌─ tests/linter/incorrect_redundant_ref_deref.move:112:27 | ||
│ | ||
112 │ let _ref = &*&mut *&resource; // Mixed mutable and immutable redundant borrows | ||
│ ^^^^^^^^^^ Could not infer this type. Try adding an annotation | ||
|
||
error[E03009]: unbound variable | ||
┌─ tests/linter/incorrect_redundant_ref_deref.move:112:29 | ||
│ | ||
112 │ let _ref = &*&mut *&resource; // Mixed mutable and immutable redundant borrows | ||
│ ^^^^^^^^ Unbound variable 'resource' | ||
|
||
warning[Lint W01009]: redundant reference/dereference | ||
┌─ tests/linter/incorrect_redundant_ref_deref.move:117:25 | ||
│ | ||
117 │ let _value = *&(*&resource.value + 1); // Redundant borrows in complex expression | ||
│ ^^^^^^^^^^^^^^^^ Redundant borrow-dereference detected. Use the field access directly. | ||
│ | ||
= This warning can be suppressed with '#[allow(lint(redundant_ref_deref))]' applied to the 'module' or module member ('const', 'fun', or 'struct') | ||
|
||
warning[Lint W01009]: redundant reference/dereference | ||
┌─ tests/linter/incorrect_redundant_ref_deref.move:124:21 | ||
│ | ||
124 │ let _ref = &*(&resource.value); // Complex nested borrow on field, might be missed | ||
│ ^^^^^^^^^^^^^^^^^^ Redundant borrow-dereference detected. Use the field access directly. | ||
│ | ||
= This warning can be suppressed with '#[allow(lint(redundant_ref_deref))]' applied to the 'module' or module member ('const', 'fun', or 'struct') | ||
|
Oops, something went wrong.