Repository: tafia/quick-xml
Pull request #689 (2023-11-29 20:00:48)

Fix `buffer_position()` after resuming parsing after `IllFormed` errors
Because since #677 we can continue parsing after `Error::IllFormed` we no longer can change `buffer_position()` so that it points to useful position of error. The best solution would be to add position to the `Error` itself, in form of:
```rust
struct Error {
  kind: ErrorKind, // former Error enum
  offset: usize,
}
```
but that would require massive changes, also in the code that I plan to remove soon during rewrite. To keep changes relative small and do not touch that code I decided to add a new `error_position()` getter which would report position of the error. After rewrite I'll try to implement the best solution.

`reader-error.rs` tests was updated to check `buffer_position()` result after resuming parsing after error. Also a couple of new cases was added, which also allowed me to find a bug in one case.


Patch:
diff --git a/Changelog.md b/Changelog.md
index d8c4bf7..5c51eec 100644
--- a/Changelog.md
+++ b/Changelog.md
@@ -14,6 +14,10 @@ The way to configure parser is changed. Now all configuration is contained in th
 `Config` struct and can be applied at once. When `serde-types` feature is enabled,
 configuration is serializable.
 
+The method of reporting positions of errors has changed - use `error_position()`
+to get an offset of the error position. For `SyntaxError`s the range
+`error_position()..buffer_position()` also will represent a span of error.
+
 ### New Features
 
 - [#513]: Allow to continue parsing after getting new `Error::IllFormed`.
@@ -46,11 +50,14 @@ configuration is serializable.
   - `Error::XmlDeclWithoutVersion` replaced by `IllFormedError::MissingDeclVersion` (in [#684])
   - `Error::EmptyDocType` replaced by `IllFormedError::MissingDoctypeName` (in [#684])
 - [#684]: Changed positions reported for `SyntaxError`s: now they are always points
-  to the start of markup (i. e. to the `<` character) with error.
+  to the start of markup (i. e. to the `<` character) with error. Use `error_position()`
+  for that.
 - [#684]: Now `<??>` parsed as `Event::PI` with empty content instead of raising
   syntax error.
 - [#684]: Now `<?xml?>` parsed as `Event::Decl` instead of `Event::PI`.
 - [#362]: Now default quote level is `QuoteLevel::Partial` when using serde serializer.
+- [#689]: `buffer_position()` now always report the position the parser last seen.
+  To get an error position use `error_position()`.
 
 [#362]: https://github.com/tafia/quick-xml/issues/362
 [#513]: https://github.com/tafia/quick-xml/issues/513
@@ -58,6 +65,7 @@ configuration is serializable.
 [#675]: https://github.com/tafia/quick-xml/pull/675
 [#677]: https://github.com/tafia/quick-xml/pull/677
 [#684]: https://github.com/tafia/quick-xml/pull/684
+[#689]: https://github.com/tafia/quick-xml/pull/689
 
 
 ## 0.31.0 -- 2023-10-22
diff --git a/src/reader/buffered_reader.rs b/src/reader/buffered_reader.rs
index 6f9a56c..84f6587 100644
--- a/src/reader/buffered_reader.rs
+++ b/src/reader/buffered_reader.rs
@@ -105,7 +105,7 @@ macro_rules! impl_buffered_source {
             buf.push(b'!');
             self $(.$reader)? .consume(1);
 
-            let bang_type = BangType::new(self.peek_one() $(.$await)? ?, position)?;
+            let bang_type = BangType::new(self.peek_one() $(.$await)? ?)?;
 
             loop {
                 match self $(.$reader)? .fill_buf() $(.$await)? {
@@ -139,10 +139,7 @@ macro_rules! impl_buffered_source {
                 }
             }
 
-            // <!....EOF
-            //  ^^^^^ - `buf` does not contains `<`, but we want to report error at `<`,
-            //          so we move offset to it (+1 for `<`)
-            *position -= 1;
+            *position += read;
             Err(bang_type.to_err())
         }
 
@@ -186,10 +183,7 @@ macro_rules! impl_buffered_source {
                 };
             }
 
-            // <.....EOF
-            //  ^^^^^ - `buf` does not contains `<`, but we want to report error at `<`,
-            //          so we move offset to it (+1 for `<`)
-            *position -= 1;
+            *position += read;
             Err(Error::Syntax(SyntaxError::UnclosedTag))
         }
 
diff --git a/src/reader/mod.rs b/src/reader/mod.rs
index ba09044..6ccbdf5 100644
--- a/src/reader/mod.rs
+++ b/src/reader/mod.rs
@@ -331,6 +331,7 @@ macro_rules! read_until_close {
     ) => {{
         $self.state.state = ParseState::ClosedTag;
 
+        let start = $self.state.offset;
         match $reader.peek_one() $(.$await)? {
             // `<!` - comment, CDATA or DOCTYPE declaration
             Ok(Some(b'!')) => match $reader
@@ -338,7 +339,13 @@ macro_rules! read_until_close {
                 $(.$await)?
             {
                 Ok((bang_type, bytes)) => $self.state.emit_bang(bang_type, bytes),
-                Err(e) => Err(e),
+                Err(e) => {
+                    // <!....EOF
+                    //  ^^^^^ - `buf` does not contains `<`, but we want to report error at `<`,
+                    //          so we move offset to it (-1 for `<`)
+                    $self.state.last_error_offset = start - 1;
+                    Err(e)
+                }
             },
             // `</` - closing tag
             Ok(Some(b'/')) => match $reader
@@ -346,10 +353,10 @@ macro_rules! read_until_close {
                 $(.$await)?
             {
                 Ok((bytes, true)) => $self.state.emit_end(bytes),
-                Ok((bytes, false)) => {
+                Ok((_, false)) => {
                     // We want to report error at `<`, but offset was increased,
-                    // so return it back (+1 for `<`)
-                    $self.state.offset -= bytes.len() + 1;
+                    // so return it back (-1 for `<`)
+                    $self.state.last_error_offset = start - 1;
                     Err(Error::Syntax(SyntaxError::UnclosedTag))
                 }
                 Err(e) => Err(e),
@@ -360,10 +367,10 @@ macro_rules! read_until_close {
                 $(.$await)?
             {
                 Ok((bytes, true)) => $self.state.emit_question_mark(bytes),
-                Ok((bytes, false)) => {
+                Ok((_, false)) => {
                     // We want to report error at `<`, but offset was increased,
-                    // so return it back (+1 for `<`)
-                    $self.state.offset -= bytes.len() + 1;
+                    // so return it back (-1 for `<`)
+                    $self.state.last_error_offset = start - 1;
                     Err(Error::Syntax(SyntaxError::UnclosedPIOrXmlDecl))
                 }
                 Err(e) => Err(e),
@@ -379,8 +386,8 @@ macro_rules! read_until_close {
             // `<` - syntax error, tag not closed
             Ok(None) => {
                 // We want to report error at `<`, but offset was increased,
-                // so return it back (+1 for `<`)
-                $self.state.offset -= 1;
+                // so return it back (-1 for `<`)
+                $self.state.last_error_offset = start - 1;
                 Err(Error::Syntax(SyntaxError::UnclosedTag))
             }
             Err(e) => Err(e),
@@ -449,7 +456,7 @@ pub type Span = Range<usize>;
 ///   Empty     -- End                   --> ClosedTag
 ///   _ -. Eof .-> Exit
 /// ```
-#[derive(Clone)]
+#[derive(Clone, Debug)]
 enum ParseState {
     /// Initial state in which reader stay after creation. Transition from that
     /// state could produce a `Text`, `Decl`, `Comment` or `Start` event. The next
@@ -490,7 +497,7 @@ enum ParseState {
 ///   BomDetected -- "encoding=..." --> XmlDetected
 /// ```
 #[cfg(feature = "encoding")]
-#[derive(Clone, Copy)]
+#[derive(Clone, Copy, Debug)]
 enum EncodingRef {
     /// Encoding was implicitly assumed to have a specified value. It can be refined
     /// using BOM or by the XML declaration event (`<?xml encoding=... ?>`)
@@ -680,8 +687,6 @@ impl<R> Reader<R> {
     }
 
     /// Gets the current byte position in the input data.
-    ///
-    /// Useful when debugging errors.
     pub fn buffer_position(&self) -> usize {
         // when internal state is OpenedTag, we have actually read until '<',
         // which we don't want to show
@@ -692,6 +697,21 @@ impl<R> Reader<R> {
         }
     }
 
+    /// Gets the last error byte position in the input data. If there is no errors
+    /// yet, returns `0`.
+    ///
+    /// Unlike `buffer_position` it will point to the place where it is rational
+    /// to report error to the end user. For example, all [`SyntaxError`]s are
+    /// reported when the parser sees EOF inside of some kind of markup. The
+    /// `buffer_position()` will point to the last byte of input which is not
+    /// very useful. `error_position()` will point to the start of corresponding
+    /// markup element (i. e. to the `<` character).
+    ///
+    /// This position is always `<= buffer_position()`.
+    pub fn error_position(&self) -> usize {
+        self.state.last_error_offset
+    }
+
     /// Get the decoder, used to decode bytes, read by this reader, to the strings.
     ///
     /// If [`encoding`] feature is enabled, the used encoding may change after
@@ -870,17 +890,12 @@ enum BangType {
 }
 impl BangType {
     #[inline(always)]
-    fn new(byte: Option<u8>, position: &mut usize) -> Result<Self> {
+    fn new(byte: Option<u8>) -> Result<Self> {
         Ok(match byte {
             Some(b'[') => Self::CData,
             Some(b'-') => Self::Comment,
             Some(b'D') | Some(b'd') => Self::DocType,
-            _ => {
-                // <!EOF
-                //  ^ - we want to report error at `<`, so we move offset to it (+1 for `<`)
-                *position -= 1;
-                return Err(Error::Syntax(SyntaxError::InvalidBangMarkup));
-            }
+            _ => return Err(Error::Syntax(SyntaxError::InvalidBangMarkup)),
         })
     }
 
@@ -1148,8 +1163,7 @@ mod test {
                                 x
                             ),
                         }
-                        // We want to report error at `<`
-                        assert_eq!(position, 0);
+                        assert_eq!(position, 1);
                     }
 
                     /// Checks that if CDATA startup sequence was matched, but an end sequence
@@ -1159,7 +1173,7 @@ mod test {
                         let buf = $buf;
                         let mut position = 1;
                         let mut input = b"![CDATA[other content".as_ref();
-                        //                ^= 1
+                        //                ^= 1                 ^= 22
 
                         match $source(&mut input).read_bang_element(buf, &mut position) $(.$await)? {
                             Err(Error::Syntax(SyntaxError::UnclosedCData)) => {}
@@ -1168,8 +1182,7 @@ mod test {
                                 x
                             ),
                         }
-                        // We want to report error at `<`
-                        assert_eq!(position, 0);
+                        assert_eq!(position, 22);
                     }
 
                     /// Checks that CDATA element without content inside parsed successfully
@@ -1248,8 +1261,7 @@ mod test {
                                 x
                             ),
                         }
-                        // We want to report error at `<`
-                        assert_eq!(position, 0);
+                        assert_eq!(position, 1);
                     }
 
                     #[$test]
@@ -1257,7 +1269,7 @@ mod test {
                         let buf = $buf;
                         let mut position = 1;
                         let mut input = b"!->other content".as_ref();
-                        //                ^= 1
+                        //                ^= 1            ^= 17
 
                         match $source(&mut input).read_bang_element(buf, &mut position) $(.$await)? {
                             Err(Error::Syntax(SyntaxError::UnclosedComment)) => {}
@@ -1266,8 +1278,7 @@ mod test {
                                 x
                             ),
                         }
-                        // We want to report error at `<`
-                        assert_eq!(position, 0);
+                        assert_eq!(position, 17);
                     }
 
                     #[$test]
@@ -1275,7 +1286,7 @@ mod test {
                         let buf = $buf;
                         let mut position = 1;
                         let mut input = b"!--other content".as_ref();
-                        //                ^= 1
+                        //                ^= 1            ^= 17
 
                         match $source(&mut input).read_bang_element(buf, &mut position) $(.$await)? {
                             Err(Error::Syntax(SyntaxError::UnclosedComment)) => {}
@@ -1284,8 +1295,7 @@ mod test {
                                 x
                             ),
                         }
-                        // We want to report error at `<`
-                        assert_eq!(position, 0);
+                        assert_eq!(position, 17);
                     }
 
                     #[$test]
@@ -1293,7 +1303,7 @@ mod test {
                         let buf = $buf;
                         let mut position = 1;
                         let mut input = b"!-->other content".as_ref();
-                        //                ^= 1
+                        //                ^= 1             ^= 18
 
                         match $source(&mut input).read_bang_element(buf, &mut position) $(.$await)? {
                             Err(Error::Syntax(SyntaxError::UnclosedComment)) => {}
@@ -1302,8 +1312,7 @@ mod test {
                                 x
                             ),
                         }
-                        // We want to report error at `<`
-                        assert_eq!(position, 0);
+                        assert_eq!(position, 18);
                     }
 
                     #[$test]
@@ -1311,7 +1320,7 @@ mod test {
                         let buf = $buf;
                         let mut position = 1;
                         let mut input = b"!--->other content".as_ref();
-                        //                ^= 1
+                        //                ^= 1              ^= 19
 
                         match $source(&mut input).read_bang_element(buf, &mut position) $(.$await)? {
                             Err(Error::Syntax(SyntaxError::UnclosedComment)) => {}
@@ -1320,8 +1329,7 @@ mod test {
                                 x
                             ),
                         }
-                        // We want to report error at `<`
-                        assert_eq!(position, 0);
+                        assert_eq!(position, 19);
                     }
 
                     #[$test]
@@ -1374,7 +1382,7 @@ mod test {
                             let buf = $buf;
                             let mut position = 1;
                             let mut input = b"!D other content".as_ref();
-                            //                ^= 1
+                            //                ^= 1            ^= 17
 
                             match $source(&mut input).read_bang_element(buf, &mut position) $(.$await)? {
                                 Err(Error::Syntax(SyntaxError::UnclosedDoctype)) => {}
@@ -1383,8 +1391,7 @@ mod test {
                                     x
                                 ),
                             }
-                            // We want to report error at `<`
-                            assert_eq!(position, 0);
+                            assert_eq!(position, 17);
                         }
 
                         #[$test]
@@ -1392,7 +1399,7 @@ mod test {
                             let buf = $buf;
                             let mut position = 1;
                             let mut input = b"!DOCTYPEother content".as_ref();
-                            //                ^= 1
+                            //                ^= 1                 ^= 22
 
                             match $source(&mut input).read_bang_element(buf, &mut position) $(.$await)? {
                                 Err(Error::Syntax(SyntaxError::UnclosedDoctype)) => {}
@@ -1401,8 +1408,7 @@ mod test {
                                     x
                                 ),
                             }
-                            // We want to report error at `<`
-                            assert_eq!(position, 0);
+                            assert_eq!(position, 22);
                         }
 
                         #[$test]
@@ -1428,7 +1434,7 @@ mod test {
                             let buf = $buf;
                             let mut position = 1;
                             let mut input = b"!DOCTYPE other content".as_ref();
-                            //                ^= 1
+                            //                ^= 1                  ^23
 
                             match $source(&mut input).read_bang_element(buf, &mut position) $(.$await)? {
                                 Err(Error::Syntax(SyntaxError::UnclosedDoctype)) => {}
@@ -1437,8 +1443,7 @@ mod test {
                                     x
                                 ),
                             }
-                            // We want to report error at `<`
-                            assert_eq!(position, 0);
+                            assert_eq!(position, 23);
                         }
                     }
 
@@ -1451,7 +1456,7 @@ mod test {
                             let buf = $buf;
                             let mut position = 1;
                             let mut input = b"!d other content".as_ref();
-                            //                ^= 1
+                            //                ^= 1            ^= 17
 
                             match $source(&mut input).read_bang_element(buf, &mut position) $(.$await)? {
                                 Err(Error::Syntax(SyntaxError::UnclosedDoctype)) => {}
@@ -1460,8 +1465,7 @@ mod test {
                                     x
                                 ),
                             }
-                            // We want to report error at `<`
-                            assert_eq!(position, 0);
+                            assert_eq!(position, 17);
                         }
 
                         #[$test]
@@ -1469,7 +1473,7 @@ mod test {
                             let buf = $buf;
                             let mut position = 1;
                             let mut input = b"!doctypeother content".as_ref();
-                            //                ^= 1
+                            //                ^= 1                 ^= 22
 
                             match $source(&mut input).read_bang_element(buf, &mut position) $(.$await)? {
                                 Err(Error::Syntax(SyntaxError::UnclosedDoctype)) => {}
@@ -1478,8 +1482,7 @@ mod test {
                                     x
                                 ),
                             }
-                            // We want to report error at `<`
-                            assert_eq!(position, 0);
+                            assert_eq!(position, 22);
                         }
 
                         #[$test]
@@ -1505,7 +1508,7 @@ mod test {
                             let buf = $buf;
                             let mut position = 1;
                             let mut input = b"!doctype other content".as_ref();
-                            //                ^= 1
+                            //                ^= 1                  ^= 23
 
                             match $source(&mut input).read_bang_element(buf, &mut position) $(.$await)? {
                                 Err(Error::Syntax(SyntaxError::UnclosedDoctype)) => {}
@@ -1514,8 +1517,7 @@ mod test {
                                     x
                                 ),
                             }
-                            // We want to report error at `<`
-                            assert_eq!(position, 0);
+                            assert_eq!(position, 23);
                         }
                     }
                 }
@@ -1542,8 +1544,7 @@ mod test {
                             x
                         ),
                     }
-                    // We want to report error at `<`
-                    assert_eq!(position, 0);
+                    assert_eq!(position, 1);
                 }
 
                 mod open {
@@ -1697,63 +1698,6 @@ mod test {
                 }
             }
 
-            mod issue_344 {
-                use crate::errors::{Error, SyntaxError};
-                use crate::reader::Reader;
-
-                #[$test]
-                $($async)? fn cdata() {
-                    let mut reader = Reader::from_str("![]]>");
-
-                    match reader.$read_until_close($buf) $(.$await)? {
-                        Err(Error::Syntax(SyntaxError::UnclosedCData)) => {}
-                        x => panic!(
-                            "Expected `Err(Syntax(UnclosedCData))`, but got `{:?}`",
-                            x
-                        ),
-                    }
-                }
-
-                #[$test]
-                $($async)? fn comment() {
-                    let mut reader = Reader::from_str("!- -->");
-
-                    match reader.$read_until_close($buf) $(.$await)? {
-                        Err(Error::Syntax(SyntaxError::UnclosedComment)) => {}
-                        x => panic!(
-                            "Expected `Err(Syntax(UnclosedComment)`, but got `{:?}`",
-                            x
-                        ),
-                    }
-                }
-
-                #[$test]
-                $($async)? fn doctype_uppercase() {
-                    let mut reader = Reader::from_str("!D>");
-
-                    match reader.$read_until_close($buf) $(.$await)? {
-                        Err(Error::Syntax(SyntaxError::UnclosedDoctype)) => {}
-                        x => panic!(
-                            "Expected `Err(Syntax(UnclosedDoctype))`, but got `{:?}`",
-                            x
-                        ),
-                    }
-                }
-
-                #[$test]
-                $($async)? fn doctype_lowercase() {
-                    let mut reader = Reader::from_str("!d>");
-
-                    match reader.$read_until_close($buf) $(.$await)? {
-                        Err(Error::Syntax(SyntaxError::UnclosedDoctype)) => {}
-                        x => panic!(
-                            "Expected `Err(Syntax(UnclosedDoctype))`, but got `{:?}`",
-                            x
-                        ),
-                    }
-                }
-            }
-
             /// Ensures, that no empty `Text` events are generated
             mod $read_event {
                 use crate::events::{BytesCData, BytesDecl, BytesEnd, BytesStart, BytesText, Event};
diff --git a/src/reader/slice_reader.rs b/src/reader/slice_reader.rs
index eb3961c..5e807e2 100644
--- a/src/reader/slice_reader.rs
+++ b/src/reader/slice_reader.rs
@@ -286,7 +286,7 @@ impl<'a> XmlSource<'a, ()> for &'a [u8] {
         // start with it.
         debug_assert_eq!(self[0], b'!');
 
-        let bang_type = BangType::new(self[1..].first().copied(), position)?;
+        let bang_type = BangType::new(self[1..].first().copied())?;
 
         if let Some((bytes, i)) = bang_type.parse(&[], self) {
             *position += i;
@@ -294,10 +294,7 @@ impl<'a> XmlSource<'a, ()> for &'a [u8] {
             return Ok((bang_type, bytes));
         }
 
-        // <!....EOF
-        //  ^^^^^ - `self` does not contains `<`, but we want to report error at `<`,
-        //          so we move offset to it (+1 for `<`)
-        *position -= 1;
+        *position += self.len();
         Err(bang_type.to_err())
     }
 
@@ -311,10 +308,7 @@ impl<'a> XmlSource<'a, ()> for &'a [u8] {
             return Ok(bytes);
         }
 
-        // <.....EOF
-        //  ^^^^^ - `self` does not contains `<`, but we want to report error at `<`,
-        //          so we move offset to it (+1 for `<`)
-        *position -= 1;
+        *position += self.len();
         Err(Error::Syntax(SyntaxError::UnclosedTag))
     }
 
diff --git a/src/reader/state.rs b/src/reader/state.rs
index 9ac62c9..d579b76 100644
--- a/src/reader/state.rs
+++ b/src/reader/state.rs
@@ -13,10 +13,15 @@ use memchr;
 /// A struct that holds a current reader state and a parser configuration.
 /// It is independent on a way of reading data: the reader feed data into it and
 /// get back produced [`Event`]s.
-#[derive(Clone)]
+#[derive(Clone, Debug)]
 pub(super) struct ReaderState {
     /// Number of bytes read from the source of data since the reader was created
     pub offset: usize,
+    /// A snapshot of an `offset` of the last error returned. It can be less than
+    /// `offset`, because some errors conveniently report at earlier position,
+    /// and changing `offset` is not possible, because `Error::IllFormed` errors
+    /// are recoverable.
+    pub last_error_offset: usize,
     /// Defines how to process next byte
     pub state: ParseState,
     /// User-defined settings that affect parsing
@@ -95,13 +100,29 @@ impl ReaderState {
                         off += p + 1;
                         // if next byte after `-` is also `-`, return an error
                         if buf[3 + off] == b'-' {
-                            self.offset -= len - 2 - p;
+                            // Explanation of the magic:
+                            //
+                            // - `self.offset`` just after `>`,
+                            // - `buf` contains `!-- con--tent --`
+                            // - `p` is counted from byte after `<!--`
+                            //
+                            // <!-- con--tent -->:
+                            //  ~~~~~~~~~~~~~~~~ : - buf
+                            //   : ===========   : - zone of search (possible values of `p`)
+                            //   : |---p         : - p is counted from | (| is 0)
+                            //   : :   :         ^ - self.offset
+                            //   ^ :   :           - self.offset - len
+                            //     ^   :           - self.offset - len + 2
+                            //         ^           - self.offset - len + 2 + p
+                            self.last_error_offset = self.offset - len + 2 + p;
                             return Err(Error::IllFormed(IllFormedError::DoubleHyphenInComment));
                         }
+                        // Continue search after single `-` (+1 to skip it)
                         haystack = &haystack[p + 1..];
                     }
                 }
                 Ok(Event::Comment(BytesText::wrap(
+                    // Cut of `!--` and `--` from start and end
                     &buf[3..len - 2],
                     self.decoder(),
                 )))
@@ -109,6 +130,7 @@ impl ReaderState {
             BangType::CData if uncased_starts_with(buf, b"![CDATA[") => {
                 debug_assert!(buf.ends_with(b"]]"));
                 Ok(Event::CData(BytesCData::wrap(
+                    // Cut of `![CDATA[` and `]]` from start and end
                     &buf[8..len - 2],
                     self.decoder(),
                 )))
@@ -116,6 +138,7 @@ impl ReaderState {
             BangType::DocType if uncased_starts_with(buf, b"!DOCTYPE") => {
                 match buf[8..].iter().position(|&b| !is_whitespace(b)) {
                     Some(start) => Ok(Event::DocType(BytesText::wrap(
+                        // Cut of `!DOCTYPE` and any number of spaces from start
                         &buf[8 + start..],
                         self.decoder(),
                     ))),
@@ -123,16 +146,16 @@ impl ReaderState {
                         // Because we here, we at least read `<!DOCTYPE>` and offset after `>`.
                         // We want report error at place where name is expected - this is just
                         // before `>`
-                        self.offset -= 1;
+                        self.last_error_offset = self.offset - 1;
                         return Err(Error::IllFormed(IllFormedError::MissingDoctypeName));
                     }
                 }
             }
             _ => {
-                // <!....
-                //  ^^^^^ - `buf` does not contains `<`, but we want to report error at `<`,
-                //          so we move offset to it (+1 for `<`)
-                self.offset -= 1;
+                // <!....>
+                //  ^^^^^ - `buf` does not contain `<` and `>`, but `self.offset` is after `>`.
+                // ^------- We report error at that position, so we need to subtract 2 and buf len
+                self.last_error_offset = self.offset - len - 2;
                 Err(bang_type.to_err())
             }
         }
@@ -168,8 +191,8 @@ impl ReaderState {
                         self.opened_buffer.truncate(start);
 
                         // Report error at start of the end tag at `<` character
-                        // +2 for `<` and `>`
-                        self.offset -= buf.len() + 2;
+                        // -2 for `<` and `>`
+                        self.last_error_offset = self.offset - buf.len() - 2;
                         return Err(Error::IllFormed(IllFormedError::MismatchedEndTag {
                             expected,
                             found: decoder.decode(name).unwrap_or_default().into_owned(),
@@ -181,8 +204,8 @@ impl ReaderState {
             }
             None => {
                 // Report error at start of the end tag at `<` character
-                // +2 for `<` and `>`
-                self.offset -= buf.len() + 2;
+                // -2 for `<` and `>`
+                self.last_error_offset = self.offset - buf.len() - 2;
                 return Err(Error::IllFormed(IllFormedError::UnmatchedEndTag(
                     decoder.decode(name).unwrap_or_default().into_owned(),
                 )));
@@ -204,6 +227,7 @@ impl ReaderState {
         // We accept at least <??>
         //                     ~~ - len = 2
         if len > 1 && buf[len - 1] == b'?' {
+            // Cut of `?` and `?` from start and end
             let content = &buf[1..len - 1];
             let len = content.len();
 
@@ -225,8 +249,8 @@ impl ReaderState {
         } else {
             // <?....EOF
             //  ^^^^^ - `buf` does not contains `<`, but we want to report error at `<`,
-            //          so we move offset to it (+2 for `<`and `>`)
-            self.offset -= len + 2;
+            //          so we move offset to it (-2 for `<` and `>`)
+            self.last_error_offset = self.offset - len - 2;
             Err(Error::Syntax(SyntaxError::UnclosedPIOrXmlDecl))
         }
     }
@@ -294,6 +318,7 @@ impl Default for ReaderState {
     fn default() -> Self {
         Self {
             offset: 0,
+            last_error_offset: 0,
             state: ParseState::Init,
             config: Config::default(),
             opened_buffer: Vec::new(),


Test patch:
diff --git a/tests/issues.rs b/tests/issues.rs
index 7f93025..261a8bd 100644
--- a/tests/issues.rs
+++ b/tests/issues.rs
@@ -73,6 +73,15 @@ fn issue299() -> Result<(), Error> {
     Ok(())
 }
 
+/// Regression test for https://github.com/tafia/quick-xml/issues/344
+#[test]
+fn issue344() {
+    let mut reader = Reader::from_str("<!D>");
+    let mut buf = Vec::new();
+    let _ = reader.read_event_into(&mut buf);
+    let _ = reader.read_event_into(&mut buf);
+}
+
 /// Regression test for https://github.com/tafia/quick-xml/issues/360
 #[test]
 fn issue360() {
diff --git a/tests/reader-errors.rs b/tests/reader-errors.rs
index 698401f..0ca306f 100644
--- a/tests/reader-errors.rs
+++ b/tests/reader-errors.rs
@@ -8,52 +8,73 @@ macro_rules! ok {
     ($test:ident($xml:literal) => $event:expr) => {
         mod $test {
             use super::*;
-            use pretty_assertions::assert_eq;
 
-            #[test]
-            fn borrowed() {
-                let mut reader = Reader::from_str($xml);
-                reader.config_mut().enable_all_checks(true);
-                assert_eq!(reader.read_event().unwrap(), $event);
+            mod reader {
+                use super::*;
+                use pretty_assertions::assert_eq;
 
-                let mut reader = NsReader::from_str($xml);
-                reader.config_mut().enable_all_checks(true);
-                assert_eq!(reader.read_resolved_event().unwrap().1, $event);
-            }
+                #[test]
+                fn borrowed() {
+                    let mut reader = Reader::from_str($xml);
+                    reader.config_mut().enable_all_checks(true);
+                    assert_eq!(reader.read_event().unwrap(), $event);
+                }
 
-            #[test]
-            fn buffered() {
-                let mut buf = Vec::new();
-                let mut reader = Reader::from_str($xml);
-                reader.config_mut().enable_all_checks(true);
-                assert_eq!(reader.read_event_into(&mut buf).unwrap(), $event);
+                #[test]
+                fn buffered() {
+                    let mut buf = Vec::new();
+                    let mut reader = Reader::from_str($xml);
+                    reader.config_mut().enable_all_checks(true);
+                    assert_eq!(reader.read_event_into(&mut buf).unwrap(), $event);
+                }
 
-                let mut reader = NsReader::from_str($xml);
-                reader.config_mut().enable_all_checks(true);
-                assert_eq!(reader.read_resolved_event_into(&mut buf).unwrap().1, $event);
+                #[cfg(feature = "async-tokio")]
+                #[tokio::test]
+                async fn async_tokio() {
+                    let mut buf = Vec::new();
+                    let mut reader = Reader::from_str($xml);
+                    reader.config_mut().enable_all_checks(true);
+                    assert_eq!(
+                        reader.read_event_into_async(&mut buf).await.unwrap(),
+                        $event
+                    );
+                }
             }
 
-            #[cfg(feature = "async-tokio")]
-            #[tokio::test]
-            async fn async_tokio() {
-                let mut buf = Vec::new();
-                let mut reader = Reader::from_str($xml);
-                reader.config_mut().enable_all_checks(true);
-                assert_eq!(
-                    reader.read_event_into_async(&mut buf).await.unwrap(),
-                    $event
-                );
+            mod ns_reader {
+                use super::*;
+                use pretty_assertions::assert_eq;
 
-                let mut reader = NsReader::from_str($xml);
-                reader.config_mut().enable_all_checks(true);
-                assert_eq!(
-                    reader
-                        .read_resolved_event_into_async(&mut buf)
-                        .await
-                        .unwrap()
-                        .1,
-                    $event
-                );
+                #[test]
+                fn borrowed() {
+                    let mut reader = NsReader::from_str($xml);
+                    reader.config_mut().enable_all_checks(true);
+                    assert_eq!(reader.read_resolved_event().unwrap().1, $event);
+                }
+
+                #[test]
+                fn buffered() {
+                    let mut buf = Vec::new();
+                    let mut reader = NsReader::from_str($xml);
+                    reader.config_mut().enable_all_checks(true);
+                    assert_eq!(reader.read_resolved_event_into(&mut buf).unwrap().1, $event);
+                }
+
+                #[cfg(feature = "async-tokio")]
+                #[tokio::test]
+                async fn async_tokio() {
+                    let mut buf = Vec::new();
+                    let mut reader = NsReader::from_str($xml);
+                    reader.config_mut().enable_all_checks(true);
+                    assert_eq!(
+                        reader
+                            .read_resolved_event_into_async(&mut buf)
+                            .await
+                            .unwrap()
+                            .1,
+                        $event
+                    );
+                }
             }
         }
     };
@@ -63,119 +84,143 @@ mod syntax {
     use super::*;
 
     macro_rules! err {
-        ($test:ident($xml:literal) => $cause:expr) => {
+        ($test:ident($xml:literal) => $pos:expr, $cause:expr) => {
             mod $test {
                 use super::*;
-                use pretty_assertions::assert_eq;
 
-                #[test]
-                fn borrowed() {
-                    let mut reader = Reader::from_str($xml);
-                    match reader.read_event() {
-                        Err(Error::Syntax(cause)) => {
-                            assert_eq!(cause, $cause);
-                            assert_eq!(reader.buffer_position(), 0);
+                mod reader {
+                    use super::*;
+                    use pretty_assertions::assert_eq;
+
+                    #[test]
+                    fn borrowed() {
+                        let mut reader = Reader::from_str($xml);
+                        match reader.read_event() {
+                            Err(Error::Syntax(cause)) => assert_eq!(
+                                (cause, reader.error_position(), reader.buffer_position()),
+                                ($cause, 0, $pos),
+                            ),
+                            x => panic!("Expected `Err(Syntax(_))`, but got {:?}", x),
                         }
-                        x => panic!("Expected `Err(Syntax(_))`, but got {:?}", x),
+                        assert_eq!(
+                            reader
+                                .read_event()
+                                .expect("parser should return `Event::Eof` after error"),
+                            Event::Eof
+                        );
                     }
-                    assert_eq!(
-                        reader
-                            .read_event()
-                            .expect("parser should return `Event::Eof` after error"),
-                        Event::Eof
-                    );
 
-                    let mut reader = NsReader::from_str($xml);
-                    match reader.read_resolved_event() {
-                        Err(Error::Syntax(cause)) => {
-                            assert_eq!(cause, $cause);
-                            assert_eq!(reader.buffer_position(), 0);
+                    #[test]
+                    fn buffered() {
+                        let mut buf = Vec::new();
+                        let mut reader = Reader::from_str($xml);
+                        match reader.read_event_into(&mut buf) {
+                            Err(Error::Syntax(cause)) => assert_eq!(
+                                (cause, reader.error_position(), reader.buffer_position()),
+                                ($cause, 0, $pos),
+                            ),
+                            x => panic!("Expected `Err(Syntax(_))`, but got {:?}", x),
                         }
-                        x => panic!("Expected `Err(Syntax(_))`, but got {:?}", x),
+                        assert_eq!(
+                            reader
+                                .read_event_into(&mut buf)
+                                .expect("parser should return `Event::Eof` after error"),
+                            Event::Eof
+                        );
                     }
-                    assert_eq!(
-                        reader
-                            .read_resolved_event()
-                            .expect("parser should return `Event::Eof` after error")
-                            .1,
-                        Event::Eof
-                    );
-                }
 
-                #[test]
-                fn buffered() {
-                    let mut buf = Vec::new();
-                    let mut reader = Reader::from_str($xml);
-                    match reader.read_event_into(&mut buf) {
-                        Err(Error::Syntax(cause)) => {
-                            assert_eq!(cause, $cause);
-                            assert_eq!(reader.buffer_position(), 0);
+                    #[cfg(feature = "async-tokio")]
+                    #[tokio::test]
+                    async fn async_tokio() {
+                        let mut buf = Vec::new();
+                        let mut reader = Reader::from_str($xml);
+                        match reader.read_event_into_async(&mut buf).await {
+                            Err(Error::Syntax(cause)) => assert_eq!(
+                                (cause, reader.error_position(), reader.buffer_position()),
+                                ($cause, 0, $pos),
+                            ),
+                            x => panic!("Expected `Err(Syntax(_))`, but got {:?}", x),
                         }
-                        x => panic!("Expected `Err(Syntax(_))`, but got {:?}", x),
+                        assert_eq!(
+                            reader
+                                .read_event_into_async(&mut buf)
+                                .await
+                                .expect("parser should return `Event::Eof` after error"),
+                            Event::Eof
+                        );
                     }
-                    assert_eq!(
-                        reader
-                            .read_event_into(&mut buf)
-                            .expect("parser should return `Event::Eof` after error"),
-                        Event::Eof
-                    );
+                }
 
-                    let mut reader = NsReader::from_str($xml);
-                    match reader.read_resolved_event_into(&mut buf) {
-                        Err(Error::Syntax(cause)) => {
-                            assert_eq!(cause, $cause);
-                            assert_eq!(reader.buffer_position(), 0);
+                mod ns_reader {
+                    use super::*;
+                    use pretty_assertions::assert_eq;
+
+                    #[test]
+                    fn borrowed() {
+                        let mut reader = NsReader::from_str($xml);
+                        match reader.read_resolved_event() {
+                            Err(Error::Syntax(cause)) => assert_eq!(
+                                (cause, reader.error_position(), reader.buffer_position()),
+                                ($cause, 0, $pos),
+                            ),
+                            x => panic!("Expected `Err(Syntax(_))`, but got {:?}", x),
                         }
-                        x => panic!("Expected `Err(Syntax(_))`, but got {:?}", x),
+                        assert_eq!(
+                            reader
+                                .read_resolved_event()
+                                .expect("parser should return `Event::Eof` after error")
+                                .1,
+                            Event::Eof
+                        );
                     }
-                    assert_eq!(
-                        reader
-                            .read_resolved_event_into(&mut buf)
-                            .expect("parser should return `Event::Eof` after error")
-                            .1,
-                        Event::Eof
-                    );
-                }
 
-                #[cfg(feature = "async-tokio")]
-                #[tokio::test]
-                async fn async_tokio() {
-                    let mut buf = Vec::new();
-                    let mut reader = Reader::from_str($xml);
-                    match reader.read_event_into_async(&mut buf).await {
-                        Err(Error::Syntax(cause)) => {
-                            assert_eq!(cause, $cause);
-                            assert_eq!(reader.buffer_position(), 0);
+                    #[test]
+                    fn buffered() {
+                        let mut buf = Vec::new();
+                        let mut reader = NsReader::from_str($xml);
+                        match reader.read_resolved_event_into(&mut buf) {
+                            Err(Error::Syntax(cause)) => assert_eq!(
+                                (cause, reader.error_position(), reader.buffer_position()),
+                                ($cause, 0, $pos),
+                            ),
+                            x => panic!("Expected `Err(Syntax(_))`, but got {:?}", x),
                         }
-                        x => panic!("Expected `Err(Syntax(_))`, but got {:?}", x),
+                        assert_eq!(
+                            reader
+                                .read_resolved_event_into(&mut buf)
+                                .expect("parser should return `Event::Eof` after error")
+                                .1,
+                            Event::Eof
+                        );
                     }
-                    assert_eq!(
-                        reader
-                            .read_event_into_async(&mut buf)
-                            .await
-                            .expect("parser should return `Event::Eof` after error"),
-                        Event::Eof
-                    );
 
-                    let mut reader = NsReader::from_str($xml);
-                    match reader.read_resolved_event_into_async(&mut buf).await {
-                        Err(Error::Syntax(cause)) => {
-                            assert_eq!(cause, $cause);
-                            assert_eq!(reader.buffer_position(), 0);
+                    #[cfg(feature = "async-tokio")]
+                    #[tokio::test]
+                    async fn async_tokio() {
+                        let mut buf = Vec::new();
+                        let mut reader = NsReader::from_str($xml);
+                        match reader.read_resolved_event_into_async(&mut buf).await {
+                            Err(Error::Syntax(cause)) => assert_eq!(
+                                (cause, reader.error_position(), reader.buffer_position()),
+                                ($cause, 0, $pos),
+                            ),
+                            x => panic!("Expected `Err(Syntax(_))`, but got {:?}", x),
                         }
-                        x => panic!("Expected `Err(Syntax(_))`, but got {:?}", x),
+                        assert_eq!(
+                            reader
+                                .read_resolved_event_into_async(&mut buf)
+                                .await
+                                .expect("parser should return `Event::Eof` after error")
+                                .1,
+                            Event::Eof
+                        );
                     }
-                    assert_eq!(
-                        reader
-                            .read_resolved_event_into_async(&mut buf)
-                            .await
-                            .expect("parser should return `Event::Eof` after error")
-                            .1,
-                        Event::Eof
-                    );
                 }
             }
         };
+        ($test:ident($xml:literal) => $cause:expr) => {
+            err!($test($xml) => $xml.len(), $cause);
+        };
     }
 
     mod tag {
@@ -188,6 +233,7 @@ mod syntax {
         err!(unclosed5("<\t") => SyntaxError::UnclosedTag);
         err!(unclosed6("<\r") => SyntaxError::UnclosedTag);
         err!(unclosed7("<\n") => SyntaxError::UnclosedTag);
+        err!(unclosed8("< \t\r\nx") => SyntaxError::UnclosedTag);
 
         /// Closed tags can be tested only in pair with open tags, because otherwise
         /// `IllFormedError::UnmatchedEndTag` will be raised
@@ -236,20 +282,26 @@ mod syntax {
         }
     }
 
-    err!(unclosed_bang1("<!")  => SyntaxError::InvalidBangMarkup);
-    err!(unclosed_bang2("<!>") => SyntaxError::InvalidBangMarkup);
+    // Incorrect after-bang symbol is detected early, so buffer_position() stay at `!`
+    err!(unclosed_bang1("<!")   => 1, SyntaxError::InvalidBangMarkup);
+    err!(unclosed_bang2("<!>")  => 1, SyntaxError::InvalidBangMarkup);
+    err!(unclosed_bang3("<!a")  => 1, SyntaxError::InvalidBangMarkup);
+    err!(unclosed_bang4("<!a>") => 1, SyntaxError::InvalidBangMarkup);
 
     /// https://www.w3.org/TR/xml11/#NT-Comment
     mod comment {
         use super::*;
 
-        err!(unclosed1("<!-")    => SyntaxError::UnclosedComment);
-        err!(unclosed2("<!--")   => SyntaxError::UnclosedComment);
-        err!(unclosed3("<!->")   => SyntaxError::UnclosedComment);
-        err!(unclosed4("<!---")  => SyntaxError::UnclosedComment);
-        err!(unclosed5("<!-->")  => SyntaxError::UnclosedComment);
-        err!(unclosed6("<!----") => SyntaxError::UnclosedComment);
-        err!(unclosed7("<!--->") => SyntaxError::UnclosedComment);
+        err!(unclosed01("<!-")    => SyntaxError::UnclosedComment);
+        err!(unclosed02("<!--")   => SyntaxError::UnclosedComment);
+        err!(unclosed03("<!->")   => SyntaxError::UnclosedComment);
+        err!(unclosed04("<!-a")   => SyntaxError::UnclosedComment);
+        err!(unclosed05("<!---")  => SyntaxError::UnclosedComment);
+        err!(unclosed06("<!-->")  => SyntaxError::UnclosedComment);
+        err!(unclosed07("<!--b")  => SyntaxError::UnclosedComment);
+        err!(unclosed08("<!----") => SyntaxError::UnclosedComment);
+        err!(unclosed09("<!--->") => SyntaxError::UnclosedComment);
+        err!(unclosed10("<!---c") => SyntaxError::UnclosedComment);
 
         ok!(normal("<!---->") => Event::Comment(BytesText::new("")));
     }
@@ -258,15 +310,31 @@ mod syntax {
     mod cdata {
         use super::*;
 
-        err!(unclosed1("<![")         => SyntaxError::UnclosedCData);
-        err!(unclosed2("<![C")        => SyntaxError::UnclosedCData);
-        err!(unclosed3("<![CD")       => SyntaxError::UnclosedCData);
-        err!(unclosed4("<![CDA")      => SyntaxError::UnclosedCData);
-        err!(unclosed5("<![CDAT")     => SyntaxError::UnclosedCData);
-        err!(unclosed6("<![CDATA")    => SyntaxError::UnclosedCData);
-        err!(unclosed7("<![CDATA[")   => SyntaxError::UnclosedCData);
-        err!(unclosed8("<![CDATA[]")  => SyntaxError::UnclosedCData);
-        err!(unclosed9("<![CDATA[]]") => SyntaxError::UnclosedCData);
+        err!(unclosed01("<![")         => SyntaxError::UnclosedCData);
+        err!(unclosed02("<![C")        => SyntaxError::UnclosedCData);
+        err!(unclosed03("<![a")        => SyntaxError::UnclosedCData);
+        err!(unclosed04("<![>")        => SyntaxError::UnclosedCData);
+        err!(unclosed05("<![CD")       => SyntaxError::UnclosedCData);
+        err!(unclosed06("<![Cb")       => SyntaxError::UnclosedCData);
+        err!(unclosed07("<![C>")       => SyntaxError::UnclosedCData);
+        err!(unclosed08("<![CDA")      => SyntaxError::UnclosedCData);
+        err!(unclosed09("<![CDc")      => SyntaxError::UnclosedCData);
+        err!(unclosed10("<![CD>")      => SyntaxError::UnclosedCData);
+        err!(unclosed11("<![CDAT")     => SyntaxError::UnclosedCData);
+        err!(unclosed12("<![CDAd")     => SyntaxError::UnclosedCData);
+        err!(unclosed13("<![CDA>")     => SyntaxError::UnclosedCData);
+        err!(unclosed14("<![CDATA")    => SyntaxError::UnclosedCData);
+        err!(unclosed15("<![CDATe")    => SyntaxError::UnclosedCData);
+        err!(unclosed16("<![CDAT>")    => SyntaxError::UnclosedCData);
+        err!(unclosed17("<![CDATA[")   => SyntaxError::UnclosedCData);
+        err!(unclosed18("<![CDATAf")   => SyntaxError::UnclosedCData);
+        err!(unclosed19("<![CDATA>")   => SyntaxError::UnclosedCData);
+        err!(unclosed20("<![CDATA[]")  => SyntaxError::UnclosedCData);
+        err!(unclosed21("<![CDATA[g")  => SyntaxError::UnclosedCData);
+        err!(unclosed22("<![CDATA[>")  => SyntaxError::UnclosedCData);
+        err!(unclosed23("<![CDATA[]]") => SyntaxError::UnclosedCData);
+        err!(unclosed24("<![CDATA[]h") => SyntaxError::UnclosedCData);
+        err!(unclosed25("<![CDATA[]>") => SyntaxError::UnclosedCData);
 
         ok!(normal("<![CDATA[]]>") => Event::CData(BytesCData::new("")));
     }
@@ -277,15 +345,29 @@ mod syntax {
     mod doctype {
         use super::*;
 
-        err!(unclosed1("<!D")         => SyntaxError::UnclosedDoctype);
-        err!(unclosed2("<!DO")        => SyntaxError::UnclosedDoctype);
-        err!(unclosed3("<!DOC")       => SyntaxError::UnclosedDoctype);
-        err!(unclosed4("<!DOCT")      => SyntaxError::UnclosedDoctype);
-        err!(unclosed5("<!DOCTY")     => SyntaxError::UnclosedDoctype);
-        err!(unclosed6("<!DOCTYP")    => SyntaxError::UnclosedDoctype);
-        err!(unclosed7("<!DOCTYPE")   => SyntaxError::UnclosedDoctype);
-        err!(unclosed8("<!DOCTYPE ")  => SyntaxError::UnclosedDoctype);
-        err!(unclosed9("<!DOCTYPE e") => SyntaxError::UnclosedDoctype);
+        err!(unclosed01("<!D")         => SyntaxError::UnclosedDoctype);
+        err!(unclosed02("<!DO")        => SyntaxError::UnclosedDoctype);
+        err!(unclosed03("<!Da")        => SyntaxError::UnclosedDoctype);
+        err!(unclosed04("<!D>")        => SyntaxError::UnclosedDoctype);
+        err!(unclosed05("<!DOC")       => SyntaxError::UnclosedDoctype);
+        err!(unclosed06("<!DOb")       => SyntaxError::UnclosedDoctype);
+        err!(unclosed07("<!DO>")       => SyntaxError::UnclosedDoctype);
+        err!(unclosed08("<!DOCT")      => SyntaxError::UnclosedDoctype);
+        err!(unclosed09("<!DOCc")      => SyntaxError::UnclosedDoctype);
+        err!(unclosed10("<!DOC>")      => SyntaxError::UnclosedDoctype);
+        err!(unclosed11("<!DOCTY")     => SyntaxError::UnclosedDoctype);
+        err!(unclosed12("<!DOCTd")     => SyntaxError::UnclosedDoctype);
+        err!(unclosed13("<!DOCT>")     => SyntaxError::UnclosedDoctype);
+        err!(unclosed14("<!DOCTYP")    => SyntaxError::UnclosedDoctype);
+        err!(unclosed15("<!DOCTYe")    => SyntaxError::UnclosedDoctype);
+        err!(unclosed16("<!DOCTY>")    => SyntaxError::UnclosedDoctype);
+        err!(unclosed17("<!DOCTYPE")   => SyntaxError::UnclosedDoctype);
+        err!(unclosed18("<!DOCTYPf")   => SyntaxError::UnclosedDoctype);
+        err!(unclosed19("<!DOCTYP>")   => SyntaxError::UnclosedDoctype);
+        err!(unclosed20("<!DOCTYPE ")  => SyntaxError::UnclosedDoctype);
+        err!(unclosed21("<!DOCTYPEg")  => SyntaxError::UnclosedDoctype);
+        // <!DOCTYPE> results in IllFormed(MissingDoctypeName), checked below
+        err!(unclosed22("<!DOCTYPE e") => SyntaxError::UnclosedDoctype);
 
         // According to the grammar, XML declaration MUST contain at least one space
         // and an element name, but we do not consider this as a _syntax_ error.
@@ -340,124 +422,181 @@ mod ill_formed {
         ($test:ident($xml:literal) => $pos:literal : $cause:expr) => {
             mod $test {
                 use super::*;
-                use pretty_assertions::assert_eq;
 
-                #[test]
-                fn borrowed() {
-                    let mut reader = Reader::from_str(concat!($xml, "<x/>"));
-                    reader.config_mut().enable_all_checks(true);
-                    match reader.read_event() {
-                        Err(Error::IllFormed(cause)) => {
-                            assert_eq!(cause, $cause);
-                            assert_eq!(reader.buffer_position(), $pos);
+                mod reader {
+                    use super::*;
+                    use pretty_assertions::assert_eq;
+
+                    #[test]
+                    fn borrowed() {
+                        let xml = concat!($xml, "<x/>");
+                        let mut reader = Reader::from_str(xml);
+                        reader.config_mut().enable_all_checks(true);
+                        match reader.read_event() {
+                            Err(Error::IllFormed(cause)) => assert_eq!(
+                                (cause, reader.error_position(), reader.buffer_position()),
+                                ($cause, $pos, $xml.len()),
+                            ),
+                            x => panic!("Expected `Err(IllFormed(_))`, but got {:?}", x),
                         }
-                        x => panic!("Expected `Err(IllFormed(_))`, but got {:?}", x),
+                        assert_eq!(
+                            reader.read_event().expect(
+                                "parsing should be possible to continue after `Error::IllFormed`"
+                            ),
+                            Event::Empty(BytesStart::new("x"))
+                        );
+                        assert_eq!(
+                            reader.buffer_position(),
+                            xml.len(),
+                            ".buffer_position() is incorrect in the end"
+                        );
                     }
-                    assert_eq!(
-                        reader.read_event().expect(
-                            "parsing should be possible to continue after `Error::IllFormed`"
-                        ),
-                        Event::Empty(BytesStart::new("x"))
-                    );
 
-                    let mut reader = NsReader::from_str(concat!($xml, "<x/>"));
-                    reader.config_mut().enable_all_checks(true);
-                    match reader.read_resolved_event() {
-                        Err(Error::IllFormed(cause)) => {
-                            assert_eq!(cause, $cause);
-                            assert_eq!(reader.buffer_position(), $pos);
+                    #[test]
+                    fn buffered() {
+                        let xml = concat!($xml, "<x/>");
+                        let mut buf = Vec::new();
+                        let mut reader = Reader::from_str(xml);
+                        reader.config_mut().enable_all_checks(true);
+                        match reader.read_event_into(&mut buf) {
+                            Err(Error::IllFormed(cause)) => assert_eq!(
+                                (cause, reader.error_position(), reader.buffer_position()),
+                                ($cause, $pos, $xml.len()),
+                            ),
+                            x => panic!("Expected `Err(IllFormed(_))`, but got {:?}", x),
                         }
-                        x => panic!("Expected `Err(IllFormed(_))`, but got {:?}", x),
-                    }
-                    assert_eq!(
-                        reader
-                            .read_resolved_event()
-                            .expect(
+                        assert_eq!(
+                            reader.read_event_into(&mut buf).expect(
                                 "parsing should be possible to continue after `Error::IllFormed`"
-                            )
-                            .1,
-                        Event::Empty(BytesStart::new("x"))
-                    );
-                }
+                            ),
+                            Event::Empty(BytesStart::new("x"))
+                        );
+                        assert_eq!(
+                            reader.buffer_position(),
+                            xml.len(),
+                            ".buffer_position() is incorrect in the end"
+                        );
+                    }
 
-                #[test]
-                fn buffered() {
-                    let mut buf = Vec::new();
-                    let mut reader = Reader::from_str(concat!($xml, "<x/>"));
-                    reader.config_mut().enable_all_checks(true);
-                    match reader.read_event_into(&mut buf) {
-                        Err(Error::IllFormed(cause)) => {
-                            assert_eq!(cause, $cause);
-                            assert_eq!(reader.buffer_position(), $pos);
+                    #[cfg(feature = "async-tokio")]
+                    #[tokio::test]
+                    async fn async_tokio() {
+                        let xml = concat!($xml, "<x/>");
+                        let mut buf = Vec::new();
+                        let mut reader = Reader::from_str(xml);
+                        reader.config_mut().enable_all_checks(true);
+                        match reader.read_event_into_async(&mut buf).await {
+                            Err(Error::IllFormed(cause)) => assert_eq!(
+                                (cause, reader.error_position(), reader.buffer_position()),
+                                ($cause, $pos, $xml.len()),
+                            ),
+                            x => panic!("Expected `Err(IllFormed(_))`, but got {:?}", x),
                         }
-                        x => panic!("Expected `Err(IllFormed(_))`, but got {:?}", x),
+                        assert_eq!(
+                            reader.read_event_into_async(&mut buf).await.expect(
+                                "parsing should be possible to continue after `Error::IllFormed`"
+                            ),
+                            Event::Empty(BytesStart::new("x"))
+                        );
+                        assert_eq!(
+                            reader.buffer_position(),
+                            xml.len(),
+                            ".buffer_position() is incorrect in the end"
+                        );
                     }
-                    assert_eq!(
-                        reader.read_event_into(&mut buf).expect(
-                            "parsing should be possible to continue after `Error::IllFormed`"
-                        ),
-                        Event::Empty(BytesStart::new("x"))
-                    );
+                }
 
-                    let mut reader = NsReader::from_str(concat!($xml, "<x/>"));
-                    reader.config_mut().enable_all_checks(true);
-                    match reader.read_resolved_event_into(&mut buf) {
-                        Err(Error::IllFormed(cause)) => {
-                            assert_eq!(cause, $cause);
-                            assert_eq!(reader.buffer_position(), $pos);
+                mod ns_reader {
+                    use super::*;
+                    use pretty_assertions::assert_eq;
+
+                    #[test]
+                    fn borrowed() {
+                        let xml = concat!($xml, "<x/>");
+                        let mut reader = NsReader::from_str(xml);
+                        reader.config_mut().enable_all_checks(true);
+                        match reader.read_resolved_event() {
+                            Err(Error::IllFormed(cause)) => assert_eq!(
+                                (cause, reader.error_position(), reader.buffer_position()),
+                                ($cause, $pos, $xml.len()),
+                            ),
+                            x => panic!("Expected `Err(IllFormed(_))`, but got {:?}", x),
                         }
-                        x => panic!("Expected `Err(IllFormed(_))`, but got {:?}", x),
+                        assert_eq!(
+                            reader
+                                .read_resolved_event()
+                                .expect(
+                                    "parsing should be possible to continue after `Error::IllFormed`"
+                                )
+                                .1,
+                            Event::Empty(BytesStart::new("x"))
+                        );
+                        assert_eq!(
+                            reader.buffer_position(),
+                            xml.len(),
+                            ".buffer_position() is incorrect in the end"
+                        );
                     }
-                    assert_eq!(
-                        reader
-                            .read_resolved_event_into(&mut buf)
-                            .expect(
-                                "parsing should be possible to continue after `Error::IllFormed`"
-                            )
-                            .1,
-                        Event::Empty(BytesStart::new("x"))
-                    );
-                }
 
-                #[cfg(feature = "async-tokio")]
-                #[tokio::test]
-                async fn async_tokio() {
-                    let mut buf = Vec::new();
-                    let mut reader = Reader::from_str(concat!($xml, "<x/>"));
-                    reader.config_mut().enable_all_checks(true);
-                    match reader.read_event_into_async(&mut buf).await {
-                        Err(Error::IllFormed(cause)) => {
-                            assert_eq!(cause, $cause);
-                            assert_eq!(reader.buffer_position(), $pos);
+                    #[test]
+                    fn buffered() {
+                        let xml = concat!($xml, "<x/>");
+                        let mut buf = Vec::new();
+                        let mut reader = NsReader::from_str(xml);
+                        reader.config_mut().enable_all_checks(true);
+                        match reader.read_resolved_event_into(&mut buf) {
+                            Err(Error::IllFormed(cause)) => assert_eq!(
+                                (cause, reader.error_position(), reader.buffer_position()),
+                                ($cause, $pos, $xml.len()),
+                            ),
+                            x => panic!("Expected `Err(IllFormed(_))`, but got {:?}", x),
                         }
-                        x => panic!("Expected `Err(IllFormed(_))`, but got {:?}", x),
+                        assert_eq!(
+                            reader
+                                .read_resolved_event_into(&mut buf)
+                                .expect(
+                                    "parsing should be possible to continue after `Error::IllFormed`"
+                                )
+                                .1,
+                            Event::Empty(BytesStart::new("x"))
+                        );
+                        assert_eq!(
+                            reader.buffer_position(),
+                            xml.len(),
+                            ".buffer_position() is incorrect in the end"
+                        );
                     }
-                    assert_eq!(
-                        reader.read_event_into_async(&mut buf).await.expect(
-                            "parsing should be possible to continue after `Error::IllFormed`"
-                        ),
-                        Event::Empty(BytesStart::new("x"))
-                    );
 
-                    let mut reader = NsReader::from_str(concat!($xml, "<x/>"));
-                    reader.config_mut().enable_all_checks(true);
-                    match reader.read_resolved_event_into_async(&mut buf).await {
-                        Err(Error::IllFormed(cause)) => {
-                            assert_eq!(cause, $cause);
-                            assert_eq!(reader.buffer_position(), $pos);
+                    #[cfg(feature = "async-tokio")]
+                    #[tokio::test]
+                    async fn async_tokio() {
+                        let xml = concat!($xml, "<x/>");
+                        let mut buf = Vec::new();
+                        let mut reader = NsReader::from_str(xml);
+                        reader.config_mut().enable_all_checks(true);
+                        match reader.read_resolved_event_into_async(&mut buf).await {
+                            Err(Error::IllFormed(cause)) => assert_eq!(
+                                (cause, reader.error_position(), reader.buffer_position()),
+                                ($cause, $pos, $xml.len()),
+                            ),
+                            x => panic!("Expected `Err(IllFormed(_))`, but got {:?}", x),
                         }
-                        x => panic!("Expected `Err(IllFormed(_))`, but got {:?}", x),
+                        assert_eq!(
+                            reader
+                                .read_resolved_event_into_async(&mut buf)
+                                .await
+                                .expect(
+                                    "parsing should be possible to continue after `Error::IllFormed`"
+                                )
+                                .1,
+                            Event::Empty(BytesStart::new("x"))
+                        );
+                        assert_eq!(
+                            reader.buffer_position(),
+                            xml.len(),
+                            ".buffer_position() is incorrect in the end"
+                        );
                     }
-                    assert_eq!(
-                        reader
-                            .read_resolved_event_into_async(&mut buf)
-                            .await
-                            .expect(
-                                "parsing should be possible to continue after `Error::IllFormed`"
-                            )
-                            .1,
-                        Event::Empty(BytesStart::new("x"))
-                    );
                 }
             }
         };
@@ -468,140 +607,197 @@ mod ill_formed {
         ($test:ident($xml:literal) => $pos:literal : $cause:expr) => {
             mod $test {
                 use super::*;
-                use pretty_assertions::assert_eq;
 
-                #[test]
-                fn borrowed() {
-                    let mut reader = Reader::from_str(concat!($xml, "<x/>"));
-                    reader.config_mut().enable_all_checks(true);
-                    reader.read_event().expect("first .read_event()");
-                    match reader.read_event() {
-                        Err(Error::IllFormed(cause)) => {
-                            assert_eq!(cause, $cause);
-                            assert_eq!(reader.buffer_position(), $pos);
+                mod reader {
+                    use super::*;
+                    use pretty_assertions::assert_eq;
+
+                    #[test]
+                    fn borrowed() {
+                        let xml = concat!($xml, "<x/>");
+                        let mut reader = Reader::from_str(xml);
+                        reader.config_mut().enable_all_checks(true);
+                        reader.read_event().expect("first .read_event()");
+                        match reader.read_event() {
+                            Err(Error::IllFormed(cause)) => assert_eq!(
+                                (cause, reader.error_position(), reader.buffer_position()),
+                                ($cause, $pos, $xml.len()),
+                            ),
+                            x => panic!("Expected `Err(IllFormed(_))`, but got {:?}", x),
                         }
-                        x => panic!("Expected `Err(IllFormed(_))`, but got {:?}", x),
+                        assert_eq!(
+                            reader.read_event().expect(
+                                "parsing should be possible to continue after `Error::IllFormed`"
+                            ),
+                            Event::Empty(BytesStart::new("x"))
+                        );
+                        assert_eq!(
+                            reader.buffer_position(),
+                            xml.len(),
+                            ".buffer_position() is incorrect in the end"
+                        );
                     }
-                    assert_eq!(
-                        reader.read_event().expect(
-                            "parsing should be possible to continue after `Error::IllFormed`"
-                        ),
-                        Event::Empty(BytesStart::new("x"))
-                    );
 
-                    let mut reader = NsReader::from_str(concat!($xml, "<x/>"));
-                    reader.config_mut().enable_all_checks(true);
-                    reader.read_event().expect("first .read_resolved_event()");
-                    match reader.read_resolved_event() {
-                        Err(Error::IllFormed(cause)) => {
-                            assert_eq!(cause, $cause);
-                            assert_eq!(reader.buffer_position(), $pos);
+                    #[test]
+                    fn buffered() {
+                        let xml = concat!($xml, "<x/>");
+                        let mut buf = Vec::new();
+                        let mut reader = Reader::from_str(xml);
+                        reader.config_mut().enable_all_checks(true);
+                        reader
+                            .read_event_into(&mut buf)
+                            .expect("first .read_event_into()");
+                        match reader.read_event_into(&mut buf) {
+                            Err(Error::IllFormed(cause)) => assert_eq!(
+                                (cause, reader.error_position(), reader.buffer_position()),
+                                ($cause, $pos, $xml.len()),
+                            ),
+                            x => panic!("Expected `Err(IllFormed(_))`, but got {:?}", x),
                         }
-                        x => panic!("Expected `Err(IllFormed(_))`, but got {:?}", x),
+                        assert_eq!(
+                            reader.read_event_into(&mut buf).expect(
+                                "parsing should be possible to continue after `Error::IllFormed`"
+                            ),
+                            Event::Empty(BytesStart::new("x"))
+                        );
+                        assert_eq!(
+                            reader.buffer_position(),
+                            xml.len(),
+                            ".buffer_position() is incorrect in the end"
+                        );
                     }
-                    assert_eq!(
+
+                    #[cfg(feature = "async-tokio")]
+                    #[tokio::test]
+                    async fn async_tokio() {
+                        let xml = concat!($xml, "<x/>");
+                        let mut buf = Vec::new();
+                        let mut reader = Reader::from_str(xml);
+                        reader.config_mut().enable_all_checks(true);
                         reader
-                            .read_resolved_event()
-                            .expect(
+                            .read_event_into_async(&mut buf)
+                            .await
+                            .expect("first .read_event_into_async()");
+                        match reader.read_event_into_async(&mut buf).await {
+                            Err(Error::IllFormed(cause)) => assert_eq!(
+                                (cause, reader.error_position(), reader.buffer_position()),
+                                ($cause, $pos, $xml.len()),
+                            ),
+                            x => panic!("Expected `Err(IllFormed(_))`, but got {:?}", x),
+                        }
+                        assert_eq!(
+                            reader.read_event_into_async(&mut buf).await.expect(
                                 "parsing should be possible to continue after `Error::IllFormed`"
-                            )
-                            .1,
-                        Event::Empty(BytesStart::new("x"))
-                    );
+                            ),
+                            Event::Empty(BytesStart::new("x"))
+                        );
+                        assert_eq!(
+                            reader.buffer_position(),
+                            xml.len(),
+                            ".buffer_position() is incorrect in the end"
+                        );
+                    }
                 }
 
-                #[test]
-                fn buffered() {
-                    let mut buf = Vec::new();
-                    let mut reader = Reader::from_str(concat!($xml, "<x/>"));
-                    reader.config_mut().enable_all_checks(true);
-                    reader
-                        .read_event_into(&mut buf)
-                        .expect("first .read_event_into()");
-                    match reader.read_event_into(&mut buf) {
-                        Err(Error::IllFormed(cause)) => {
-                            assert_eq!(cause, $cause);
-                            assert_eq!(reader.buffer_position(), $pos);
+                mod ns_reader {
+                    use super::*;
+                    use pretty_assertions::assert_eq;
+
+                    #[test]
+                    fn borrowed() {
+                        let xml = concat!($xml, "<x/>");
+                        let mut reader = NsReader::from_str(xml);
+                        reader.config_mut().enable_all_checks(true);
+                        reader.read_event().expect("first .read_resolved_event()");
+                        match reader.read_resolved_event() {
+                            Err(Error::IllFormed(cause)) => assert_eq!(
+                                (cause, reader.error_position(), reader.buffer_position()),
+                                ($cause, $pos, $xml.len()),
+                            ),
+                            x => panic!("Expected `Err(IllFormed(_))`, but got {:?}", x),
                         }
-                        x => panic!("Expected `Err(IllFormed(_))`, but got {:?}", x),
+                        assert_eq!(
+                            reader
+                                .read_resolved_event()
+                                .expect(
+                                    "parsing should be possible to continue after `Error::IllFormed`"
+                                )
+                                .1,
+                            Event::Empty(BytesStart::new("x"))
+                        );
+                        assert_eq!(
+                            reader.buffer_position(),
+                            xml.len(),
+                            ".buffer_position() is incorrect in the end"
+                        );
                     }
-                    assert_eq!(
-                        reader.read_event_into(&mut buf).expect(
-                            "parsing should be possible to continue after `Error::IllFormed`"
-                        ),
-                        Event::Empty(BytesStart::new("x"))
-                    );
 
-                    let mut reader = NsReader::from_str(concat!($xml, "<x/>"));
-                    reader.config_mut().enable_all_checks(true);
-                    reader
-                        .read_resolved_event_into(&mut buf)
-                        .expect("first .read_resolved_event_into()");
-                    match reader.read_resolved_event_into(&mut buf) {
-                        Err(Error::IllFormed(cause)) => {
-                            assert_eq!(cause, $cause);
-                            assert_eq!(reader.buffer_position(), $pos);
-                        }
-                        x => panic!("Expected `Err(IllFormed(_))`, but got {:?}", x),
-                    }
-                    assert_eq!(
+                    #[test]
+                    fn buffered() {
+                        let xml = concat!($xml, "<x/>");
+                        let mut buf = Vec::new();
+                        let mut reader = NsReader::from_str(xml);
+                        reader.config_mut().enable_all_checks(true);
                         reader
                             .read_resolved_event_into(&mut buf)
-                            .expect(
-                                "parsing should be possible to continue after `Error::IllFormed`"
-                            )
-                            .1,
-                        Event::Empty(BytesStart::new("x"))
-                    );
-                }
-
-                #[cfg(feature = "async-tokio")]
-                #[tokio::test]
-                async fn async_tokio() {
-                    let mut buf = Vec::new();
-                    let mut reader = Reader::from_str(concat!($xml, "<x/>"));
-                    reader.config_mut().enable_all_checks(true);
-                    reader
-                        .read_event_into_async(&mut buf)
-                        .await
-                        .expect("first .read_event_into_async()");
-                    match reader.read_event_into_async(&mut buf).await {
-                        Err(Error::IllFormed(cause)) => {
-                            assert_eq!(cause, $cause);
-                            assert_eq!(reader.buffer_position(), $pos);
+                            .expect("first .read_resolved_event_into()");
+                        match reader.read_resolved_event_into(&mut buf) {
+                            Err(Error::IllFormed(cause)) => assert_eq!(
+                                (cause, reader.error_position(), reader.buffer_position()),
+                                ($cause, $pos, $xml.len()),
+                            ),
+                            x => panic!("Expected `Err(IllFormed(_))`, but got {:?}", x),
                         }
-                        x => panic!("Expected `Err(IllFormed(_))`, but got {:?}", x),
+                        assert_eq!(
+                            reader
+                                .read_resolved_event_into(&mut buf)
+                                .expect(
+                                    "parsing should be possible to continue after `Error::IllFormed`"
+                                )
+                                .1,
+                            Event::Empty(BytesStart::new("x"))
+                        );
+                        assert_eq!(
+                            reader.buffer_position(),
+                            xml.len(),
+                            ".buffer_position() is incorrect in the end"
+                        );
                     }
-                    assert_eq!(
-                        reader.read_event_into_async(&mut buf).await.expect(
-                            "parsing should be possible to continue after `Error::IllFormed`"
-                        ),
-                        Event::Empty(BytesStart::new("x"))
-                    );
 
-                    let mut reader = NsReader::from_str(concat!($xml, "<x/>"));
-                    reader.config_mut().enable_all_checks(true);
-                    reader
-                        .read_resolved_event_into_async(&mut buf)
-                        .await
-                        .expect("first .read_resolved_event_into_async()");
-                    match reader.read_resolved_event_into_async(&mut buf).await {
-                        Err(Error::IllFormed(cause)) => {
-                            assert_eq!(cause, $cause);
-                            assert_eq!(reader.buffer_position(), $pos);
-                        }
-                        x => panic!("Expected `Err(IllFormed(_))`, but got {:?}", x),
-                    }
-                    assert_eq!(
+                    #[cfg(feature = "async-tokio")]
+                    #[tokio::test]
+                    async fn async_tokio() {
+                        let xml = concat!($xml, "<x/>");
+                        let mut buf = Vec::new();
+                        let mut reader = NsReader::from_str(xml);
+                        reader.config_mut().enable_all_checks(true);
                         reader
                             .read_resolved_event_into_async(&mut buf)
                             .await
-                            .expect(
-                                "parsing should be possible to continue after `Error::IllFormed`"
-                            )
-                            .1,
-                        Event::Empty(BytesStart::new("x"))
-                    );
+                            .expect("first .read_resolved_event_into_async()");
+                        match reader.read_resolved_event_into_async(&mut buf).await {
+                            Err(Error::IllFormed(cause)) => assert_eq!(
+                                (cause, reader.error_position(), reader.buffer_position()),
+                                ($cause, $pos, $xml.len()),
+                            ),
+                            x => panic!("Expected `Err(IllFormed(_))`, but got {:?}", x),
+                        }
+                        assert_eq!(
+                            reader
+                                .read_resolved_event_into_async(&mut buf)
+                                .await
+                                .expect(
+                                    "parsing should be possible to continue after `Error::IllFormed`"
+                                )
+                                .1,
+                            Event::Empty(BytesStart::new("x"))
+                        );
+                        assert_eq!(
+                            reader.buffer_position(),
+                            xml.len(),
+                            ".buffer_position() is incorrect in the end"
+                        );
+                    }
                 }
             }
         };
@@ -610,9 +806,9 @@ mod ill_formed {
     // IllFormedError::MissingDeclVersion is generated lazily when you call `BytesDecl::version()`
 
     err!(missing_doctype_name1("<!DOCTYPE>") => 9: IllFormedError::MissingDoctypeName);
-    //                                  ^= 9
+    //                                   ^= 9
     err!(missing_doctype_name2("<!DOCTYPE \t\r\n>") => 13: IllFormedError::MissingDoctypeName);
-    //                                         ^= 13
+    //                                          ^= 13
     ok!(missing_doctype_name3("<!DOCTYPE \t\r\nx>") => Event::DocType(BytesText::new("x")));
 
     err!(unmatched_end_tag1("</>") => 0: IllFormedError::UnmatchedEndTag("".to_string()));
@@ -621,17 +817,17 @@ mod ill_formed {
 
     ok!(mismatched_end_tag1("<start></start>") => Event::Start(BytesStart::new("start")));
     err2!(mismatched_end_tag2("<start></>") => 7: IllFormedError::MismatchedEndTag {
-        //                        ^= 7
+        //                            ^= 7
         expected: "start".to_string(),
         found: "".to_string(),
     });
     err2!(mismatched_end_tag3("<start></end>") => 7: IllFormedError::MismatchedEndTag {
-        //                        ^= 7
+        //                            ^= 7
         expected: "start".to_string(),
         found: "end".to_string(),
     });
     err2!(mismatched_end_tag4("<start></end >") => 7: IllFormedError::MismatchedEndTag {
-        //                        ^= 7
+        //                            ^= 7
         expected: "start".to_string(),
         found: "end".to_string(),
     });
diff --git a/tests/unit_tests.rs b/tests/unit_tests.rs
index 9dec515..954b90a 100644
--- a/tests/unit_tests.rs
+++ b/tests/unit_tests.rs
@@ -366,22 +366,6 @@ fn test_new_xml_decl_empty() {
     );
 }
 
-#[test]
-fn test_offset_err_end_element() {
-    let mut r = Reader::from_str("</a>");
-    r.config_mut().trim_text(true);
-
-    match r.read_event() {
-        Err(_) if r.buffer_position() == 0 => (), // error at char 0: no opening tag
-        Err(e) => panic!(
-            "expecting buf_pos = 0, found {}, err: {:?}",
-            r.buffer_position(),
-            e
-        ),
-        e => panic!("expecting error, found {:?}", e),
-    }
-}
-
 #[test]
 fn test_escaped_content() {
     let mut r = Reader::from_str("<a>&lt;test&gt;</a>");