Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 15 additions & 4 deletions anchor-lints-utils/src/diag_items.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,8 @@ pub enum DiagnoticItem {
SplTokenAccount,
/// `spl_token::state::Mint`
SplTokenMint,
/// `anchor_lang::prelude::LazyAccount`
AnchorLazyAccount,
}

impl DiagnoticItem {
Expand Down Expand Up @@ -149,6 +151,9 @@ impl DiagnoticItem {
DiagnoticItem::SplTokenMint => {
return None;
}
DiagnoticItem::AnchorLazyAccount => {
return None;
}
})
}

Expand Down Expand Up @@ -255,6 +260,7 @@ impl DiagnoticItem {
}
DiagnoticItem::SplTokenAccount => &["spl_token::state::Account"],
DiagnoticItem::SplTokenMint => &["spl_token::state::Mint"],
DiagnoticItem::AnchorLazyAccount => &["anchor_lang::prelude::LazyAccount"],
}
}

Expand Down Expand Up @@ -463,10 +469,10 @@ pub fn is_solana_instruction_type(tcx: TyCtxt, ty: Ty) -> bool {

pub fn is_box_type(tcx: TyCtxt, ty: Ty) -> bool {
let ty = ty.peel_refs();
if let ty::Adt(adt_def, _) = ty.kind() {
if let Some(box_def_id) = tcx.lang_items().owned_box() {
return adt_def.did() == box_def_id;
}
if let ty::Adt(adt_def, _) = ty.kind()
&& let Some(box_def_id) = tcx.lang_items().owned_box()
{
return adt_def.did() == box_def_id;
}
false
}
Expand All @@ -492,3 +498,8 @@ pub fn is_cpi_builder_constructor_fn(tcx: TyCtxt, def_id: DefId) -> bool {
|| path.contains("UnlockV1")
|| path.contains("RevokeStaking")
}

pub fn is_anchor_lazy_account_type(tcx: TyCtxt, ty: Ty) -> bool {
let ty = ty.peel_refs();
DiagnoticItem::AnchorLazyAccount.defid_is_type(tcx, ty)
}
42 changes: 41 additions & 1 deletion lints/missing_account_reload/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use std::collections::{HashMap, HashSet};

use anchor_lints_utils::utils::should_skip_function;
use anchor_lints_utils::{
diag_items::{DiagnoticItem, is_cpi_invoke_fn},
diag_items::{DiagnoticItem, is_anchor_lazy_account_type, is_cpi_invoke_fn},
mir_analyzer::{AnchorContextInfo, MirAnalyzer},
utils::get_hir_body_from_local_def_id,
};
Expand Down Expand Up @@ -124,6 +124,25 @@ impl<'tcx> LateLintPass<'tcx> for MissingAccountReload {
}
}
}
// Check for LazyAccount::load/LazyAccount::load_mut (reloads for LazyAccount)
else if let Some(name) = cx.tcx.opt_item_name(*fn_def_id)
&& (name.as_str() == "load" || name.as_str() == "load_mut")
&& let Some(receiver) = args.first()
&& let Operand::Move(place) | Operand::Copy(place) = &receiver.node
&& let Some(local) = place.as_local()
&& let Some(decl) = mir.local_decls.get(local)
{
let receiver_ty = decl.ty.peel_refs();
if is_anchor_lazy_account_type(cx.tcx, receiver_ty)
&& let Some(account_name_and_local) =
mir_analyzer.extract_account_name_from_local(&local, false)
{
account_reloads
.entry(account_name_and_local.account_name)
.or_default()
.insert(bb);
}
}
// Or a CPI invoke function
else if is_cpi_invoke_fn(cx.tcx, *fn_def_id)
|| mir_analyzer.takes_cpi_context(args)
Expand Down Expand Up @@ -375,6 +394,27 @@ pub fn analyze_nested_function_operations<'tcx>(
nested_function_blocks.push(block);
}
}
// Check for LazyAccount::load/LazyAccount::load_mut (reloads for LazyAccount)
else if let Some(name) = cx.tcx.opt_item_name(*def_id)
&& (name.as_str() == "load" || name.as_str() == "load_mut")
&& let Some(receiver) = args.first()
&& let Operand::Move(place) | Operand::Copy(place) = &receiver.node
&& let Some(local) = place.as_local()
&& let Some(decl) = mir_body.local_decls.get(local)
{
let receiver_ty = decl.ty.peel_refs();
if is_anchor_lazy_account_type(cx.tcx, receiver_ty)
&& let Some(block) = handle_account_reload_in_nested_function(
&mir_analyzer,
mir_body,
args,
*fn_span,
bb,
)
{
nested_function_blocks.push(block);
}
}
// Handle account access (deref) in nested function
else if cx
.tcx
Expand Down
8 changes: 5 additions & 3 deletions lints/missing_account_reload/src/utils/paths.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
use anchor_lints_utils::{
diag_items::{
is_account_info_type, is_anchor_account_loader_type, is_anchor_account_type,
is_anchor_interface_account_type, is_anchor_signer_type, is_anchor_system_account_type,
is_anchor_unchecked_account_type, is_box_type,
is_anchor_interface_account_type, is_anchor_lazy_account_type, is_anchor_signer_type,
is_anchor_system_account_type, is_anchor_unchecked_account_type, is_box_type,
},
mir_analyzer::AnchorContextInfo,
};
Expand Down Expand Up @@ -58,7 +58,9 @@ pub fn contains_deserialized_data<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) ->
if is_anchor_account_loader_type(cx.tcx, ty) {
return false;
}
if is_anchor_interface_account_type(cx.tcx, ty) || is_anchor_system_account_type(cx.tcx, ty)
if is_anchor_interface_account_type(cx.tcx, ty)
|| is_anchor_system_account_type(cx.tcx, ty)
|| is_anchor_lazy_account_type(cx.tcx, ty)
{
return true;
}
Expand Down
2 changes: 1 addition & 1 deletion lints/missing_account_reload/tests/test_program/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,4 @@ workspace = "../../../../tests"
crate-type = ["cdylib"]

[dependencies]
anchor-lang = { workspace = true }
anchor-lang = { workspace = true, features = ["lazy-account"] }
56 changes: 53 additions & 3 deletions lints/missing_account_reload/tests/test_program/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use anchor_lang::solana_program::{
system_instruction,
};

use anchor_lang::system_program::{transfer, Transfer};
use anchor_lang::system_program::{Transfer, transfer};

declare_id!("11111111111111111111111111111111");

Expand Down Expand Up @@ -319,18 +319,56 @@ pub mod missing_account_reload_tests {
}

// Pattern 17: CPI call with self implementation - safe
pub fn invoke_with_self_implementation_safe(ctx: Context<SolTransfer3>, amount: u64) -> Result<()> {
pub fn invoke_with_self_implementation_safe(
ctx: Context<SolTransfer3>,
amount: u64,
) -> Result<()> {
ctx.accounts.cpi_call_safe(amount)?;
let _final_data = ctx.accounts.pda_account.data; // [safe_account_accessed]
Ok(())
}

// Pattern 18: CPI call with self implementation - unsafe
pub fn invoke_with_self_implementation_unsafe(ctx: Context<SolTransfer3>, amount: u64) -> Result<()> {
pub fn invoke_with_self_implementation_unsafe(
ctx: Context<SolTransfer3>,
amount: u64,
) -> Result<()> {
ctx.accounts.cpi_call_unsafe(amount)?;
let _final_data = ctx.accounts.pda_account.data; // [unsafe_account_accessed]
Ok(())
}

/// Patten 19: load_mut() then CPI — unsafe.
pub fn test_lazy_account_unsafe(
ctx: Context<LazyAccountCpiAccounts>,
amount: u64,
) -> Result<()> {
let data = ctx.accounts.lazy_acc.load_mut()?; // ref held across CPI
let _ = data.value;
let new_space = ctx.accounts.lazy_acc.to_account_info().data_len() as u64 + amount;
let ix = system_instruction::allocate(&ctx.accounts.lazy_acc.key(), new_space);
let account_infos = vec![
ctx.accounts.lazy_acc.to_account_info(),
ctx.accounts.system_program.to_account_info(),
];
invoke(&ix, &account_infos)?; // [cpi_call]
let _ = data.value; // [unsafe_account_accessed]
Ok(())
}

// Pattern 20: load_mut() then CPI with load_mut() — safe
pub fn test_lazy_account_safe(ctx: Context<LazyAccountCpiAccounts>, amount: u64) -> Result<()> {
let new_space = ctx.accounts.lazy_acc.to_account_info().data_len() as u64 + amount;
let ix = system_instruction::allocate(&ctx.accounts.lazy_acc.key(), new_space);
let account_infos = vec![
ctx.accounts.lazy_acc.to_account_info(),
ctx.accounts.system_program.to_account_info(),
];
invoke(&ix, &account_infos)?;
let data = ctx.accounts.lazy_acc.load_mut()?;
let _ = data.value; // [safe_account_accessed]
Ok(())
}
}
pub fn cpi_call_safe(ctx_a: &mut Context<SolTransfer3>, amount: u64) -> Result<()> {
let from_pubkey = ctx_a.accounts.pda_account.to_account_info();
Expand Down Expand Up @@ -614,6 +652,18 @@ pub struct UserState {
pub data: u64,
}

#[account]
pub struct LazyLoadable {
pub value: u64,
}

#[derive(Accounts)]
pub struct LazyAccountCpiAccounts<'info> {
#[account(mut)]
pub lazy_acc: LazyAccount<'info, LazyLoadable>,
pub system_program: Program<'info, System>,
}

#[account]
pub struct InnerAccount {
pub data: u64,
Expand Down
Loading