Skip to main content

run_emitter_test_suite/
run-emitter-test-suite.rs

1#![warn(clippy::pedantic)]
2#![allow(
3    clippy::cast_lossless,
4    clippy::cast_possible_truncation,
5    clippy::cast_possible_wrap,
6    clippy::cast_sign_loss,
7    clippy::items_after_statements,
8    clippy::let_underscore_untyped,
9    clippy::missing_errors_doc,
10    clippy::missing_safety_doc,
11    clippy::ptr_as_ptr,
12    clippy::single_match_else,
13    clippy::too_many_lines,
14    clippy::uninlined_format_args,
15    clippy::unreadable_literal
16)]
17
18mod cstr;
19
20use std::{
21    env,
22    error::Error,
23    ffi::c_void,
24    fs::File,
25    io::{self, Read, Write},
26    mem::MaybeUninit,
27    process::{self, ExitCode},
28    ptr::{self, addr_of_mut},
29    slice,
30};
31
32use unsafe_libyaml::{
33    YAML_ANY_SCALAR_STYLE, YAML_BLOCK_MAPPING_STYLE, YAML_BLOCK_SEQUENCE_STYLE, YAML_DOUBLE_QUOTED_SCALAR_STYLE,
34    YAML_EMITTER_ERROR, YAML_FOLDED_SCALAR_STYLE, YAML_LITERAL_SCALAR_STYLE, YAML_MEMORY_ERROR,
35    YAML_PLAIN_SCALAR_STYLE, YAML_SINGLE_QUOTED_SCALAR_STYLE, YAML_UTF8_ENCODING, YAML_WRITER_ERROR,
36    yaml_alias_event_initialize, yaml_document_end_event_initialize, yaml_document_start_event_initialize,
37    yaml_emitter_delete, yaml_emitter_emit, yaml_emitter_initialize, yaml_emitter_set_canonical,
38    yaml_emitter_set_output, yaml_emitter_set_unicode, yaml_emitter_t, yaml_event_t, yaml_mapping_end_event_initialize,
39    yaml_mapping_start_event_initialize, yaml_scalar_event_initialize, yaml_scalar_style_t,
40    yaml_sequence_end_event_initialize, yaml_sequence_start_event_initialize, yaml_stream_end_event_initialize,
41    yaml_stream_start_event_initialize, yaml_tag_directive_t, yaml_version_directive_t,
42};
43
44use self::cstr::CStr;
45
46pub(crate) unsafe fn unsafe_main(stdin: &mut dyn Read, mut stdout: &mut dyn Write) -> Result<(), Box<dyn Error>> {
47    let mut emitter = MaybeUninit::<yaml_emitter_t>::uninit();
48    let emitter = emitter.as_mut_ptr();
49    if yaml_emitter_initialize(emitter).fail {
50        return Err("Could not initialize the emitter object".into());
51    }
52
53    unsafe fn write_to_stdio(data: *mut c_void, buffer: *mut u8, size: u64) -> i32 {
54        let stdout: *mut &mut dyn Write = data.cast();
55        let bytes = slice::from_raw_parts(buffer.cast(), size as usize);
56        match (*stdout).write(bytes) {
57            Ok(n) => n as i32,
58            Err(_) => 0,
59        }
60    }
61
62    yaml_emitter_set_output(emitter, write_to_stdio, addr_of_mut!(stdout).cast());
63    yaml_emitter_set_canonical(emitter, false);
64    yaml_emitter_set_unicode(emitter, false);
65
66    let mut buf = ReadBuf::new();
67    let mut event = MaybeUninit::<yaml_event_t>::uninit();
68    let event = event.as_mut_ptr();
69    let result = loop {
70        let line = match buf.get_line(stdin) {
71            Some(line) => line,
72            None => break Ok(()),
73        };
74
75        let mut anchor = [0u8; 256];
76        let mut tag = [0u8; 256];
77        let result = if line.starts_with(b"+STR") {
78            yaml_stream_start_event_initialize(event, YAML_UTF8_ENCODING)
79        } else if line.starts_with(b"-STR") {
80            yaml_stream_end_event_initialize(event)
81        } else if line.starts_with(b"+DOC") {
82            let implicit = !line[4..].starts_with(b" ---");
83            yaml_document_start_event_initialize(
84                event,
85                ptr::null_mut::<yaml_version_directive_t>(),
86                ptr::null_mut::<yaml_tag_directive_t>(),
87                ptr::null_mut::<yaml_tag_directive_t>(),
88                implicit,
89            )
90        } else if line.starts_with(b"-DOC") {
91            let implicit = !line[4..].starts_with(b" ...");
92            yaml_document_end_event_initialize(event, implicit)
93        } else if line.starts_with(b"+MAP") {
94            yaml_mapping_start_event_initialize(
95                event,
96                get_anchor(b'&', line, anchor.as_mut_ptr()),
97                get_tag(line, tag.as_mut_ptr()),
98                false,
99                YAML_BLOCK_MAPPING_STYLE,
100            )
101        } else if line.starts_with(b"-MAP") {
102            yaml_mapping_end_event_initialize(event)
103        } else if line.starts_with(b"+SEQ") {
104            yaml_sequence_start_event_initialize(
105                event,
106                get_anchor(b'&', line, anchor.as_mut_ptr()),
107                get_tag(line, tag.as_mut_ptr()),
108                false,
109                YAML_BLOCK_SEQUENCE_STYLE,
110            )
111        } else if line.starts_with(b"-SEQ") {
112            yaml_sequence_end_event_initialize(event)
113        } else if line.starts_with(b"=VAL") {
114            let mut value = [0i8; 1024];
115            let mut style = YAML_ANY_SCALAR_STYLE;
116            get_value(line, value.as_mut_ptr(), &mut style);
117            let implicit = get_tag(line, tag.as_mut_ptr()).is_null();
118            yaml_scalar_event_initialize(
119                event,
120                get_anchor(b'&', line, anchor.as_mut_ptr()),
121                get_tag(line, tag.as_mut_ptr()),
122                value.as_mut_ptr() as *mut u8,
123                -1,
124                implicit,
125                implicit,
126                style,
127            )
128        } else if line.starts_with(b"=ALI") {
129            yaml_alias_event_initialize(event, get_anchor(b'*', line, anchor.as_mut_ptr()))
130        } else {
131            let line = line as *mut [u8] as *mut i8;
132            break Err(format!("Unknown event: '{}'", CStr::from_ptr(line)).into());
133        };
134
135        if result.fail {
136            break Err("Memory error: Not enough memory for creating an event".into());
137        }
138        if yaml_emitter_emit(emitter, event).fail {
139            break Err(match (&(*emitter)).error {
140                YAML_MEMORY_ERROR => "Memory error: Not enough memory for emitting".into(),
141                YAML_WRITER_ERROR => format!("Writer error: {}", CStr::from_ptr((&(*emitter)).problem)).into(),
142                YAML_EMITTER_ERROR => format!("Emitter error: {}", CStr::from_ptr((&(*emitter)).problem)).into(),
143                // Couldn't happen.
144                _ => "Internal error".into(),
145            });
146        }
147    };
148
149    yaml_emitter_delete(emitter);
150    result
151}
152
153struct ReadBuf {
154    buf: [u8; 1024],
155    offset: usize,
156    filled: usize,
157}
158
159impl ReadBuf {
160    fn new() -> Self {
161        ReadBuf {
162            buf: [0; 1024],
163            offset: 0,
164            filled: 0,
165        }
166    }
167
168    fn get_line(&mut self, input: &mut dyn Read) -> Option<&mut [u8]> {
169        loop {
170            for i in self.offset..self.offset + self.filled {
171                if self.buf[i] == b'\n' {
172                    self.buf[i] = b'\0';
173                    let line = &mut self.buf[self.offset..=i];
174                    self.offset = i + 1;
175                    self.filled -= line.len();
176                    return Some(line);
177                }
178            }
179            let mut remainder = &mut self.buf[self.offset + self.filled..];
180            if remainder.is_empty() {
181                if self.offset == 0 {
182                    let _ = writeln!(io::stderr(), "Line too long: '{}'", String::from_utf8_lossy(&self.buf),);
183                    process::abort();
184                }
185                self.buf.copy_within(self.offset.., 0);
186                self.offset = 0;
187                remainder = &mut self.buf;
188            }
189            let n = input.read(remainder).ok()?;
190            self.filled += n;
191            if n == 0 {
192                return None;
193            }
194        }
195    }
196}
197
198unsafe fn get_anchor(sigil: u8, line: &[u8], anchor: *mut u8) -> *mut u8 {
199    let start = match line.iter().position(|ch| *ch == sigil) {
200        Some(offset) => offset + 1,
201        None => return ptr::null_mut::<u8>(),
202    };
203    let end = match line[start..].iter().position(|ch| *ch == b' ') {
204        Some(offset) => start + offset,
205        None => line.len(),
206    };
207    ptr::copy_nonoverlapping(line[start..end].as_ptr(), anchor, end - start);
208    *anchor.add(end - start) = b'\0';
209    anchor
210}
211
212unsafe fn get_tag(line: &[u8], tag: *mut u8) -> *mut u8 {
213    let start = match line.iter().position(|ch| *ch == b'<') {
214        Some(offset) => offset + 1,
215        None => return ptr::null_mut::<u8>(),
216    };
217    let end = match line[start..].iter().position(|ch| *ch == b'>') {
218        Some(offset) => start + offset,
219        None => return ptr::null_mut::<u8>(),
220    };
221    ptr::copy_nonoverlapping(line[start..end].as_ptr(), tag, end - start);
222    *tag.add(end - start) = b'\0';
223    tag
224}
225
226unsafe fn get_value(line: &[u8], value: *mut i8, style: *mut yaml_scalar_style_t) {
227    let line_len = line.len();
228    let line = line as *const [u8] as *mut i8;
229    let mut start = ptr::null_mut::<i8>();
230    let end = line.add(line_len);
231    let mut c = line.offset(4);
232    while c < end {
233        if *c as u8 == b' ' {
234            start = c.offset(1);
235            *style = match *start as u8 {
236                b':' => YAML_PLAIN_SCALAR_STYLE,
237                b'\'' => YAML_SINGLE_QUOTED_SCALAR_STYLE,
238                b'"' => YAML_DOUBLE_QUOTED_SCALAR_STYLE,
239                b'|' => YAML_LITERAL_SCALAR_STYLE,
240                b'>' => YAML_FOLDED_SCALAR_STYLE,
241                _ => {
242                    start = ptr::null_mut::<i8>();
243                    c = c.offset(1);
244                    continue;
245                }
246            };
247            start = start.offset(1);
248            break;
249        }
250        c = c.offset(1);
251    }
252    if start.is_null() {
253        process::abort();
254    }
255
256    let mut i = 0;
257    c = start;
258    while c < end {
259        *value.offset(i) = if *c as u8 == b'\\' {
260            c = c.offset(1);
261            match *c as u8 {
262                b'\\' => b'\\' as i8,
263                b'0' => b'\0' as i8,
264                b'b' => b'\x08' as i8,
265                b'n' => b'\n' as i8,
266                b'r' => b'\r' as i8,
267                b't' => b'\t' as i8,
268                _ => process::abort(),
269            }
270        } else {
271            *c
272        };
273        i += 1;
274        c = c.offset(1);
275    }
276    *value.offset(i) = b'\0' as i8;
277}
278
279fn main() -> ExitCode {
280    let args = env::args_os().skip(1);
281    if args.len() == 0 {
282        let _ = writeln!(io::stderr(), "Usage: run-emitter-test-suite <test.event>...",);
283        return ExitCode::FAILURE;
284    }
285    for arg in args {
286        let mut stdin = File::open(arg).unwrap();
287        let mut stdout = io::stdout();
288        let result = unsafe { unsafe_main(&mut stdin, &mut stdout) };
289        if let Err(err) = result {
290            let _ = writeln!(io::stderr(), "{}", err);
291            return ExitCode::FAILURE;
292        }
293    }
294    ExitCode::SUCCESS
295}