1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
use super::super::super::*;
use crate::instruction::OpCode;
#[test]
fn high_level_pick_of_literal_skips_temp() {
// Script: PUSH1, PUSH2, PUSH1 (index), PICK, RET
// PICK duplicates a simple literal (`1`), so — like DUP/OVER/TUCK —
// it skips the temp materialization and just copies the value. No
// `// pick stack[N]` comment is emitted (parity with the JS port's
// `materialiseStackTopForDup`).
let script = [
OpCode::Push1.byte(),
OpCode::Push2.byte(),
OpCode::Push1.byte(),
OpCode::Pick.byte(),
OpCode::Ret.byte(),
];
let nef_bytes = build_nef(&script);
let decompilation = Decompiler::new()
.decompile_bytes(&nef_bytes)
.expect("decompile succeeds");
let high_level = decompilation
.high_level
.as_deref()
.expect("high-level output");
assert!(
!high_level.contains("pick stack["),
"literal PICK should skip the temp/comment: {high_level}"
);
assert!(
!high_level.contains("insufficient values on stack for PICK"),
"literal PICK must not underflow: {high_level}"
);
}
#[test]
fn high_level_pick_of_side_effecting_value_materializes_temp() {
// Script: SYSCALL(System.Runtime.GetTime) ; PUSH0 (index) ; PICK ;
// ADD ; RET
// PICK duplicates the syscall result (a side-effecting expression).
// It must be hoisted into a temp so the call is evaluated once and
// referenced twice — not string-copied (which would emit two
// syscalls).
let script = [
OpCode::Syscall.byte(),
0xB7,
0xC3,
0x88,
0x03, // SYSCALL System.Runtime.GetTime
OpCode::Push0.byte(), // PUSH0 (index)
OpCode::Pick.byte(), // PICK
OpCode::Add.byte(), // ADD
OpCode::Ret.byte(), // RET
];
let nef_bytes = build_nef(&script);
let decompilation = Decompiler::new()
.decompile_bytes(&nef_bytes)
.expect("decompile succeeds");
let high_level = decompilation
.high_level
.as_deref()
.expect("high-level output");
let syscall_occurrences = high_level.matches("System.Runtime.GetTime").count();
assert_eq!(
syscall_occurrences, 1,
"PICK of a syscall result must evaluate it once (materialized temp): {high_level}"
);
}
#[test]
fn high_level_lifts_xdrop_with_literal_index() {
// Script: PUSH1, PUSH2, PUSH3, PUSH1 (index), XDROP, RET
// XDROP uses the literal index (1) to remove the second item from the
// top. The lifted statement keeps the post-XDROP stack right (PUSH3
// bubbles to the top) — the informational `// xdrop stack[N]`
// comment that used to surface here was just noise duplicating
// what the surrounding statements already convey, and `strip_stack_comments`
// now drops it for parity with the JS port.
let script = [
OpCode::Push1.byte(),
OpCode::Push2.byte(),
OpCode::Push3.byte(),
OpCode::Push1.byte(),
OpCode::Xdrop.byte(),
OpCode::Ret.byte(),
];
let nef_bytes = build_nef(&script);
let decompilation = Decompiler::new()
.decompile_bytes(&nef_bytes)
.expect("decompile succeeds");
let high_level = decompilation
.high_level
.as_deref()
.expect("high-level output");
assert!(
high_level.contains("return t2;") || high_level.contains("return 3;"),
"XDROP should preserve the top value (PUSH3): {high_level}"
);
assert!(
!high_level.contains("// xdrop stack"),
"informational xdrop comment should be stripped: {high_level}"
);
}
#[test]
fn high_level_pick_preserves_packed_shape_for_unpack_reverse4() {
// Script:
// INITSLOT 1,0
// PUSH1; PUSH2; PUSH2; PACK; STLOC0
// PUSH3; LDLOC0; PUSH0; PICK; UNPACK; DROP; REVERSE4; RET
let script = [
OpCode::Initslot.byte(),
0x01,
0x00, // INITSLOT 1 local, 0 args
OpCode::Push1.byte(),
OpCode::Push2.byte(),
OpCode::Push2.byte(),
OpCode::Pack.byte(),
OpCode::Stloc0.byte(), // PUSH1; PUSH2; PUSH2; PACK; STLOC0
OpCode::Push3.byte(),
OpCode::Ldloc0.byte(),
OpCode::Push0.byte(),
OpCode::Pick.byte(),
OpCode::Unpack.byte(),
OpCode::Drop.byte(),
OpCode::Reverse4.byte(),
OpCode::Ret.byte(), // PUSH3; LDLOC0; PUSH0; PICK; UNPACK; DROP; REVERSE4; RET
];
let nef_bytes = build_nef(&script);
let decompilation = Decompiler::new()
.decompile_bytes(&nef_bytes)
.expect("decompile succeeds");
let high_level = decompilation
.high_level
.as_deref()
.expect("high-level output");
// The previous "reverse top 4 stack values" check was VM
// narration — stripped from clean output now. The substantive
// check below ensures REVERSE4 didn't underflow after the
// PICK→UNPACK chain.
assert!(
!high_level.contains("insufficient values on stack for REVERSE4"),
"PICK should preserve packed shape metadata for downstream UNPACK stack modeling: {high_level}"
);
}