1mod error {
5 use crate::asm::Error as AssembleError;
6 use crate::ParseError;
7
8 use snafu::{Backtrace, Snafu};
9
10 use std::path::PathBuf;
11
12 #[derive(Debug, Snafu)]
14 #[non_exhaustive]
15 #[snafu(context(suffix(false)), visibility(pub(super)))]
16 pub enum Error {
17 #[snafu(display(
19 "`{}` is outside of the root directory `{}`",
20 file.display(),
21 root.display()
22 ))]
23 #[non_exhaustive]
24 DirectoryTraversal {
25 root: PathBuf,
27
28 file: PathBuf,
30 },
31
32 #[snafu(display(
34 "an i/o error occurred on path `{}` ({})",
35 path.as_ref().map(|p| p.display().to_string()).unwrap_or_default(),
36 message,
37 ))]
38 #[non_exhaustive]
39 Io {
40 source: std::io::Error,
42
43 message: String,
45
46 backtrace: Backtrace,
48
49 path: Option<PathBuf>,
51 },
52
53 #[snafu(context(false))]
55 #[non_exhaustive]
56 #[snafu(display("parsing failed"))]
57 Parse {
58 #[snafu(backtrace)]
60 source: ParseError,
61 },
62
63 #[snafu(context(false))]
65 #[non_exhaustive]
66 #[snafu(display("assembling failed"))]
67 Assemble {
68 #[snafu(backtrace)]
70 source: AssembleError,
71 },
72
73 #[snafu(display("included file `{}` is invalid hex: {}", path.to_string_lossy(), source))]
75 #[non_exhaustive]
76 InvalidHex {
77 path: PathBuf,
79
80 source: Box<dyn std::error::Error>,
82
83 backtrace: Backtrace,
85 },
86
87 #[snafu(display("too many levels of recursion/includes"))]
89 #[non_exhaustive]
90 RecursionLimit {
91 backtrace: Backtrace,
93 },
94 }
95}
96
97use crate::asm::{Assembler, RawOp};
98use crate::ast::Node;
99use crate::parse::parse_asm;
100
101pub use self::error::Error;
102
103use snafu::{ensure, ResultExt};
104
105use std::fs::{read_to_string, File};
106use std::io::{self, Read, Write};
107use std::path::{Path, PathBuf};
108
109fn parse_file<P: AsRef<Path>>(path: P) -> Result<Vec<Node>, Error> {
110 let asm = read_to_string(path.as_ref()).with_context(|_| error::Io {
111 message: "reading file before parsing",
112 path: path.as_ref().to_owned(),
113 })?;
114 let nodes = parse_asm(&asm)?;
115
116 Ok(nodes)
117}
118
119#[derive(Debug)]
120enum Scope {
121 Same,
122 Independent(Box<Assembler>),
123}
124
125impl Scope {
126 fn same() -> Self {
127 Self::Same
128 }
129
130 fn independent() -> Self {
131 Self::Independent(Box::new(Assembler::new()))
132 }
133}
134
135#[derive(Debug)]
136struct Source {
137 path: PathBuf,
138 nodes: std::vec::IntoIter<Node>,
139 scope: Scope,
140}
141
142#[derive(Debug)]
143struct Root {
144 original: PathBuf,
145 canonicalized: PathBuf,
146}
147
148impl Root {
149 fn new(mut file: PathBuf) -> Result<Self, Error> {
150 if !file.pop() {
152 return Err(io::Error::from(io::ErrorKind::NotFound)).context(error::Io {
153 message: "no parent",
154 path: Some(file),
155 });
156 }
157
158 let file = std::env::current_dir()
159 .context(error::Io {
160 message: "getting cwd",
161 path: None,
162 })?
163 .join(file);
164
165 let metadata = file.metadata().with_context(|_| error::Io {
166 message: "getting metadata",
167 path: file.clone(),
168 })?;
169
170 if !metadata.is_dir() {
172 let err = io::Error::from(io::ErrorKind::NotFound);
173 return Err(err).context(error::Io {
174 message: "root is not directory",
175 path: file,
176 });
177 }
178
179 let canonicalized = std::fs::canonicalize(&file).with_context(|_| error::Io {
180 message: "canonicalizing root",
181 path: file.clone(),
182 })?;
183
184 Ok(Self {
185 original: file,
186 canonicalized,
187 })
188 }
189
190 fn check<P>(&self, path: P) -> Result<(), Error>
191 where
192 P: AsRef<Path>,
193 {
194 let path = path.as_ref();
195
196 let canonicalized = std::fs::canonicalize(path).with_context(|_| error::Io {
197 message: "canonicalizing include/import",
198 path: path.to_owned(),
199 })?;
200
201 if canonicalized.starts_with(&self.canonicalized) {
203 Ok(())
204 } else {
205 error::DirectoryTraversal {
206 root: self.original.clone(),
207 file: path.to_owned(),
208 }
209 .fail()
210 }
211 }
212}
213
214#[must_use]
215struct PartialSource<'a, W> {
216 stack: &'a mut SourceStack<W>,
217 path: PathBuf,
218 scope: Scope,
219}
220
221impl<'a, W> PartialSource<'a, W> {
222 fn path(&self) -> &Path {
223 &self.path
224 }
225
226 fn push(self, nodes: Vec<Node>) -> &'a mut Source {
227 self.stack.sources.push(Source {
228 path: self.path,
229 nodes: nodes.into_iter(),
230 scope: self.scope,
231 });
232
233 self.stack.sources.last_mut().unwrap()
234 }
235}
236
237#[derive(Debug)]
238struct SourceStack<W> {
239 output: W,
240 sources: Vec<Source>,
241 root: Option<Root>,
242}
243
244impl<W> SourceStack<W> {
245 fn new(output: W) -> Self {
246 Self {
247 output,
248 sources: Default::default(),
249 root: Default::default(),
250 }
251 }
252
253 fn resolve(&mut self, path: PathBuf, scope: Scope) -> Result<PartialSource<W>, Error> {
254 ensure!(self.sources.len() <= 255, error::RecursionLimit);
255
256 let path = if let Some(ref root) = self.root {
257 let last = self.sources.last().unwrap();
258 let dir = match last.path.parent() {
259 Some(s) => s,
260 None => Path::new("./"),
261 };
262 let candidate = dir.join(path);
263 root.check(&candidate)?;
264 candidate
265 } else {
266 assert!(self.sources.is_empty());
267 self.root = Some(Root::new(path.clone())?);
268 path
269 };
270
271 Ok(PartialSource {
272 stack: self,
273 path,
274 scope,
275 })
276 }
277
278 fn peek(&mut self) -> Option<&mut Source> {
279 self.sources.last_mut()
280 }
281}
282
283impl<W> SourceStack<W>
284where
285 W: Write,
286{
287 fn pop(&mut self) -> Result<(), Error> {
288 let popped = self.sources.pop().unwrap();
289
290 if self.sources.is_empty() {
291 self.root = None;
292 }
293
294 let mut asm = match popped.scope {
295 Scope::Independent(a) => a,
296 Scope::Same => return Ok(()),
297 };
298
299 let raw = asm.take();
300 asm.finish()?;
301
302 if raw.is_empty() {
303 return Ok(());
304 }
305
306 if self.sources.is_empty() {
307 self.output.write_all(&raw).context(error::Io {
308 message: "writing output",
309 path: None,
310 })?;
311 Ok(())
312 } else {
313 self.write(RawOp::Raw(raw))
314 }
315 }
316
317 fn write(&mut self, mut op: RawOp) -> Result<(), Error> {
318 if self.sources.is_empty() {
319 panic!("no sources!");
320 }
321
322 for frame in self.sources[1..].iter_mut().rev() {
323 let asm = match frame.scope {
324 Scope::Same => continue,
325 Scope::Independent(ref mut a) => a,
326 };
327
328 if 0 == asm.push(op)? {
329 return Ok(());
330 } else {
331 op = RawOp::Raw(asm.take());
332 }
333 }
334
335 let first_asm = match self.sources[0].scope {
336 Scope::Independent(ref mut a) => a,
337 Scope::Same => panic!("sources[0] must be independent"),
338 };
339
340 first_asm.push(op)?;
341
342 Ok(())
343 }
344}
345
346#[derive(Debug)]
372pub struct Ingest<W> {
373 sources: SourceStack<W>,
374}
375
376impl<W> Ingest<W> {
377 pub fn new(output: W) -> Self {
379 Self {
380 sources: SourceStack::new(output),
381 }
382 }
383}
384
385impl<W> Ingest<W>
386where
387 W: Write,
388{
389 pub fn ingest_file<P>(&mut self, path: P) -> Result<(), Error>
391 where
392 P: Into<PathBuf>,
393 {
394 let path = path.into();
395
396 let mut file = File::open(&path).with_context(|_| error::Io {
397 message: "opening source",
398 path: path.clone(),
399 })?;
400 let mut text = String::new();
401 file.read_to_string(&mut text).with_context(|_| error::Io {
402 message: "reading source",
403 path: path.clone(),
404 })?;
405
406 self.ingest(path, &text)
407 }
408
409 pub fn ingest<P>(&mut self, path: P, src: &str) -> Result<(), Error>
412 where
413 P: Into<PathBuf>,
414 {
415 let nodes = parse_asm(src)?;
416 let partial = self.sources.resolve(path.into(), Scope::independent())?;
417 partial.push(nodes);
418
419 while let Some(source) = self.sources.peek() {
420 let node = match source.nodes.next() {
421 Some(n) => n,
422 None => {
423 self.sources.pop()?;
424 continue;
425 }
426 };
427
428 match node {
429 Node::Op(op) => {
430 self.sources.write(RawOp::Op(op))?;
431 }
432 Node::Raw(raw) => {
433 self.sources.write(RawOp::Raw(raw))?;
434 }
435 Node::Import(path) => {
436 let partial = self.sources.resolve(path, Scope::same())?;
437 let parsed = parse_file(partial.path())?;
438 partial.push(parsed);
439 }
440 Node::Include(path) => {
441 let partial = self.sources.resolve(path, Scope::independent())?;
442 let parsed = parse_file(partial.path())?;
443 partial.push(parsed);
444 }
445 Node::IncludeHex(path) => {
446 let partial = self.sources.resolve(path, Scope::same())?;
447
448 let file =
449 std::fs::read_to_string(partial.path()).with_context(|_| error::Io {
450 message: "reading hex include",
451 path: partial.path().to_owned(),
452 })?;
453
454 let raw = hex::decode(file.trim())
455 .map_err(|e| Box::new(e) as Box<dyn std::error::Error>)
456 .context(error::InvalidHex {
457 path: partial.path().to_owned(),
458 })?;
459
460 partial.push(vec![Node::Raw(raw)]);
461 }
462 }
463 }
464
465 if !self.sources.sources.is_empty() {
466 panic!("extra sources?");
467 }
468
469 Ok(())
470 }
471}
472
473#[cfg(test)]
474mod tests {
475 use assert_matches::assert_matches;
476
477 use crate::asm::Error as AsmError;
478
479 use hex_literal::hex;
480
481 use std::fmt::Display;
482 use std::io::Write;
483
484 use super::*;
485
486 use tempfile::NamedTempFile;
487
488 fn new_file<S: Display>(s: S) -> (NamedTempFile, PathBuf) {
489 let mut f = NamedTempFile::new().unwrap();
490 let root = f.path().parent().unwrap().join("root.asm");
491
492 write!(f, "{}", s).unwrap();
493 (f, root)
494 }
495
496 #[test]
497 fn ingest_import() -> Result<(), Error> {
498 let (f, root) = new_file("push1 42");
499
500 let text = format!(
501 r#"
502 push1 1
503 %import("{}")
504 push1 2
505 "#,
506 f.path().display()
507 );
508
509 let mut output = Vec::new();
510 let mut ingest = Ingest::new(&mut output);
511 ingest.ingest(root, &text)?;
512 assert_eq!(output, hex!("6001602a6002"));
513
514 Ok(())
515 }
516
517 #[test]
518 fn ingest_include() -> Result<(), Error> {
519 let (f, root) = new_file(
520 r#"
521 a:
522 jumpdest
523 pc
524 push1 a
525 jump
526 "#,
527 );
528
529 let text = format!(
530 r#"
531 push1 1
532 %include("{}")
533 push1 2
534 "#,
535 f.path().display()
536 );
537
538 let mut output = Vec::new();
539 let mut ingest = Ingest::new(&mut output);
540 ingest.ingest(root, &text)?;
541 assert_eq!(output, hex!("60015b586000566002"));
542
543 Ok(())
544 }
545
546 #[test]
547 fn ingest_import_twice() {
548 let (f, root) = new_file(
549 r#"
550 a:
551 jumpdest
552 push1 a
553 "#,
554 );
555
556 let text = format!(
557 r#"
558 push1 1
559 %import("{0}")
560 %import("{0}")
561 push1 2
562 "#,
563 f.path().display()
564 );
565
566 let mut output = Vec::new();
567 let mut ingest = Ingest::new(&mut output);
568 let err = ingest.ingest(root, &text).unwrap_err();
569
570 assert_matches!(
571 err,
572 Error::Assemble {
573 source: AsmError::DuplicateLabel { label, ..}
574 } if label == "a"
575 );
576 }
577
578 #[test]
579 fn ingest_include_hex() -> Result<(), Error> {
580 let (f, root) = new_file("deadbeef0102f6");
581
582 let text = format!(
583 r#"
584 push1 1
585 %include_hex("{}")
586 push1 2
587 "#,
588 f.path().display(),
589 );
590
591 let mut output = Vec::new();
592 let mut ingest = Ingest::new(&mut output);
593 ingest.ingest(root, &text)?;
594 assert_eq!(output, hex!("6001deadbeef0102f66002"));
595
596 Ok(())
597 }
598
599 #[test]
600 fn ingest_include_hex_label() -> Result<(), Error> {
601 let (f, root) = new_file("deadbeef0102f6");
602
603 let text = format!(
604 r#"
605 push1 1
606 %include_hex("{}")
607 a:
608 jumpdest
609 push1 a
610 push1 0xff
611 "#,
612 f.path().display(),
613 );
614
615 let mut output = Vec::new();
616 let mut ingest = Ingest::new(&mut output);
617 ingest.ingest(root, &text)?;
618 assert_eq!(output, hex!("6001deadbeef0102f65b600960ff"));
619
620 Ok(())
621 }
622
623 #[test]
624 fn ingest_pending_then_raw() -> Result<(), Error> {
625 let (f, root) = new_file("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa");
626
627 let text = format!(
628 r#"
629 push2 lbl
630 %include_hex("{}")
631 lbl:
632 jumpdest
633 "#,
634 f.path().display(),
635 );
636
637 let mut output = Vec::new();
638 let mut ingest = Ingest::new(&mut output);
639 ingest.ingest(root, &text)?;
640
641 let expected = hex!("61001caaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa5b");
642 assert_eq!(output, expected);
643
644 Ok(())
645 }
646
647 #[test]
648 fn ingest_import_in_import() -> Result<(), Error> {
649 let (end, _) = new_file(
650 r#"
651 end:
652 jumpdest
653 push1 start
654 push1 middle
655 "#,
656 );
657
658 let (middle, root) = new_file(format!(
659 r#"
660 %import("{}")
661 middle:
662 jumpdest
663 push2 start
664 push2 end
665 "#,
666 end.path().display(),
667 ));
668
669 let text = format!(
670 r#"
671 push3 end
672 push3 middle
673 start:
674 jumpdest
675 %import("{}")
676 "#,
677 middle.path().display(),
678 );
679
680 let mut output = Vec::new();
681 let mut ingest = Ingest::new(&mut output);
682 ingest.ingest(root, &text)?;
683
684 let expected = hex!("620000096200000e5b5b6008600e5b610008610009");
685 assert_eq!(output, expected);
686
687 Ok(())
688 }
689
690 #[test]
691 fn ingest_import_in_include() -> Result<(), Error> {
692 let (end, _) = new_file(
693 r#"
694 included:
695 jumpdest
696 push2 backward
697 push2 forward
698 "#,
699 );
700
701 let (middle, root) = new_file(format!(
702 r#"
703 pc
704 push1 backward
705 forward:
706 jumpdest
707 %import("{}")
708 backward:
709 jumpdest
710 push1 forward
711 push1 included
712 "#,
713 end.path().display(),
714 ));
715
716 let text = format!(
717 r#"
718 push3 backward
719 forward:
720 jumpdest
721 %include("{}")
722 backward:
723 jumpdest
724 push3 forward
725 "#,
726 middle.path().display(),
727 );
728
729 let mut output = Vec::new();
730 let mut ingest = Ingest::new(&mut output);
731 ingest.ingest(root, &text)?;
732
733 let expected = hex!("620000155b58600b5b5b61000b6100035b600360045b62000004");
734 assert_eq!(output, expected);
735
736 Ok(())
737 }
738
739 #[test]
740 fn ingest_directory_traversal() {
741 let (f, _) = new_file("pc");
742
743 let text = format!(
744 r#"
745 %include("{}")
746 "#,
747 f.path().display(),
748 );
749
750 let mut output = Vec::new();
751 let mut ingest = Ingest::new(&mut output);
752 let root = std::env::current_exe().unwrap();
753 let err = ingest.ingest(root, &text).unwrap_err();
754
755 assert_matches!(err, Error::DirectoryTraversal { .. });
756 }
757
758 #[test]
759 fn ingest_recursive() {
760 let (mut f, root) = new_file("");
761 let path = f.path().display().to_string();
762 write!(f, r#"%import("{}")"#, path).unwrap();
763
764 let text = format!(
765 r#"
766 %import("{}")
767 "#,
768 path,
769 );
770
771 let mut output = Vec::new();
772 let mut ingest = Ingest::new(&mut output);
773 let err = ingest.ingest(root, &text).unwrap_err();
774
775 assert_matches!(err, Error::RecursionLimit { .. });
776 }
777}