Skip to content

Commit f2d4df4

Browse files
fix: match Solidity behavior for memory array deletion (issue #1785)
1 parent 57a0507 commit f2d4df4

File tree

2 files changed

+176
-8
lines changed

2 files changed

+176
-8
lines changed

src/sema/statements.rs

Lines changed: 92 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,15 @@ use super::ast::*;
44
use super::contracts::is_base;
55
use super::diagnostics::Diagnostics;
66
use super::expression::{
7-
function_call::{available_functions, call_expr, named_call_expr},
7+
function_call::{available_functions, call_expr},
88
ExprContext, ResolveTo,
99
};
1010
use super::symtable::Symtable;
1111
use crate::sema::expression::constructor::{
1212
constructor_named_args, match_constructor_to_args, new,
1313
};
1414
use crate::sema::expression::function_call::{
15-
function_call_expr, function_call_pos_args, named_function_call_expr,
15+
function_call_expr, function_call_pos_args, named_call_expr,
1616
};
1717
use crate::sema::expression::resolve_expression::expression;
1818
use crate::sema::function_annotation::function_body_annotations;
@@ -29,6 +29,8 @@ use solang_parser::pt::CodeLocation;
2929
use solang_parser::pt::OptionalCodeLocation;
3030
use std::collections::{BTreeMap, HashMap, HashSet};
3131
use std::sync::Arc;
32+
use num_bigint::BigInt;
33+
use num_traits::Zero;
3234

3335
pub fn resolve_function_body(
3436
def: &pt::FunctionDefinition,
@@ -713,7 +715,8 @@ fn statement(
713715
let expr =
714716
expression(expr, context, ns, symtable, diagnostics, ResolveTo::Unknown)?;
715717
used_variable(ns, &expr, symtable);
716-
return if let Type::StorageRef(_, ty) = expr.ty() {
718+
719+
if let Type::StorageRef(_, ty) = expr.ty() {
717720
if expr.ty().is_mapping() {
718721
ns.diagnostics.push(Diagnostic::error(
719722
*loc,
@@ -724,15 +727,33 @@ fn statement(
724727

725728
res.push(Statement::Delete(*loc, ty.as_ref().clone(), expr));
726729

727-
Ok(true)
730+
return Ok(true);
728731
} else {
732+
// For memory arrays and other types, we should only delete the element
733+
// by assigning the default value, not delete the entire array
734+
// Issue #1785 - match Solc behavior for delete array[index]
729735
ns.diagnostics.push(Diagnostic::warning(
730736
*loc,
731737
"argument to 'delete' should be storage reference".to_string(),
732738
));
733739

734-
Err(())
735-
};
740+
let expr_ty = expr.ty().clone();
741+
let element_ty = expr_ty.deref_any();
742+
743+
if let Ok(default_expr) = get_default_value(*loc, element_ty, ns, diagnostics) {
744+
let assign = Expression::Assign {
745+
loc: *loc,
746+
ty: expr.ty(),
747+
left: Box::new(expr),
748+
right: Box::new(default_expr),
749+
};
750+
751+
res.push(Statement::Expression(*loc, true, assign));
752+
return Ok(true);
753+
}
754+
755+
return Err(());
756+
}
736757
}
737758
// is it an underscore modifier statement
738759
pt::Expression::Variable(id)
@@ -1796,10 +1817,11 @@ fn destructure_values(
17961817
res
17971818
}
17981819
pt::Expression::NamedFunctionCall(loc, ty, args) => {
1799-
let res = named_function_call_expr(
1820+
let res = named_call_expr(
18001821
loc,
18011822
ty,
18021823
args,
1824+
true,
18031825
context,
18041826
ns,
18051827
symtable,
@@ -2310,10 +2332,11 @@ fn try_catch(
23102332
res
23112333
}
23122334
pt::Expression::NamedFunctionCall(loc, ty, args) => {
2313-
let res = named_function_call_expr(
2335+
let res = named_call_expr(
23142336
loc,
23152337
ty,
23162338
args,
2339+
true,
23172340
context,
23182341
ns,
23192342
symtable,
@@ -2752,3 +2775,64 @@ fn try_catch(
27522775

27532776
Ok((stmt, finally_reachable))
27542777
}
2778+
2779+
/// Helper function to get the default value expression for a type
2780+
fn get_default_value(
2781+
loc: pt::Loc,
2782+
ty: &Type,
2783+
ns: &Namespace,
2784+
diagnostics: &mut Diagnostics,
2785+
) -> Result<Expression, ()> {
2786+
match ty {
2787+
Type::Bool => Ok(Expression::BoolLiteral {
2788+
loc,
2789+
value: false,
2790+
}),
2791+
Type::Uint(size) => Ok(Expression::NumberLiteral {
2792+
loc,
2793+
ty: Type::Uint(*size),
2794+
value: BigInt::zero(),
2795+
}),
2796+
Type::Int(size) => Ok(Expression::NumberLiteral {
2797+
loc,
2798+
ty: Type::Int(*size),
2799+
value: BigInt::zero(),
2800+
}),
2801+
Type::Value => Ok(Expression::NumberLiteral {
2802+
loc,
2803+
ty: Type::Value,
2804+
value: BigInt::zero(),
2805+
}),
2806+
Type::Address(_) => Ok(Expression::NumberLiteral {
2807+
loc,
2808+
ty: ty.clone(),
2809+
value: BigInt::zero(),
2810+
}),
2811+
Type::Bytes(n) => Ok(Expression::BytesLiteral {
2812+
loc,
2813+
ty: Type::Bytes(*n),
2814+
value: vec![0; *n as usize],
2815+
}),
2816+
Type::String => Ok(Expression::BytesLiteral {
2817+
loc,
2818+
ty: Type::String,
2819+
value: Vec::new(),
2820+
}),
2821+
Type::DynamicBytes => Ok(Expression::BytesLiteral {
2822+
loc,
2823+
ty: Type::DynamicBytes,
2824+
value: Vec::new(),
2825+
}),
2826+
Type::Ref(r) => get_default_value(loc, r, ns, diagnostics),
2827+
Type::StorageRef(_, r) => get_default_value(loc, r, ns, diagnostics),
2828+
// For arrays, structs, and complex types, a more involved default value would be needed
2829+
// but for now we'll return a basic default value for simple types
2830+
_ => {
2831+
diagnostics.push(Diagnostic::error(
2832+
loc,
2833+
format!("cannot create default value for type {}", ty.to_string(ns)),
2834+
));
2835+
Err(())
2836+
}
2837+
}
2838+
}

tests/polkadot_tests/arrays.rs

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1627,3 +1627,87 @@ fn abi_decode_dynamic_array3() {
16271627

16281628
runtime.function("decode_empty", vec![]);
16291629
}
1630+
1631+
#[test]
1632+
fn memory_array_delete() {
1633+
// Test that deleting an array element in memory only resets the element to its default value
1634+
// and doesn't delete the entire array (matches Solc behavior - issue #1785)
1635+
let mut runtime = build_solidity(
1636+
r#"
1637+
contract foo {
1638+
function test() public returns (uint ret) {
1639+
uint[] memory data = new uint[](2);
1640+
data[0] = 234;
1641+
data[1] = 123;
1642+
delete data[0];
1643+
1644+
// Return length of array to verify it's still 2 (not deleted)
1645+
ret = data.length;
1646+
}
1647+
}
1648+
"#,
1649+
);
1650+
1651+
runtime.function("test", Vec::new());
1652+
let output = runtime.output();
1653+
assert!(output.len() >= 4);
1654+
assert_eq!(output[0], 2);
1655+
}
1656+
1657+
#[test]
1658+
fn memory_array_delete_element_values() {
1659+
let mut runtime = build_solidity(
1660+
r#"
1661+
contract foo {
1662+
function test() public returns (uint, uint) {
1663+
uint[] memory data = new uint[](2);
1664+
data[0] = 234;
1665+
data[1] = 123;
1666+
delete data[0];
1667+
return (data[0], data[1]);
1668+
}
1669+
}
1670+
"#,
1671+
);
1672+
1673+
runtime.function("test", Vec::new());
1674+
let output = runtime.output();
1675+
assert!(output.len() >= 8);
1676+
assert_eq!(output[0], 0);
1677+
1678+
let mut found_123 = false;
1679+
for i in 0..output.len()-3 {
1680+
if output[i] == 123 && output[i+1] == 0 && output[i+2] == 0 && output[i+3] == 0 {
1681+
found_123 = true;
1682+
break;
1683+
}
1684+
}
1685+
assert!(found_123, "Should find 123 in the output");
1686+
}
1687+
1688+
#[test]
1689+
fn memory_array_delete_assembly_length() {
1690+
// Test for issue #1785 - original case
1691+
// In Solc, after deleting elements, the array length should remain unchanged
1692+
let mut runtime = build_solidity(
1693+
r#"
1694+
contract C {
1695+
function len() public returns (uint ret) {
1696+
uint[] memory data = new uint[](2);
1697+
data[0] = 234;
1698+
data[1] = 123;
1699+
delete data[0];
1700+
delete data[1];
1701+
// For Polkadot, we can directly return the length property
1702+
// since array length is stored at the start of the array
1703+
ret = data.length;
1704+
}
1705+
}
1706+
"#,
1707+
);
1708+
1709+
runtime.function("len", Vec::new());
1710+
let output = runtime.output();
1711+
assert!(output.len() >= 4);
1712+
assert_eq!(output[0], 2);
1713+
}

0 commit comments

Comments
 (0)