Skip to content

Commit 7cab049

Browse files
Evaluate substring operands once (#577)
Make substring calls evaluate their receiver and arguments once in both output models. Duplicating an operand during inline substitution can change both the selected bytes and caller state; this handles the substring case of #575. The side-effect cases in [tests/unit/string.cpp](tests/unit/string.cpp) compare substring bytes and final counters with C++ for position, count and receiver expressions. - Capture the receiver and arguments in a tuple match, which also keeps temporary receivers alive until the substring has been copied. - Borrow the source after evaluating the arguments, so argument expressions can modify it before the slice is read. - Materialize temporary receivers before taking their address in the unsafe converter. The result remains an independent, null-terminated copy of the selected slice. No whole-source clone is introduced; reference-counted receivers must now be addressable. Existing range clipping and overflow handling are unchanged. ## Testing - `lit` on `tests/unit/string.cpp` and `tests/unit/string_substr.cpp` (Linux, Release build): pass. Both fail without this change. - Full suite (unit, ub and multi-file) on Linux at ce0915c: 1142 pass / 14 xfail / 0 fail before; 1144 pass / 14 xfail / 0 fail with this change. The two new passes are the tests above. On macOS, one expected output in `string_substr.cpp` hard-codes the libstdc++ `std::string` size; a fix for that follows. - `clang-format --dry-run --Werror` on the changed C++ files, `rustfmt --check` on the changed rule files, and `cargo clippy --all-targets --all-features -- -Dwarnings` on `rules/`: clean. [tests/unit/string_substr.cpp](tests/unit/string_substr.cpp) checks receiver-before-argument ordering, source mutations in either argument, independent result storage, and temporary, pointer, field and container-element receivers. The generated Rust expectations cover both models. Related work: #576 changes vector proxy handling in the converter and binds repeated operands in vector rules; this change handles substring expansion. [Case and evidence](<https://app.perfloop.ai/t/oss/case_gs6rt5cvpd>) --- Generated by [Perfloop](https://app.perfloop.ai). Human sponsor: [Tomás Senart](https://github.com/tsenart). --------- Signed-off-by: Perfloop Agent <agent@perfloop.ai> Signed-off-by: Tomás Senart <ts@perfloop.ai>
1 parent eeabae7 commit 7cab049

9 files changed

Lines changed: 824 additions & 124 deletions

File tree

‎cpp2rust/converter/converter.cpp‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -231,7 +231,12 @@ std::string Converter::ConvertFreshObject(clang::Expr *expr) {
231231

232232
std::string Converter::ConvertPointerReceiver(clang::Expr *arg,
233233
std::string_view param_type) {
234-
return std::format("({} as {})", ConvertFreshObject(arg), param_type);
234+
auto *unwrapped = clang::IgnoreExprNodes(
235+
arg, clang::IgnoreParensSingleStep, clang::IgnoreImplicitCastsSingleStep);
236+
auto receiver = clang::isa<clang::MaterializeTemporaryExpr>(unwrapped)
237+
? ConvertTemporaryPointer(arg, std::nullopt)
238+
: ConvertFreshObject(arg);
239+
return std::format("({} as {})", receiver, param_type);
235240
}
236241

237242
std::string Converter::ConvertTemporaryPointer(

‎rules/string/tgt_refcount.rs‎

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,16 @@ fn t7() -> i8 {
2525
0_i8
2626
}
2727

28-
fn f1(a0: Vec<i8>, a1: usize, a2: usize) -> Vec<i8> {
29-
let mut __tmp1 =
30-
a0[(a1) as usize..::std::cmp::min((a1 + a2) as usize, a0.len().saturating_sub(1))].to_vec();
31-
__tmp1.push(0);
32-
__tmp1
28+
// Keep temporary receivers alive, but borrow only after evaluating the arguments.
29+
fn f1(a0: Ptr<Vec<i8>>, a1: usize, a2: usize) -> Vec<i8> {
30+
match (a0, a1, a2) {
31+
(__source, __pos, __count) => __source.with(|__s: &Vec<i8>| {
32+
let mut __tmp1 =
33+
__s[__pos..::std::cmp::min(__pos + __count, __s.len().saturating_sub(1))].to_vec();
34+
__tmp1.push(0);
35+
__tmp1
36+
}),
37+
}
3338
}
3439

3540
fn f3(a0: Vec<i8>, a1: Ptr<i8>) -> Vec<i8> {

‎rules/string/tgt_unsafe.rs‎

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,16 @@ fn t7() -> libc::c_char {
2727
0 as libc::c_char
2828
}
2929

30-
unsafe fn f1(a0: Vec<libc::c_char>, a1: usize, a2: usize) -> Vec<libc::c_char> {
31-
let mut __tmp1 = a0[(a1) as usize..::std::cmp::min((a1 + a2) as usize, a0.len() - 1)].to_vec();
32-
__tmp1.push(0);
33-
__tmp1
30+
// Keep temporary receivers alive, but borrow only after evaluating the arguments.
31+
unsafe fn f1(a0: *const Vec<libc::c_char>, a1: usize, a2: usize) -> Vec<libc::c_char> {
32+
match (a0, a1, a2) {
33+
(__source, __pos, __count) => {
34+
let __s = &*__source;
35+
let mut __tmp1 = __s[__pos..::std::cmp::min(__pos + __count, __s.len() - 1)].to_vec();
36+
__tmp1.push(0);
37+
__tmp1
38+
}
39+
}
3440
}
3541
unsafe fn f2(a0: Vec<libc::c_char>) -> usize {
3642
(a0.len() - 1)

‎tests/unit/out/refcount/string.rs‎

Lines changed: 125 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,14 @@ use std::io::prelude::*;
66
use std::io::{Read, Seek, Write};
77
use std::os::fd::AsFd;
88
use std::rc::{Rc, Weak};
9+
pub fn counted_string_0(calls: Ptr<i32>) -> Vec<i8> {
10+
calls.with_mut(|__v| __v.prefix_inc());
11+
return {
12+
let mut __bytes = Ptr::<i8>::from_string_literal(b"hello").to_c_bytes();
13+
__bytes.push(0);
14+
__bytes
15+
};
16+
}
917
pub fn main() {
1018
__cpp2rust_init_globals();
1119
std::process::exit(main_0());
@@ -73,15 +81,13 @@ fn main_0() -> i32 {
7381
);
7482
i.prefix_inc();
7583
}
76-
let s3: Value<Vec<i8>> = Rc::new(RefCell::new({
77-
let mut __tmp1 = (*s2.borrow())[(2_usize) as usize
78-
..::std::cmp::min(
79-
(2_usize + 5_usize) as usize,
80-
(*s2.borrow()).len().saturating_sub(1),
81-
)]
82-
.to_vec();
83-
__tmp1.push(0);
84-
__tmp1
84+
let s3: Value<Vec<i8>> = Rc::new(RefCell::new(match (s2.as_pointer(), 2_usize, 5_usize) {
85+
(__source, __pos, __count) => __source.with(|__s: &Vec<i8>| {
86+
let mut __tmp1 =
87+
__s[__pos..::std::cmp::min(__pos + __count, __s.len().saturating_sub(1))].to_vec();
88+
__tmp1.push(0);
89+
__tmp1
90+
}),
8591
}));
8692
assert!((((*s3.borrow()).len() - 1) == 5_usize));
8793
assert!((((*s3.borrow()).len() - 1) == ((*s3.borrow()).len() - 1)));
@@ -93,23 +99,27 @@ fn main_0() -> i32 {
9399
);
94100
i.prefix_inc();
95101
}
96-
let s4: Value<Vec<i8>> = Rc::new(RefCell::new({
97-
let mut __tmp1 = (*s1.borrow())[(1_usize) as usize
98-
..::std::cmp::min(
99-
(1_usize
100-
+ Ptr::<i8>::from_string_literal(b"l").with_c_str(|__lookup| {
101-
(*s1.borrow())
102-
.iter()
103-
.take((*s1.borrow()).len().saturating_sub(1))
104-
.rposition(|&x| __lookup.contains(&x))
105-
.unwrap_or(usize::MAX)
106-
})) as usize,
107-
(*s1.borrow()).len().saturating_sub(1),
108-
)]
109-
.to_vec();
110-
__tmp1.push(0);
111-
__tmp1
112-
}));
102+
let s4: Value<Vec<i8>> = Rc::new(RefCell::new(
103+
match (
104+
s1.as_pointer(),
105+
1_usize,
106+
Ptr::<i8>::from_string_literal(b"l").with_c_str(|__lookup| {
107+
(*s1.borrow())
108+
.iter()
109+
.take((*s1.borrow()).len().saturating_sub(1))
110+
.rposition(|&x| __lookup.contains(&x))
111+
.unwrap_or(usize::MAX)
112+
}),
113+
) {
114+
(__source, __pos, __count) => __source.with(|__s: &Vec<i8>| {
115+
let mut __tmp1 = __s
116+
[__pos..::std::cmp::min(__pos + __count, __s.len().saturating_sub(1))]
117+
.to_vec();
118+
__tmp1.push(0);
119+
__tmp1
120+
}),
121+
},
122+
));
113123
assert!((((*s4.borrow()).len() - 1) == 3_usize));
114124
assert!((((*s4.borrow()).len() - 1) == ((*s4.borrow()).len() - 1)));
115125
let mut p4: Ptr<i8> = s4.decay();
@@ -201,55 +211,55 @@ fn main_0() -> i32 {
201211
assert!(((string[1_usize] as i32) == (('a' as i8) as i32)));
202212
assert!(((string[2_usize] as i32) == (('r' as i8) as i32)));
203213
assert!(((string[3_usize] as i32) == 0));
204-
let mut result: Vec<i8> = {
214+
let result: Value<Vec<i8>> = Rc::new(RefCell::new({
205215
let mut r = (string).clone();
206216
r.pop();
207217
Ptr::<i8>::from_string_literal(b" foo").with_c_str(|__s| r.extend_from_slice(__s));
208218
r.push(0);
209219
r
210-
};
211-
assert!(((result.len() - 1) == 8_usize));
212-
assert!(((result[0_usize] as i32) == (('b' as i8) as i32)));
213-
assert!(((result[1_usize] as i32) == (('a' as i8) as i32)));
214-
assert!(((result[2_usize] as i32) == (('r' as i8) as i32)));
215-
assert!(((result[3_usize] as i32) == 0));
216-
assert!(((result[4_usize] as i32) == ((' ' as i8) as i32)));
217-
assert!(((result[5_usize] as i32) == (('f' as i8) as i32)));
218-
assert!(((result[6_usize] as i32) == (('o' as i8) as i32)));
219-
assert!(((result[7_usize] as i32) == (('o' as i8) as i32)));
220-
let mut substr_0: Vec<i8> = {
221-
let mut __tmp1 = result[(5_usize) as usize
222-
..::std::cmp::min((5_usize + 3_usize) as usize, result.len().saturating_sub(1))]
223-
.to_vec();
224-
__tmp1.push(0);
225-
__tmp1
220+
}));
221+
assert!((((*result.borrow()).len() - 1) == 8_usize));
222+
assert!((({ (*result.borrow())[0_usize] } as i32) == (('b' as i8) as i32)));
223+
assert!((({ (*result.borrow())[1_usize] } as i32) == (('a' as i8) as i32)));
224+
assert!((({ (*result.borrow())[2_usize] } as i32) == (('r' as i8) as i32)));
225+
assert!((({ (*result.borrow())[3_usize] } as i32) == 0));
226+
assert!((({ (*result.borrow())[4_usize] } as i32) == ((' ' as i8) as i32)));
227+
assert!((({ (*result.borrow())[5_usize] } as i32) == (('f' as i8) as i32)));
228+
assert!((({ (*result.borrow())[6_usize] } as i32) == (('o' as i8) as i32)));
229+
assert!((({ (*result.borrow())[7_usize] } as i32) == (('o' as i8) as i32)));
230+
let mut substr_0: Vec<i8> = match (result.as_pointer(), 5_usize, 3_usize) {
231+
(__source, __pos, __count) => __source.with(|__s: &Vec<i8>| {
232+
let mut __tmp1 =
233+
__s[__pos..::std::cmp::min(__pos + __count, __s.len().saturating_sub(1))].to_vec();
234+
__tmp1.push(0);
235+
__tmp1
236+
}),
226237
};
227238
assert!(((substr_0.len() - 1) == 3_usize));
228239
assert!(((substr_0[0_usize] as i32) == (('f' as i8) as i32)));
229240
assert!(((substr_0[1_usize] as i32) == (('o' as i8) as i32)));
230241
assert!(((substr_0[2_usize] as i32) == (('o' as i8) as i32)));
231-
let mut substr_1: Vec<i8> = {
232-
let mut __tmp1 = result[(0_usize) as usize
233-
..::std::cmp::min((0_usize + 5_usize) as usize, result.len().saturating_sub(1))]
234-
.to_vec();
235-
__tmp1.push(0);
236-
__tmp1
242+
let mut substr_1: Vec<i8> = match (result.as_pointer(), 0_usize, 5_usize) {
243+
(__source, __pos, __count) => __source.with(|__s: &Vec<i8>| {
244+
let mut __tmp1 =
245+
__s[__pos..::std::cmp::min(__pos + __count, __s.len().saturating_sub(1))].to_vec();
246+
__tmp1.push(0);
247+
__tmp1
248+
}),
237249
};
238250
assert!(((substr_1.len() - 1) == 5_usize));
239251
assert!(((substr_1[0_usize] as i32) == (('b' as i8) as i32)));
240252
assert!(((substr_1[1_usize] as i32) == (('a' as i8) as i32)));
241253
assert!(((substr_1[2_usize] as i32) == (('r' as i8) as i32)));
242254
assert!(((substr_1[3_usize] as i32) == 0));
243255
assert!(((substr_1[4_usize] as i32) == ((' ' as i8) as i32)));
244-
let mut substr_2: Vec<i8> = {
245-
let mut __tmp1 = result[(0_usize) as usize
246-
..::std::cmp::min(
247-
(0_usize + 15_usize) as usize,
248-
result.len().saturating_sub(1),
249-
)]
250-
.to_vec();
251-
__tmp1.push(0);
252-
__tmp1
256+
let mut substr_2: Vec<i8> = match (result.as_pointer(), 0_usize, 15_usize) {
257+
(__source, __pos, __count) => __source.with(|__s: &Vec<i8>| {
258+
let mut __tmp1 =
259+
__s[__pos..::std::cmp::min(__pos + __count, __s.len().saturating_sub(1))].to_vec();
260+
__tmp1.push(0);
261+
__tmp1
262+
}),
253263
};
254264
assert!(((substr_2.len() - 1) == 8_usize));
255265
assert!(((substr_2[0_usize] as i32) == (('b' as i8) as i32)));
@@ -261,39 +271,39 @@ fn main_0() -> i32 {
261271
assert!(((substr_2[6_usize] as i32) == (('o' as i8) as i32)));
262272
assert!(((substr_2[7_usize] as i32) == (('o' as i8) as i32)));
263273
let mut pos: usize = Ptr::<i8>::from_string_literal(b"b").with_c_str(|__lookup| {
264-
result
274+
(*result.borrow())
265275
.iter()
266-
.take(result.len().saturating_sub(1))
276+
.take((*result.borrow()).len().saturating_sub(1))
267277
.rposition(|&x| __lookup.contains(&x))
268278
.unwrap_or(usize::MAX)
269279
});
270280
assert!((pos == 0_usize));
271281
pos = Ptr::<i8>::from_string_literal(b"f").with_c_str(|__lookup| {
272-
result
282+
(*result.borrow())
273283
.iter()
274-
.take(result.len().saturating_sub(1))
284+
.take((*result.borrow()).len().saturating_sub(1))
275285
.rposition(|&x| __lookup.contains(&x))
276286
.unwrap_or(usize::MAX)
277287
});
278288
assert!((pos == 5_usize));
279289
pos = Ptr::<i8>::from_string_literal(b"o").with_c_str(|__lookup| {
280-
result
290+
(*result.borrow())
281291
.iter()
282-
.take(result.len().saturating_sub(1))
292+
.take((*result.borrow()).len().saturating_sub(1))
283293
.rposition(|&x| __lookup.contains(&x))
284294
.unwrap_or(usize::MAX)
285295
});
286296
assert!((pos == 7_usize));
287297
pos = Ptr::<i8>::from_string_literal(b"x").with_c_str(|__lookup| {
288-
result
298+
(*result.borrow())
289299
.iter()
290-
.take(result.len().saturating_sub(1))
300+
.take((*result.borrow()).len().saturating_sub(1))
291301
.rposition(|&x| __lookup.contains(&x))
292302
.unwrap_or(usize::MAX)
293303
});
294304
assert!((pos == ((-1_i64 as u64) as usize)));
295305
pos = {
296-
let __hay = &result[..result.len().saturating_sub(1)];
306+
let __hay = &(*result.borrow())[..(*result.borrow()).len().saturating_sub(1)];
297307
Ptr::<i8>::from_string_literal(b"f").with_c_str(|__needle| {
298308
(0_usize..=__hay.len().saturating_sub(__needle.len()))
299309
.find(|&__i| __hay[__i..].starts_with(__needle))
@@ -302,7 +312,7 @@ fn main_0() -> i32 {
302312
};
303313
assert!((pos == 5_usize));
304314
pos = {
305-
let __hay = &result[..result.len().saturating_sub(1)];
315+
let __hay = &(*result.borrow())[..(*result.borrow()).len().saturating_sub(1)];
306316
Ptr::<i8>::from_string_literal(b"oo").with_c_str(|__needle| {
307317
(0_usize..=__hay.len().saturating_sub(__needle.len()))
308318
.find(|&__i| __hay[__i..].starts_with(__needle))
@@ -311,7 +321,7 @@ fn main_0() -> i32 {
311321
};
312322
assert!((pos == 6_usize));
313323
pos = {
314-
let __hay = &result[..result.len().saturating_sub(1)];
324+
let __hay = &(*result.borrow())[..(*result.borrow()).len().saturating_sub(1)];
315325
Ptr::<i8>::from_string_literal(b"x").with_c_str(|__needle| {
316326
(0_usize..=__hay.len().saturating_sub(__needle.len()))
317327
.find(|&__i| __hay[__i..].starts_with(__needle))
@@ -334,6 +344,52 @@ fn main_0() -> i32 {
334344
assert!((diff < 0_i64));
335345
diff += 6_i64;
336346
assert!((diff == 3_i64));
347+
let mut substr_pos: usize = 1_usize;
348+
let once: Value<Vec<i8>> = Rc::new(RefCell::new(
349+
match (s1.as_pointer(), substr_pos.postfix_inc(), 2_usize) {
350+
(__source, __pos, __count) => __source.with(|__s: &Vec<i8>| {
351+
let mut __tmp1 = __s
352+
[__pos..::std::cmp::min(__pos + __count, __s.len().saturating_sub(1))]
353+
.to_vec();
354+
__tmp1.push(0);
355+
__tmp1
356+
}),
357+
},
358+
));
359+
println!("position: {} {}", once.decay(), substr_pos);
360+
let mut substr_count: usize = 2_usize;
361+
once.as_pointer().write(
362+
match (s1.as_pointer(), 1_usize, substr_count.postfix_inc()) {
363+
(__source, __pos, __count) => __source.with(|__s: &Vec<i8>| {
364+
let mut __tmp1 = __s
365+
[__pos..::std::cmp::min(__pos + __count, __s.len().saturating_sub(1))]
366+
.to_vec();
367+
__tmp1.push(0);
368+
__tmp1
369+
}),
370+
},
371+
);
372+
println!("count: {} {}", once.decay(), substr_count);
373+
let receiver_calls: Value<i32> = Rc::new(RefCell::new(0));
374+
once.as_pointer().write(
375+
match (
376+
Rc::new(RefCell::new(
377+
({ counted_string_0(receiver_calls.as_pointer()) }),
378+
))
379+
.as_pointer(),
380+
1_usize,
381+
2_usize,
382+
) {
383+
(__source, __pos, __count) => __source.with(|__s: &Vec<i8>| {
384+
let mut __tmp1 = __s
385+
[__pos..::std::cmp::min(__pos + __count, __s.len().saturating_sub(1))]
386+
.to_vec();
387+
__tmp1.push(0);
388+
__tmp1
389+
}),
390+
},
391+
);
392+
println!("receiver: {} {}", once.decay(), (*receiver_calls.borrow()));
337393
let mut t0: usize = ((*s1.borrow()).len() - 1);
338394
let mut t1: usize = (t0).wrapping_add(((p1.read()) as usize));
339395
let mut t2: usize = (t1).wrapping_add(((*s2.borrow()).len() - 1));

0 commit comments

Comments
 (0)