parse_dockerfile/
error.rs

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
150
151
152
153
154
155
156
157
158
// SPDX-License-Identifier: Apache-2.0 OR MIT

use core::{fmt, marker::PhantomData, ops::Range, str};
use std::borrow::Cow;

use super::ParseIter;

pub(crate) type Result<T, E = Error> = core::result::Result<T, E>;

/// An error that occurred during parsing the dockerfile.
// Boxing ErrorInner to keep error type small for performance.
// Using PhantomData to make error type !UnwindSafe & !RefUnwindSafe for forward compatibility.
pub struct Error(Box<ErrorInner>, PhantomData<Box<dyn Send + Sync>>);

impl Error {
    /// Returns the line number at which the error was detected.
    #[must_use]
    pub fn line(&self) -> usize {
        self.0.line
    }
    /// Returns the column number at which the error was detected.
    #[must_use]
    pub fn column(&self) -> usize {
        self.0.column
    }
}

#[derive(Debug)]
struct ErrorInner {
    msg: Cow<'static, str>,
    line: usize,
    column: usize,
}

#[cfg_attr(test, derive(Debug))]
pub(crate) enum ErrorKind {
    Expected(&'static str, /* pos */ usize),
    ExpectedOwned(String, /* pos */ usize),
    AtLeastOneArgument { instruction_start: usize },
    AtLeastTwoArguments { instruction_start: usize },
    ExactlyOneArgument { instruction_start: usize },
    UnknownInstruction { instruction_start: usize },
    InvalidEscape { escape_start: usize },
    DuplicateName { first: Range<usize>, second: Range<usize> },
    NoStages,
    Json { arguments_start: usize },
}

impl ErrorKind {
    #[cold]
    #[inline(never)]
    pub(crate) fn into_error(self, p: &ParseIter<'_>) -> Error {
        let msg = match self {
            Self::Expected(msg, ..) => format!("expected {msg}").into(),
            Self::ExpectedOwned(ref msg, ..) => format!("expected {msg}").into(),
            Self::AtLeastOneArgument { instruction_start: pos }
            | Self::AtLeastTwoArguments { instruction_start: pos }
            | Self::ExactlyOneArgument { instruction_start: pos }
            | Self::UnknownInstruction { instruction_start: pos }
            | Self::DuplicateName { first: Range { start: pos, .. }, .. } => {
                let mut s = &p.text.as_bytes()[pos..];
                let word =
                    super::collect_non_whitespace_unescaped(&mut s, p.text, p.escape_byte).value;
                match self {
                    Self::AtLeastOneArgument { .. } => {
                        format!("{word} instruction requires at least one argument").into()
                    }
                    Self::AtLeastTwoArguments { .. } => {
                        format!("{word} instruction requires at least two arguments").into()
                    }
                    Self::ExactlyOneArgument { .. } => {
                        format!("{word} instruction requires exactly one argument").into()
                    }
                    Self::UnknownInstruction { .. } => {
                        format!("unknown instruction '{word}'").into()
                    }
                    Self::DuplicateName { .. } => format!("duplicate name '{word}'").into(),
                    _ => unreachable!(),
                }
            }
            Self::NoStages => "expected at least one FROM instruction".into(),
            Self::Json { .. } => "invalid JSON".into(),
            Self::InvalidEscape { escape_start } => {
                let mut s = &p.text.as_bytes()[escape_start..];
                super::skip_non_whitespace_no_escape(&mut s);
                let escape = &p.text[escape_start..p.text.len() - s.len()];
                format!("invalid escape '{escape}'").into()
            }
        };
        let (line, column) = match self {
            Self::Expected(_, pos)
            | Self::ExpectedOwned(_, pos)
            | Self::AtLeastOneArgument { instruction_start: pos }
            | Self::AtLeastTwoArguments { instruction_start: pos }
            | Self::ExactlyOneArgument { instruction_start: pos }
            | Self::UnknownInstruction { instruction_start: pos, .. }
            | Self::InvalidEscape { escape_start: pos }
            | Self::DuplicateName { second: Range { start: pos, .. }, .. }
            | Self::Json { arguments_start: pos } => find_location_from_pos(pos, p.text.as_bytes()),
            Self::NoStages => (0, 0),
        };
        Error(Box::new(ErrorInner { msg, line, column }), PhantomData)
    }
}

impl fmt::Debug for Error {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        fmt::Debug::fmt(&self.0, f)
    }
}

impl fmt::Display for Error {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        if self.0.line == 0 || f.alternate() {
            fmt::Display::fmt(&self.0.msg, f)
        } else {
            write!(f, "{} at line {} column {}", self.0.msg, self.0.line, self.0.column)
        }
    }
}

impl std::error::Error for Error {}

#[cold]
fn find_location_from_pos(pos: usize, text: &[u8]) -> (usize, usize) {
    let line = find_line_from_pos(pos, text);
    let column = memrchr(b'\n', text.get(..pos).unwrap_or_default()).unwrap_or(pos) + 1;
    (line, column)
}

#[cold]
fn find_line_from_pos(pos: usize, text: &[u8]) -> usize {
    bytecount(b'\n', text.get(..pos).unwrap_or_default()) + 1
}

#[inline]
const fn memrchr_naive(needle: u8, mut s: &[u8]) -> Option<usize> {
    let start = s;
    while let Some((&b, s_next)) = s.split_last() {
        if b == needle {
            return Some(start.len() - s.len());
        }
        s = s_next;
    }
    None
}
use self::memrchr_naive as memrchr;

#[inline]
const fn bytecount_naive(needle: u8, mut s: &[u8]) -> usize {
    let mut n = 0;
    while let Some((&b, s_next)) = s.split_first() {
        n += (b == needle) as usize;
        s = s_next;
    }
    n
}
use self::bytecount_naive as bytecount;