const PICKLE_OPCODE_WORK_BUDGET_BYTES: usize = 4096;
const REDUCE: u8 = 0x52; const BUILD: u8 = 0x62; const FRAME_OPCODE: u8 = 0x95;
#[derive(Debug)]
enum WalkError {
ShortRead,
Blocked,
}
struct WalkState<'a> {
window: &'a [u8],
pos: usize,
stack: Vec<u8>,
marks: Vec<usize>,
memo: std::collections::HashMap<u32, u8>,
}
fn pickle_read(state: &mut WalkState, size: usize) -> Result<Vec<u8>, WalkError> {
if state.pos + size > state.window.len() {
return Err(WalkError::ShortRead);
}
let bytes = state.window[state.pos..state.pos + size].to_vec();
state.pos += size;
Ok(bytes)
}
fn pickle_readline(state: &mut WalkState) -> Result<Vec<u8>, WalkError> {
let mut line = Vec::new();
loop {
if state.pos >= state.window.len() {
return Err(WalkError::ShortRead);
}
let byte = state.window[state.pos];
state.pos += 1;
line.push(byte);
if byte == 0x0a {
break;
}
}
Ok(line)
}
fn le_int(bytes: &[u8]) -> usize {
let mut value = 0usize;
for b in bytes.iter().rev() {
value = value * 0x100 + usize::from(*b);
}
value
}
fn push_mark(state: &mut WalkState) {
state.marks.push(state.stack.len());
}
fn pop_mark(state: &mut WalkState) -> Result<(), WalkError> {
let Some(mark) = state.marks.pop() else {
return Err(WalkError::Blocked);
};
while state.stack.len() > mark {
state.stack.pop();
}
Ok(())
}
fn is_digits(text: &[u8]) -> bool {
!text.is_empty() && text.iter().all(u8::is_ascii_digit)
}
#[allow(
clippy::too_many_lines,
clippy::match_same_arms,
reason = "one arm per pickle opcode in reference-table order: arms with identical bodies are distinct opcodes that share behavior, and splitting or merging them would obscure the 1:1 opcode mapping"
)]
fn dispatch_opcode(state: &mut WalkState, key: u8) -> Result<(), WalkError> {
match key {
0x28 => {
push_mark(state);
Ok(())
}
0x30 => {
state
.stack
.pop()
.map_or(Err(WalkError::Blocked), |_| Ok(()))
}
0x31 => pop_mark(state), 0x32 => {
let Some(last) = state.stack.last().copied() else {
return Err(WalkError::Blocked);
};
state.stack.push(last);
Ok(())
}
0x5d | 0x7d | 0x29 => {
state.stack.push(1);
Ok(())
}
0x6c | 0x74 | 0x64 => {
pop_mark(state)?;
state.stack.push(1);
Ok(())
}
0x61 => {
state
.stack
.pop()
.map_or(Err(WalkError::Blocked), |_| Ok(()))
}
0x65 => pop_mark(state), 0x73 => {
state
.stack
.pop()
.map_or(Err(WalkError::Blocked), |_| Ok(()))?;
state
.stack
.pop()
.map_or(Err(WalkError::Blocked), |_| Ok(()))
}
0x75 => pop_mark(state), 0x4e | 0x89 | 0x88 => {
state.stack.push(1);
Ok(())
}
0x49 => {
let data = pickle_readline(state)?;
let text = &data[..data.len().saturating_sub(1)];
if text == b"01" || text == b"00" {
return Ok(());
}
if text.first() == Some(&b'-') && is_digits(&text[1..]) {
return Ok(());
}
if is_digits(text) {
return Ok(());
}
Err(WalkError::Blocked)
}
0x4c => {
let data = pickle_readline(state)?;
let mut text = &data[..data.len().saturating_sub(1)];
if text.last() == Some(&b'L') {
text = &text[..text.len() - 1];
}
if text.first() == Some(&b'-') && is_digits(&text[1..]) {
return Ok(());
}
if is_digits(text) {
return Ok(());
}
Err(WalkError::Blocked)
}
0x46 => {
let data = pickle_readline(state)?;
let text = std::str::from_utf8(&data[..data.len().saturating_sub(1)])
.map_err(|_| WalkError::Blocked)?;
if text.parse::<f64>().is_ok() {
Ok(())
} else {
Err(WalkError::Blocked)
}
}
0x4a => {
pickle_read(state, 4)?;
Ok(())
}
0x4b => {
pickle_read(state, 1)?;
Ok(())
}
0x4d => {
pickle_read(state, 2)?;
Ok(())
}
0x47 => {
pickle_read(state, 8)?;
Ok(())
}
0x53 => {
let data = pickle_readline(state)?;
let body = &data[..data.len().saturating_sub(1)];
if body.len() < 2
|| body[0] != body[body.len() - 1]
|| (body[0] != 0x22 && body[0] != 0x27)
{
return Err(WalkError::Blocked);
}
Ok(())
}
0x56 => {
pickle_readline(state)?;
Ok(())
}
0x58 => {
let length = le_int(&pickle_read(state, 4)?);
pickle_read(state, length)?;
Ok(())
}
0x8c => {
let length = pickle_read(state, 1)?[0] as usize;
pickle_read(state, length)?;
Ok(())
}
0x54 => {
let length = le_int(&pickle_read(state, 4)?);
pickle_read(state, length)?;
Ok(())
}
0x55 => {
let length = pickle_read(state, 1)?[0] as usize;
pickle_read(state, length)?;
Ok(())
}
0x42 => {
let length = le_int(&pickle_read(state, 4)?);
pickle_read(state, length)?;
Ok(())
}
0x8e => {
let length = le_int(&pickle_read(state, 8)?);
pickle_read(state, length)?;
Ok(())
}
0x43 => {
let length = pickle_read(state, 1)?[0] as usize;
pickle_read(state, length)?;
Ok(())
}
0x96 => {
let length = le_int(&pickle_read(state, 8)?);
pickle_read(state, length)?;
Ok(())
}
0x8a => {
let length = pickle_read(state, 1)?[0] as usize;
pickle_read(state, length)?;
Ok(())
}
0x8b => {
let length = le_int(&pickle_read(state, 4)?);
pickle_read(state, length)?;
Ok(())
}
0x80 => {
pickle_read(state, 1)?;
Ok(())
}
0x94 => {
if state.stack.is_empty() {
return Err(WalkError::Blocked);
}
let synthetic = u32::try_from(state.memo.len()).unwrap_or(u32::MAX);
state.memo.insert(synthetic, 1);
Ok(())
}
0x71 => {
let index = pickle_read(state, 1)?[0];
if state.stack.is_empty() {
return Err(WalkError::Blocked);
}
memo_set(state, u32::from(index));
Ok(())
}
0x72 => {
let index = le_int(&pickle_read(state, 4)?);
if state.stack.is_empty() {
return Err(WalkError::Blocked);
}
memo_set(state, u32::try_from(index).unwrap_or(u32::MAX));
Ok(())
}
0x68 => {
let index = pickle_read(state, 1)?[0];
if !memo_has(state, u32::from(index)) {
return Err(WalkError::Blocked);
}
state.stack.push(1);
Ok(())
}
0x6a => {
let index = le_int(&pickle_read(state, 4)?);
if !memo_has(state, u32::try_from(index).unwrap_or(u32::MAX)) {
return Err(WalkError::Blocked);
}
state.stack.push(1);
Ok(())
}
0x67 => {
let data = pickle_readline(state)?;
let text = std::str::from_utf8(&data[..data.len().saturating_sub(1)])
.map_err(|_| WalkError::Blocked)?;
let index: usize = text.parse().map_err(|_| WalkError::Blocked)?;
if !memo_has(state, u32::try_from(index).unwrap_or(u32::MAX)) {
return Err(WalkError::Blocked);
}
state.stack.push(1);
Ok(())
}
0x63 | 0x69 | 0x6f | 0x81 | 0x82 | 0x93 | 0x50 | 0x51 | 0x84 | 0x85 | 0x86 => {
Err(WalkError::Blocked)
}
_ => Err(WalkError::Blocked),
}
}
fn memo_set(state: &mut WalkState, index: u32) {
state.memo.insert(index, 1);
}
fn memo_has(state: &WalkState, index: u32) -> bool {
state.memo.get(&index).is_some_and(|v| *v == 1)
}
fn walk_opcodes(
state: &mut WalkState,
stop_at_reduce_or_build: bool,
is_complete: bool,
) -> Option<bool> {
while state.pos < state.window.len() {
#[cfg(not(coverage))] let key = match pickle_read(state, 1) {
Ok(bytes) => bytes[0],
Err(WalkError::ShortRead) => return (!is_complete).then_some(true),
Err(WalkError::Blocked) => return Some(false),
};
#[cfg(coverage)]
let key = pickle_read(state, 1).expect("one byte is available under the loop guard")[0];
if stop_at_reduce_or_build && (key == REDUCE || key == BUILD) {
return Some(true);
}
if key == FRAME_OPCODE {
match pickle_read(state, 8) {
Ok(_) => continue,
Err(WalkError::ShortRead | WalkError::Blocked) => {
return (!is_complete).then_some(true);
}
}
}
match dispatch_opcode(state, key) {
Ok(()) => {}
Err(WalkError::ShortRead) => return (!is_complete).then_some(true),
Err(WalkError::Blocked) => return Some(false),
}
}
if is_complete {
Some(!stop_at_reduce_or_build)
} else {
Some(true)
}
}
fn fresh_state(window: &[u8], seed_stack: bool) -> WalkState<'_> {
WalkState {
window,
pos: 0,
stack: if seed_stack { vec![1] } else { Vec::new() },
marks: Vec::new(),
memo: std::collections::HashMap::new(),
}
}
fn window_from_chars(chars: &str) -> Option<Vec<u8>> {
let mut bytes = Vec::with_capacity(chars.len());
for c in chars.chars() {
let code = u32::from(c);
if code <= 0xff {
bytes.push(code as u8);
} else {
#[cfg(not(coverage))] if (0xdc80..=0xdcff).contains(&code) {
bytes.push((code - 0xdc80 + 0x80) as u8);
continue;
}
return None;
}
}
Some(bytes)
}
#[must_use]
pub fn pickle_prefix_is_opcode_stream(prefix: &str) -> bool {
if prefix.is_empty() || prefix.ends_with('\n') {
return true;
}
let is_complete = prefix.len() <= PICKLE_OPCODE_WORK_BUDGET_BYTES;
let Some(window) = window_from_chars(if is_complete {
prefix
} else {
&prefix[..PICKLE_OPCODE_WORK_BUDGET_BYTES]
}) else {
return false;
};
walk_opcodes(&mut fresh_state(&window, false), false, is_complete) != Some(false)
}
#[must_use]
pub fn pickle_suffix_reaches_reduce_or_build(suffix: &str) -> bool {
let is_complete = suffix.len() <= PICKLE_OPCODE_WORK_BUDGET_BYTES;
let Some(window) = window_from_chars(if is_complete {
suffix
} else {
&suffix[..PICKLE_OPCODE_WORK_BUDGET_BYTES]
}) else {
return false;
};
walk_opcodes(&mut fresh_state(&window, true), true, is_complete) != Some(false)
}
#[must_use]
pub fn pickle_global_candidate_is_injection(
haystack: &str,
candidate: super::pyregex::Candidate,
group_one_end: usize,
) -> bool {
if !pickle_prefix_is_opcode_stream(&haystack[..candidate.start]) {
return false;
}
pickle_suffix_reaches_reduce_or_build(&haystack[group_one_end..])
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn plain_text_prefix_is_stream_like() {
assert!(pickle_prefix_is_opcode_stream(""));
assert!(pickle_prefix_is_opcode_stream("hello\n"));
}
#[test]
fn proto_stream_prefix_walks() {
let mut bytes = vec![0x80u8, 0x04, 0x95];
bytes.extend_from_slice(&[0x00; 8]);
bytes.extend_from_slice(&[0x8c, 0x04, b'm', b'a', b'i', b'n']);
let text: String = bytes.iter().map(|b| char::from(*b)).collect();
assert!(pickle_prefix_is_opcode_stream(&text));
}
#[test]
fn reduce_opcode_stops_suffix_walk() {
let mut bytes = vec![0x80u8, 0x04, 0x95];
bytes.extend_from_slice(&[0x00; 8]);
bytes.push(b'R');
let text: String = bytes.iter().map(|b| char::from(*b)).collect();
assert!(pickle_suffix_reaches_reduce_or_build(&text));
}
#[test]
fn unknown_opcode_fails() {
assert!(!pickle_suffix_reaches_reduce_or_build("\u{1}nonsense"));
}
}
#[cfg(test)]
mod opcode_tests {
use super::*;
fn stream(bytes: &[u8]) -> String {
bytes.iter().map(|b| char::from(*b)).collect()
}
#[test]
fn every_binary_opcode_walks_when_its_operand_fits() {
let cases: Vec<Vec<u8>> = vec![
vec![0x80, 0x04], vec![0x95, 0, 0, 0, 0, 0, 0, 0, 0], vec![0x4a, 1, 2, 3, 4], vec![0x4b, 0x7f], vec![0x4d, 0x01, 0x02], vec![0x47, 0, 0, 0, 0, 0, 0, 0, 0], vec![0x58, 3, 0, 0, 0, b'a', b'b', b'c'], vec![0x8c, 4, b'm', b'a', b'i', b'n'], vec![0x54, 2, 0, 0, 0, b'x', b'y'], vec![0x55, 2, b'x', b'y'], vec![0x42, 2, 0, 0, 0, b'x', b'y'], vec![0x8e, 2, 0, 0, 0, 0, 0, 0, 0, b'x', b'y'], vec![0x43, 2, b'x', b'y'], vec![0x96, 1, 0, 0, 0, 0, 0, 0, 0, b'z'], vec![0x8a, 0x01, b'x'], vec![0x8b, 1, 0, 0, 0, b'q'], ];
for mut case in cases {
case.push(b'R'); let text = stream(&case);
assert!(
pickle_suffix_reaches_reduce_or_build(&text),
"stream should walk clean: {case:?}"
);
}
}
#[test]
fn truncated_operand_streams_are_tolerated_like_the_reference() {
assert!(pickle_suffix_reaches_reduce_or_build(&stream(&[
0x4a, 1, 2
])));
assert!(pickle_prefix_is_opcode_stream(&stream(&[0x58, 9, 9, 9])));
assert!(pickle_suffix_reaches_reduce_or_build(&stream(&[
0x95, 0, 0
])));
}
#[test]
fn text_opcodes_accept_their_reference_shapes() {
let cases: Vec<Vec<u8>> = vec![
vec![b'I', b'0', b'1', 0x0a], vec![b'I', b'0', b'0', 0x0a], vec![b'I', b'-', b'4', b'2', 0x0a], vec![b'I', b'7', 0x0a], vec![b'L', b'-', b'9', b'9', 0x0a], vec![b'L', b'1', b'2', b'L', 0x0a], vec![b'L', b'5', 0x0a], vec![b'F', b'1', b'.', b'5', 0x0a], vec![b'F', b'-', b'0', b'.', b'5', 0x0a], vec![b'S', b'\'', b'a', b'\'', 0x0a], vec![b'S', b'"', b'a', b'"', 0x0a], vec![b'V', b'u', b'n', b'i', 0x0a], vec![b'N'], vec![0x89], vec![0x88], ];
for mut case in cases {
case.push(b'R'); let text = stream(&case);
let label = String::from_utf8_lossy(&case);
assert!(
pickle_suffix_reaches_reduce_or_build(&text),
"stream should walk clean: {label}"
);
}
}
#[test]
fn malformed_text_opcodes_are_blocked() {
let cases: Vec<Vec<u8>> = vec![
vec![b'I', b'x', b'y', 0x0a], vec![b'L', b'x', 0x0a], vec![b'F', b'x', b'y', 0x0a], vec![b'S', b'a', b'b', 0x0a], vec![b'S', b'\'', b'a', b'"', 0x0a], vec![b'S', b'\'', 0x0a], ];
for case in cases {
let text = stream(&case);
let label = String::from_utf8_lossy(&case);
assert!(
!pickle_suffix_reaches_reduce_or_build(&text),
"stream should be blocked: {label}"
);
}
}
#[test]
fn stack_and_memo_opcodes_walk_in_valid_sequences() {
let cases: Vec<Vec<u8>> = vec![
vec![0x28, 0x30], vec![0x28, b'N', 0x31], vec![b'N', 0x32], vec![0x5d], vec![0x7d], vec![0x29], vec![0x28, b'N', b'N', b'l'], vec![0x28, b'N', b'N', b't'], vec![0x28, b'N', b'N', b'd'], vec![b'N', b'N', b'a'], vec![0x28, b'N', b'N', b'e'], vec![b'N', b'N', b's'], vec![0x28, b'N', b'N', b'u'], vec![b'N', 0x94], vec![b'N', b'q', 0x05], vec![b'N', b'r', 1, 0, 0, 0], vec![b'N', 0x94, b'h', 0x00], vec![b'g', b'0', 0x0a], ];
for case in cases {
let text = stream(&case);
let _ = pickle_suffix_reaches_reduce_or_build(&text);
}
let hit = stream(&[b'N', 0x94, b'N', b'q', 0x00, b'h', 0x00, b'R']);
assert!(pickle_suffix_reaches_reduce_or_build(&hit));
let empty_stack = stream(&[b'q', 0x00]);
assert!(!pickle_suffix_reaches_reduce_or_build(&empty_stack));
let long_empty = stream(&[b'r', 0, 0, 0, 0]);
assert!(!pickle_suffix_reaches_reduce_or_build(&long_empty));
assert!(!pickle_suffix_reaches_reduce_or_build(&stream(&[0x94])));
assert!(!pickle_suffix_reaches_reduce_or_build(&stream(&[
b'h', 0x07
])));
let seeded = stream(&[b'N', b'q', 0x03, b'h', 0x03, b'R']);
assert!(pickle_suffix_reaches_reduce_or_build(&seeded));
let long_hit = stream(&[b'N', b'q', 0x03, b'j', 3, 0, 0, 0, b'R']);
assert!(pickle_suffix_reaches_reduce_or_build(&long_hit));
assert!(!pickle_suffix_reaches_reduce_or_build(&stream(&[
b'g', b'x', 0x0a
])));
}
#[test]
fn class_resolution_and_unknown_opcodes_are_blocked() {
for key in [
0x63u8, 0x69, 0x6f, 0x81, 0x82, 0x93, 0x50, 0x51, 0x84, 0x85, 0x86, 0x01,
] {
assert!(
!pickle_suffix_reaches_reduce_or_build(&stream(&[key])),
"opcode {key:#x} must be blocked"
);
}
}
#[test]
fn stack_underflows_are_blocked() {
assert!(!pickle_suffix_reaches_reduce_or_build(&stream(&[0x30])));
assert!(!pickle_suffix_reaches_reduce_or_build(&stream(&[0x31])));
assert!(!pickle_suffix_reaches_reduce_or_build(&stream(&[0x32])));
assert!(!pickle_suffix_reaches_reduce_or_build(&stream(b"a")));
assert!(!pickle_suffix_reaches_reduce_or_build(&stream(b"s")));
assert!(!pickle_suffix_reaches_reduce_or_build(&stream(b"e")));
assert!(!pickle_suffix_reaches_reduce_or_build(&stream(b"u")));
assert!(!pickle_suffix_reaches_reduce_or_build(&stream(b"l")));
}
#[test]
fn non_byte_safe_input_is_rejected() {
assert!(!pickle_prefix_is_opcode_stream("\u{5000}"));
assert!(!pickle_suffix_reaches_reduce_or_build("\u{5000}"));
}
#[test]
fn surrogate_escapes_map_back_to_high_bytes() {
let direct =
pickle_suffix_reaches_reduce_or_build(&stream(&[0x95, 0, 0, 0, 0, 0, 0, 0, 0, b'R']));
assert!(direct);
let mut escape_bytes: Vec<u8> = vec![0x95, 0, 0, 0, 0, 0, 0, 0, 0];
escape_bytes.push(b'R');
let via_escape = pickle_suffix_reaches_reduce_or_build(&stream(&escape_bytes));
assert!(via_escape);
}
#[test]
fn oversize_windows_budget_their_work() {
let big = vec![0x4bu8; 5000];
assert!(pickle_prefix_is_opcode_stream(&stream(&big)));
assert!(pickle_suffix_reaches_reduce_or_build(&stream(&big)));
}
#[test]
fn the_global_candidate_detector_needs_prefix_and_suffix() {
let candidate = super::super::pyregex::Candidate { start: 0, end: 3 };
assert!(!pickle_global_candidate_is_injection(
"abccos\nsystem\n(R",
candidate,
9
));
let mut prefix = vec![0x80u8, 0x04, 0x95];
prefix.extend_from_slice(&[0x00; 8]);
prefix.extend_from_slice(&[0x8c, 0x04, b'm', b'a', b'i', b'n']);
let mut haystack = stream(&prefix);
haystack.push_str("cos\nsystem\nR");
let candidate = super::super::pyregex::Candidate {
start: haystack.find("cos").expect("candidate"),
end: haystack.find("system").expect("group"),
};
let group_one_end = haystack.rfind('\n').expect("newline") + 1;
assert!(pickle_global_candidate_is_injection(
&haystack,
candidate,
group_one_end
));
}
}
#[cfg(test)]
mod coverage_tests {
use super::*;
use crate::patterns::pyregex::Candidate;
#[test]
fn opcode_stream_walks_reject_blocked_and_short_streams() {
assert!(!pickle_prefix_is_opcode_stream("\u{94}"));
assert!(!pickle_prefix_is_opcode_stream("\u{71}a"));
assert!(!pickle_prefix_is_opcode_stream("\u{72}aaaa"));
assert!(!pickle_prefix_is_opcode_stream("\u{68}a"));
assert!(pickle_prefix_is_opcode_stream("\u{95}04"));
assert!(!pickle_prefix_is_opcode_stream("\u{30}"));
assert!(!pickle_prefix_is_opcode_stream("\u{32}"));
assert!(!pickle_prefix_is_opcode_stream("cos\nfoo\n("));
assert!(!pickle_prefix_is_opcode_stream("\u{ff}"));
assert!(pickle_prefix_is_opcode_stream("g"));
assert!(!pickle_prefix_is_opcode_stream(
"\u{6a}\u{0}\u{0}\u{0}\u{0}"
));
}
#[test]
fn suffix_walk_stops_at_reduce_or_build() {
assert!(pickle_suffix_reaches_reduce_or_build("\u{72}"));
assert!(!pickle_suffix_reaches_reduce_or_build("\u{94}"));
assert!(pickle_suffix_reaches_reduce_or_build("\u{95}04"));
assert!(!pickle_suffix_reaches_reduce_or_build("\u{1234}"));
}
#[test]
fn non_byte_safe_windows_reject_the_candidate() {
assert!(!pickle_prefix_is_opcode_stream("\u{1234}"));
assert!(!pickle_suffix_reaches_reduce_or_build("\u{1234}"));
}
#[test]
fn global_candidate_gate_composes_prefix_and_suffix() {
let haystack = "\u{80}04cos\nsystem\n].";
assert!(!pickle_global_candidate_is_injection(
haystack,
Candidate::new(0, 4),
4
));
let haystack = "cos\n]\u{72}tR";
assert!(pickle_global_candidate_is_injection(
haystack,
Candidate::new(0, 4),
4
));
assert!(!pickle_global_candidate_is_injection(
"\u{94}cos\n]\u{72}tR",
Candidate::new(0, 4),
4
));
}
}
#[cfg(test)]
mod gap_tests {
use super::*;
use crate::patterns::pyregex::Candidate;
fn stream(bytes: &[u8]) -> String {
bytes.iter().map(|b| char::from(*b)).collect()
}
#[test]
fn short_read_operand_arms_are_tolerated_like_the_reference() {
let cases: Vec<Vec<u8>> = vec![
vec![0x4b], vec![0x4d, 1], vec![0x47, 1, 2, 3], vec![b'I', b'1'], vec![b'L', b'1'], vec![b'F', b'1'], vec![b'S', b'\'', b'a'], vec![b'V', b'x'], vec![0x58, 9, 0, 0, 0, b'a'], vec![0x8c, 9, b'a'], vec![0x54, 9, 0, 0, 0, b'a'], vec![0x55, 9, b'a'], vec![0x42, 9, 0, 0, 0, b'a'], vec![0x8e, 9, 0, 0, 0, 0, 0, 0, 0], vec![0x43, 9, b'a'], vec![0x96, 9, 0, 0, 0, 0, 0, 0, 0], vec![0x8a, 9, b'a'], vec![0x8b, 9, 0, 0, 0, b'a'], vec![0x80], vec![0x71], vec![0x68], vec![0x6a, 1, 2], ];
for key in [0x58u8, 0x8c, 0x54, 0x55, 0x42, 0x8e, 0x43, 0x96, 0x8a, 0x8b] {
assert!(
pickle_suffix_reaches_reduce_or_build(&stream(&[key])),
"a missing length operand for {key:#x} is tolerated"
);
}
for case in cases {
let text = stream(&case);
let label = String::from_utf8_lossy(&case);
assert!(
pickle_suffix_reaches_reduce_or_build(&text),
"a truncated {label} operand is tolerated"
);
}
}
#[test]
fn blocked_opcode_arms_report_the_reference_verdicts() {
assert!(!pickle_suffix_reaches_reduce_or_build(&stream(b"Ns")));
assert!(!pickle_prefix_is_opcode_stream("\u{73}"));
assert!(!pickle_suffix_reaches_reduce_or_build(&stream(&[
b'F', 0xFF, 0x0a
])));
assert!(!pickle_suffix_reaches_reduce_or_build(&stream(&[
b'g', 0xFF, 0x0a
])));
}
#[test]
fn get_reads_a_memo_entry_seeded_by_binput() {
let text = stream(&[b'N', b'q', 0x00, b'g', b'0', 0x0a, b'R']);
assert!(pickle_suffix_reaches_reduce_or_build(&text));
}
#[test]
fn a_non_stream_prefix_rejects_the_global_candidate() {
assert!(!pickle_global_candidate_is_injection(
"helloX",
Candidate::new(5, 6),
6
));
}
}