1#![allow(
8 clippy::undocumented_unsafe_blocks,
9 reason = "every pointer here is the installer's own, established once in \
10 `Stack::from_raw` and valid for the duration of a plug-in call; \
11 restating it on each dereference would bury the comments that \
12 carry real information"
13)]
14
15use alloc::string::String;
16use alloc::vec::Vec;
17
18use crate::error::{Error, Result};
19use crate::int;
20use crate::raw::StackNode;
21use crate::sys;
22use crate::tchar::{self, Tchar};
23
24pub struct Stack {
29 top: *mut *mut StackNode,
30 string_size: usize,
31}
32
33impl Stack {
34 #[must_use]
41 pub unsafe fn from_raw(top: *mut *mut StackNode, string_size: usize) -> Self {
42 Self { top, string_size }
43 }
44
45 #[must_use]
47 pub fn string_size(&self) -> usize {
48 self.string_size
49 }
50
51 #[must_use]
53 pub fn max_len(&self) -> usize {
54 self.string_size.saturating_sub(1)
55 }
56
57 #[must_use]
59 pub fn is_empty(&self) -> bool {
60 self.top.is_null() || unsafe { (*self.top).is_null() }
61 }
62
63 #[must_use]
67 pub fn len(&self) -> usize {
68 if self.top.is_null() {
69 return 0;
70 }
71 let mut node = unsafe { *self.top };
72 let mut n = 0;
73 while !node.is_null() {
74 n += 1;
75 node = unsafe { (*node).next };
76 }
77 n
78 }
79
80 pub fn pop(&mut self) -> Result<String> {
82 self.pop_units().map(|units| tchar::decode(&units))
83 }
84
85 pub fn peek(&self) -> Result<String> {
87 let node = self.head()?;
88 Ok(unsafe { tchar::read_bounded(self.text_of(node), self.string_size) })
89 }
90
91 pub fn discard(&mut self) -> Result<()> {
93 self.pop_units().map(|_| ())
94 }
95
96 pub fn pop_int(&mut self) -> Result<isize> {
101 let units = self.pop_units()?;
102 Ok(int::str_to_ptr(int::parse_window(&units)))
103 }
104
105 pub fn pop_int_or(&mut self) -> Result<isize> {
107 let units = self.pop_units()?;
108 Ok(int::atoi_or(int::parse_window(&units)))
109 }
110
111 pub fn push(&mut self, s: &str) -> Result<()> {
117 self.push_units(&tchar::encode(s))
118 }
119
120 pub fn push_int(&mut self, value: isize) -> Result<()> {
122 self.push_units(&int::format(value))
123 }
124
125 pub fn push_bool(&mut self, value: bool) -> Result<()> {
127 self.push_int(isize::from(value))
128 }
129
130 fn head(&self) -> Result<*mut StackNode> {
133 if self.top.is_null() {
134 return Err(Error::NoStack);
135 }
136 let node = unsafe { *self.top };
137 if node.is_null() {
138 return Err(Error::EmptyStack);
139 }
140 Ok(node)
141 }
142
143 fn text_of(&self, node: *mut StackNode) -> *mut Tchar {
144 unsafe { (&raw mut (*node).text).cast::<Tchar>() }
145 }
146
147 fn pop_units(&mut self) -> Result<Vec<Tchar>> {
148 let node = self.head()?;
149 let text = self.text_of(node);
150 let len = unsafe { tchar::strlen_bounded(text, self.string_size) };
151 let units = unsafe { core::slice::from_raw_parts(text, len) }.to_vec();
152 unsafe {
153 *self.top = (*node).next;
154 sys::free(node.cast());
155 }
156 Ok(units)
157 }
158
159 fn push_units(&mut self, units: &[Tchar]) -> Result<()> {
160 if self.top.is_null() {
161 return Err(Error::NoStack);
162 }
163 let node = unsafe { sys::alloc_zeroed(StackNode::alloc_size(self.string_size)) }
167 .cast::<StackNode>();
168 if node.is_null() {
169 return Err(Error::OutOfMemory);
170 }
171
172 let fit = unsafe { tchar::write_bounded(self.text_of(node), self.string_size, units) };
173 unsafe {
174 (*node).next = *self.top;
175 *self.top = node;
176 }
177
178 if fit { Ok(()) } else { Err(Error::Truncated) }
179 }
180}
181
182#[cfg(test)]
183mod tests {
184 use alloc::string::String;
185 use alloc::vec::Vec;
186
187 use super::*;
188 use crate::testing::TestInstaller;
189
190 #[test]
191 fn round_trips_a_string() {
192 let mut inst = TestInstaller::stock();
193 inst.nsis().stack.push("hello").unwrap();
194 assert_eq!(inst.nsis().stack.pop().unwrap(), "hello");
195 }
196
197 #[test]
198 fn is_last_in_first_out() {
199 let mut inst = TestInstaller::stock();
200 let mut nsis = inst.nsis();
201 for value in ["first", "second", "third"] {
202 nsis.stack.push(value).unwrap();
203 }
204 assert_eq!(inst.stack(), ["third", "second", "first"]);
205 assert_eq!(inst.pop().unwrap(), "third");
206 assert_eq!(inst.pop().unwrap(), "second");
207 assert_eq!(inst.pop().unwrap(), "first");
208 assert!(inst.is_empty());
209 }
210
211 #[test]
212 fn popping_an_empty_stack_is_an_error() {
213 let mut inst = TestInstaller::stock();
214 assert_eq!(inst.nsis().stack.pop().unwrap_err(), Error::EmptyStack);
215 }
216
217 #[test]
218 fn a_null_stack_is_not_a_crash() {
219 let mut stack = unsafe { Stack::from_raw(core::ptr::null_mut(), 1024) };
220 assert!(stack.is_empty());
221 assert_eq!(stack.len(), 0);
222 assert_eq!(stack.pop().unwrap_err(), Error::NoStack);
223 assert_eq!(stack.push("x").unwrap_err(), Error::NoStack);
224 }
225
226 #[test]
229 fn the_boundary_is_string_size_minus_one() {
230 for size in [64, 1024, 8192] {
231 let mut inst = TestInstaller::new(size);
232
233 let exact: String = core::iter::repeat_n('x', size - 1).collect();
234 inst.nsis().stack.push(&exact).unwrap();
235 assert_eq!(inst.pop().unwrap(), exact);
236
237 let one_too_many: String = core::iter::repeat_n('x', size).collect();
238 assert_eq!(
239 inst.nsis().stack.push(&one_too_many).unwrap_err(),
240 Error::Truncated
241 );
242 assert_eq!(inst.pop().unwrap().len(), size - 1);
244 }
245 }
246
247 #[test]
250 fn a_long_string_build_holds_long_strings() {
251 let mut inst = TestInstaller::long_string();
252 let long: String = core::iter::repeat_n('z', 8191).collect();
253 inst.nsis().stack.push(&long).unwrap();
254 assert_eq!(inst.pop().unwrap(), long);
255
256 let mut stock = TestInstaller::stock();
259 assert_eq!(
260 stock.nsis().stack.push(&long).unwrap_err(),
261 Error::Truncated
262 );
263 assert_eq!(stock.pop().unwrap().len(), 1023);
264 }
265
266 #[test]
268 fn a_zero_string_size_still_pushes_an_empty_entry() {
269 let mut inst = TestInstaller::new(0);
270 assert_eq!(inst.nsis().stack.push("x").unwrap_err(), Error::Truncated);
271 assert_eq!(inst.stack(), [""]);
272 }
273
274 #[test]
275 fn pops_integers_with_nsis_semantics() {
276 let mut inst = TestInstaller::stock();
277 inst.push("0x10");
278 assert_eq!(inst.nsis().stack.pop_int().unwrap(), 16);
279
280 inst.push("2|4|8");
281 assert_eq!(inst.nsis().stack.pop_int_or().unwrap(), 14);
282
283 inst.push("not a number");
285 assert_eq!(inst.nsis().stack.pop_int().unwrap(), 0);
286 }
287
288 #[test]
289 fn pushes_integers_as_scripts_expect() {
290 let mut inst = TestInstaller::stock();
291 inst.nsis().stack.push_int(-42).unwrap();
292 assert_eq!(inst.pop().unwrap(), "-42");
293 inst.nsis().stack.push_bool(true).unwrap();
294 assert_eq!(inst.pop().unwrap(), "1");
295 }
296
297 #[test]
298 fn peek_does_not_consume() {
299 let mut inst = TestInstaller::stock();
300 inst.push("kept");
301 assert_eq!(inst.nsis().stack.peek().unwrap(), "kept");
302 assert_eq!(inst.nsis().stack.peek().unwrap(), "kept");
303 assert_eq!(inst.nsis().stack.len(), 1);
304 }
305
306 #[test]
309 fn survives_non_ascii() {
310 let mut inst = TestInstaller::stock();
311
312 #[cfg(feature = "unicode")]
313 let values = ["grüße", "日本語", "🦀"];
314 #[cfg(all(feature = "ansi", not(feature = "unicode")))]
315 let values = ["grüße", "façade"];
316
317 for value in values {
318 inst.nsis().stack.push(value).unwrap();
319 assert_eq!(inst.pop().unwrap(), value);
320 }
321 }
322
323 #[test]
324 fn many_entries_do_not_leak_into_each_other() {
325 let mut inst = TestInstaller::new(32);
326 let values: Vec<String> = (0..50).map(|i| alloc::format!("value-{i}")).collect();
327 {
328 let mut nsis = inst.nsis();
329 for value in &values {
330 nsis.stack.push(value).unwrap();
331 }
332 }
333 for value in values.iter().rev() {
334 assert_eq!(&inst.pop().unwrap(), value);
335 }
336 assert!(inst.is_empty());
337 }
338}