Skip to content

Commit 468e404

Browse files
authored
capture option (#17571)
1 parent 07339e5 commit 468e404

File tree

13 files changed

+396
-13
lines changed

13 files changed

+396
-13
lines changed

aptos-move/aptos-release-builder/src/components/feature_flags.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,7 @@ pub enum FeatureFlag {
150150
CalculateTransactionFeeForDistribution,
151151
DistributeTransactionFee,
152152
MonotonicallyIncreasingCounter,
153+
EnableCaptureOption,
153154
EnableTrustedCode,
154155
}
155156

@@ -400,6 +401,7 @@ impl From<FeatureFlag> for AptosFeatureFlag {
400401
FeatureFlag::MonotonicallyIncreasingCounter => {
401402
AptosFeatureFlag::MONOTONICALLY_INCREASING_COUNTER
402403
},
404+
FeatureFlag::EnableCaptureOption => AptosFeatureFlag::ENABLE_CAPTURE_OPTION,
403405
FeatureFlag::EnableTrustedCode => AptosFeatureFlag::ENABLE_TRUSTED_CODE,
404406
}
405407
}
@@ -577,6 +579,7 @@ impl From<AptosFeatureFlag> for FeatureFlag {
577579
AptosFeatureFlag::MONOTONICALLY_INCREASING_COUNTER => {
578580
FeatureFlag::MonotonicallyIncreasingCounter
579581
},
582+
AptosFeatureFlag::ENABLE_CAPTURE_OPTION => FeatureFlag::EnableCaptureOption,
580583
AptosFeatureFlag::ENABLE_TRUSTED_CODE => FeatureFlag::EnableTrustedCode,
581584
}
582585
}

aptos-move/aptos-vm-environment/src/prod_configs.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,8 @@ pub fn aptos_prod_vm_config(
167167
// shallow while the value can be deeply nested, thanks to captured arguments not visible in a
168168
// type. Hence, depth checks have been adjusted to operate on values.
169169
let enable_depth_checks = features.is_enabled(FeatureFlag::ENABLE_FUNCTION_VALUES);
170+
let enable_capture_option = !timed_features.is_enabled(TimedFeatureFlag::DisabledCaptureOption)
171+
|| features.is_enabled(FeatureFlag::ENABLE_CAPTURE_OPTION);
170172

171173
let config = VMConfig {
172174
verifier_config,
@@ -191,6 +193,7 @@ pub fn aptos_prod_vm_config(
191193
enable_depth_checks,
192194
optimize_trusted_code: features.is_trusted_code_enabled(),
193195
paranoid_ref_checks,
196+
enable_capture_option,
194197
};
195198

196199
// Note: if max_value_nest_depth changed, make sure the constant is in-sync. Do not remove this
Lines changed: 311 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,311 @@
1+
// Copyright © Aptos Foundation
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
//! Tests for capturing option type in function values.
5+
6+
use crate::{assert_success, assert_vm_status, tests::common, MoveHarness};
7+
use aptos_framework::BuildOptions;
8+
use aptos_package_builder::PackageBuilder;
9+
use aptos_transaction_simulation::Account;
10+
use aptos_types::{
11+
on_chain_config::FeatureFlag, transaction::TransactionStatus, vm_status::StatusCode,
12+
};
13+
use move_core_types::account_address::AccountAddress;
14+
15+
#[test]
16+
fn test_vm_value_fv_capture_option_1() {
17+
let mut h = MoveHarness::new();
18+
let acc = h.new_account_at(AccountAddress::from_hex_literal("0x99").unwrap());
19+
20+
// test directly capture option type
21+
h.enable_features(vec![], vec![FeatureFlag::ENABLE_CAPTURE_OPTION]);
22+
let result = publish(
23+
&mut h,
24+
&acc,
25+
r#"
26+
module 0x99::m {
27+
use std::option;
28+
29+
struct FunctionStore has key {
30+
f: ||option::Option<u64> has copy+drop+store,
31+
}
32+
33+
public fun id(x: option::Option<u64>): option::Option<u64> {
34+
x
35+
}
36+
37+
fun init_module(account: &signer) {
38+
let v = option::none();
39+
let f: ||option::Option<u64> has copy+drop+store = || id(v);
40+
move_to(account, FunctionStore { f });
41+
}
42+
entry fun entry_func() {
43+
let v = option::none();
44+
let _f: ||option::Option<u64> has copy+drop+store = || id(v);
45+
}
46+
}
47+
"#,
48+
);
49+
// before timed feature flag is enabled
50+
assert_success!(result);
51+
52+
h.new_epoch();
53+
let result = h.run_entry_function(
54+
&acc,
55+
str::parse("0x99::m::entry_func").unwrap(),
56+
vec![],
57+
vec![],
58+
);
59+
assert_vm_status!(result, StatusCode::UNABLE_TO_CAPTURE_OPTION_TYPE);
60+
61+
h.enable_features(vec![FeatureFlag::ENABLE_CAPTURE_OPTION], vec![]);
62+
let result = h.run_entry_function(
63+
&acc,
64+
str::parse("0x99::m::entry_func").unwrap(),
65+
vec![],
66+
vec![],
67+
);
68+
assert_success!(result);
69+
}
70+
71+
#[test]
72+
fn test_vm_value_fv_capture_option_2() {
73+
let mut h = MoveHarness::new();
74+
let acc = h.new_account_at(AccountAddress::from_hex_literal("0x99").unwrap());
75+
76+
// test capture option type in a struct
77+
h.enable_features(vec![], vec![FeatureFlag::ENABLE_CAPTURE_OPTION]);
78+
let result = publish(
79+
&mut h,
80+
&acc,
81+
r#"
82+
module 0x99::m {
83+
use std::option;
84+
85+
struct OptionStore has copy,drop,store {
86+
o: option::Option<u64>,
87+
}
88+
89+
struct FunctionStore has key {
90+
f: ||option::Option<u64> has copy+drop+store,
91+
}
92+
93+
public fun id(x: OptionStore): option::Option<u64> {
94+
x.o
95+
}
96+
97+
entry fun entry_func() {
98+
let v = OptionStore { o: option::none() };
99+
let _f: ||option::Option<u64> has copy+drop+store = || id(v);
100+
}
101+
}
102+
"#,
103+
);
104+
// before timed feature flag is enabled
105+
assert_success!(result);
106+
h.new_epoch();
107+
let result = h.run_entry_function(
108+
&acc,
109+
str::parse("0x99::m::entry_func").unwrap(),
110+
vec![],
111+
vec![],
112+
);
113+
assert_vm_status!(result, StatusCode::UNABLE_TO_CAPTURE_OPTION_TYPE);
114+
115+
h.enable_features(vec![FeatureFlag::ENABLE_CAPTURE_OPTION], vec![]);
116+
let result = h.run_entry_function(
117+
&acc,
118+
str::parse("0x99::m::entry_func").unwrap(),
119+
vec![],
120+
vec![],
121+
);
122+
assert_success!(result);
123+
}
124+
125+
#[test]
126+
fn test_vm_value_fv_capture_option_3() {
127+
let mut h = MoveHarness::new();
128+
let acc = h.new_account_at(AccountAddress::from_hex_literal("0x99").unwrap());
129+
130+
// test capture option type in a vector, even if the vector is empty
131+
h.enable_features(vec![], vec![FeatureFlag::ENABLE_CAPTURE_OPTION]);
132+
let result = publish(
133+
&mut h,
134+
&acc,
135+
r#"
136+
module 0x99::m {
137+
use std::option;
138+
use std::vector;
139+
140+
struct FunctionStore has key {
141+
f: ||option::Option<u64> has copy+drop+store,
142+
}
143+
144+
public fun id(_x: vector<option::Option<u64>>): option::Option<u64> {
145+
option::none()
146+
}
147+
148+
entry fun entry_func() {
149+
let v = vector::empty();
150+
let _f: ||option::Option<u64> has copy+drop+store = || id(v);
151+
}
152+
}
153+
"#,
154+
);
155+
// before timed feature flag is enabled
156+
assert_success!(result);
157+
h.new_epoch();
158+
let result = h.run_entry_function(
159+
&acc,
160+
str::parse("0x99::m::entry_func").unwrap(),
161+
vec![],
162+
vec![],
163+
);
164+
assert_vm_status!(result, StatusCode::UNABLE_TO_CAPTURE_OPTION_TYPE);
165+
166+
h.enable_features(vec![FeatureFlag::ENABLE_CAPTURE_OPTION], vec![]);
167+
let result = h.run_entry_function(
168+
&acc,
169+
str::parse("0x99::m::entry_func").unwrap(),
170+
vec![],
171+
vec![],
172+
);
173+
assert_success!(result);
174+
}
175+
176+
#[test]
177+
fn test_vm_value_fv_capture_option_4() {
178+
let mut h = MoveHarness::new();
179+
let acc = h.new_account_at(AccountAddress::from_hex_literal("0x99").unwrap());
180+
181+
// test capture option type in a struct, parameterized by an option type
182+
h.enable_features(vec![], vec![FeatureFlag::ENABLE_CAPTURE_OPTION]);
183+
let result = publish(
184+
&mut h,
185+
&acc,
186+
r#"
187+
module 0x99::m {
188+
use std::option;
189+
190+
struct Store<T: store+drop+copy> has copy,drop,store {
191+
o: T,
192+
}
193+
194+
struct FunctionStore has key {
195+
f: ||option::Option<u64> has copy+drop+store,
196+
}
197+
198+
public fun id<T: store+drop+copy>(x: Store<T>): T {
199+
x.o
200+
}
201+
202+
fun init_module(account: &signer) {
203+
let v = Store { o: option::none() };
204+
let f: ||option::Option<u64> has copy+drop+store = || id(v);
205+
move_to(account, FunctionStore { f });
206+
}
207+
208+
entry fun entry_func() {
209+
let v = Store { o: option::none() };
210+
let _f: ||option::Option<u64> has copy+drop+store = || id(v);
211+
}
212+
}
213+
"#,
214+
);
215+
// before timed feature flag is enabled
216+
assert_success!(result);
217+
h.new_epoch();
218+
let result = h.run_entry_function(
219+
&acc,
220+
str::parse("0x99::m::entry_func").unwrap(),
221+
vec![],
222+
vec![],
223+
);
224+
assert_vm_status!(result, StatusCode::UNABLE_TO_CAPTURE_OPTION_TYPE);
225+
226+
h.enable_features(vec![FeatureFlag::ENABLE_CAPTURE_OPTION], vec![]);
227+
let result = h.run_entry_function(
228+
&acc,
229+
str::parse("0x99::m::entry_func").unwrap(),
230+
vec![],
231+
vec![],
232+
);
233+
assert_success!(result);
234+
}
235+
236+
#[test]
237+
fn test_vm_value_fv_capture_option_5() {
238+
let mut h = MoveHarness::new();
239+
let acc = h.new_account_at(AccountAddress::from_hex_literal("0x99").unwrap());
240+
241+
// test capture option type in a function type
242+
h.enable_features(vec![], vec![FeatureFlag::ENABLE_CAPTURE_OPTION]);
243+
let result = publish(
244+
&mut h,
245+
&acc,
246+
r#"
247+
module 0x99::m {
248+
use std::option;
249+
250+
251+
struct FunctionStore has key {
252+
f: ||u64 has copy+drop+store,
253+
}
254+
255+
public fun id(f: |option::Option<u64>| u64 has copy+drop+store): u64 {
256+
f(option::some(3))
257+
}
258+
259+
public fun id2(_v: option::Option<u64>): u64 {
260+
3
261+
}
262+
263+
fun init_module(account: &signer) {
264+
let v :|option::Option<u64>|u64 has copy+drop+store = id2;
265+
let f: ||u64 has copy+drop+store = || id(v);
266+
move_to(account, FunctionStore { f });
267+
}
268+
269+
entry fun entry_func() {
270+
let v :|option::Option<u64>|u64 has copy+drop+store = id2;
271+
let _f: ||u64 has copy+drop+store = || id(v);
272+
}
273+
}
274+
"#,
275+
);
276+
// before timed feature flag is enabled
277+
assert_success!(result);
278+
h.new_epoch();
279+
let result = h.run_entry_function(
280+
&acc,
281+
str::parse("0x99::m::entry_func").unwrap(),
282+
vec![],
283+
vec![],
284+
);
285+
// since arguments of function type do not show in the layout, execution should succeed
286+
assert_success!(result);
287+
288+
h.enable_features(vec![FeatureFlag::ENABLE_CAPTURE_OPTION], vec![]);
289+
let result = h.run_entry_function(
290+
&acc,
291+
str::parse("0x99::m::entry_func").unwrap(),
292+
vec![],
293+
vec![],
294+
);
295+
assert_success!(result);
296+
}
297+
298+
fn publish(h: &mut MoveHarness, account: &Account, source: &str) -> TransactionStatus {
299+
let mut builder = PackageBuilder::new("Package");
300+
builder.add_source("m.move", source);
301+
builder.add_local_dep(
302+
"MoveStdlib",
303+
&common::framework_dir_path("move-stdlib").to_string_lossy(),
304+
);
305+
let path = builder.write_to_temp().unwrap();
306+
h.publish_package_with_options(
307+
account,
308+
path.path(),
309+
BuildOptions::move_2().set_latest_language(),
310+
)
311+
}

aptos-move/e2e-move-tests/src/tests/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ mod error_map;
2424
mod events;
2525
mod fee_payer;
2626
mod friends;
27+
mod function_value_capture_option;
2728
mod function_value_depth;
2829
mod function_values;
2930
mod fungible_asset;

third_party/move/move-core/types/src/vm_status.rs

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -855,12 +855,16 @@ pub enum StatusCode {
855855
VALUE_DESERIALIZATION_ERROR = 3023,
856856
CODE_DESERIALIZATION_ERROR = 3024,
857857
INVALID_FLAG_BITS = 3025,
858+
// Returned when a function value is trying to capture an option. This is not allowed
859+
// until the feature flag ENABLE_CAPTURE_OPTION is on.
860+
UNABLE_TO_CAPTURE_OPTION_TYPE = 3026,
861+
858862
// Reserved error code for future use
859-
RESERVED_DESERIALIZAION_ERROR_1 = 3026,
860-
RESERVED_DESERIALIZAION_ERROR_2 = 3027,
861-
RESERVED_DESERIALIZAION_ERROR_3 = 3028,
862-
RESERVED_DESERIALIZAION_ERROR_4 = 3029,
863-
RESERVED_DESERIALIZAION_ERROR_5 = 3030,
863+
RESERVED_DESERIALIZAION_ERROR_1 = 3027,
864+
RESERVED_DESERIALIZAION_ERROR_2 = 3028,
865+
RESERVED_DESERIALIZAION_ERROR_3 = 3029,
866+
RESERVED_DESERIALIZAION_ERROR_4 = 3030,
867+
RESERVED_DESERIALIZAION_ERROR_5 = 3031,
864868

865869
// Errors that can arise at runtime
866870
// Runtime Errors: 4000-4999

third_party/move/move-vm/runtime/src/config.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ pub struct VMConfig {
4242
/// When this flag is set to true, Move VM will perform additional checks to ensure that
4343
/// reference safety is maintained during execution.
4444
pub paranoid_ref_checks: bool,
45+
pub enable_capture_option: bool,
4546
}
4647

4748
impl Default for VMConfig {
@@ -64,6 +65,7 @@ impl Default for VMConfig {
6465
enable_depth_checks: true,
6566
optimize_trusted_code: false,
6667
paranoid_ref_checks: false,
68+
enable_capture_option: false,
6769
}
6870
}
6971
}

0 commit comments

Comments
 (0)