Repository: tafia/quick-xml
Pull request #684 (2023-11-21 20:21:34)

Add new tests for syntax and ill-formed parser errors and fix... emm... errors
This PR is a prepare step for rewriting parser from scratch. First of all I want to guarantee that behaviour will not be changed, especially conditions on which errors is returned and a final state after the error.

Thanks to that tests, I found several bugs related to result of `.buffer_position()` that should report error position.

This PR changes how some ill-formed XML constructs are processed:
- `<?xml?>` now will be threated as syntactically correct XML declaration, although it [is not well-formed](https://www.w3.org/TR/xml11/#NT-XMLDecl). Note, however, that some [online validators](https://codebeautify.org/xmlvalidator) allows such declaration. Previously it was reported as `Event::PI`. According to the specification, a `version` attribute is mandatory, but I follow the quick-xml ideology, that we do not do checks that could slow down parsing, but that checks are performed by demand. In that case when you call `BytesDecl::version()`. Maybe that is not the better way to do things, but this PR not about it.
- `<??>` now will be parsed as syntactically correct processing instruction, although it [is not well-formed](https://www.w3.org/TR/xml11/#NT-PI), previously error is returned. Again, some online validators allows that. Currently there is no way to enforce well-formedless check for PI, but I think, that could be done in #650


Patch:
diff --git a/Changelog.md b/Changelog.md
index 986b19f..7f3134a 100644
--- a/Changelog.md
+++ b/Changelog.md
@@ -20,27 +20,39 @@ configuration is serializable.
 - [#677]: Added methods `config()` and `config_mut()` to inspect and change the parser
   configuration. Previous builder methods on `Reader` / `NsReader` was replaced by
   direct access to fields of config using `reader.config_mut().<...>`.
+- #[#684]: Added a method `Config::enable_all_checks` to turn on or off all
+  well-formedness checks.
 
 ### Bug Fixes
 
 - [#622]: Fix wrong disregarding of not closed markup, such as lone `<`.
+- [#684]: Fix incorrect position reported for `Error::IllFormed(DoubleHyphenInComment)`.
+- [#684]: Fix incorrect position reported for `Error::IllFormed(MissingDoctypeName)`.
 
 ### Misc Changes
 
 - [#675]: Minimum supported version of serde raised to 1.0.139
 - [#675]: Rework the `quick_xml::Error` type to provide more accurate information:
-  - `Error::EndEventMismatch` replaced by `IllFormedError::MismatchedEnd` in some cases
-  - `Error::EndEventMismatch` replaced by `IllFormedError::UnmatchedEnd` in some cases
+  - `Error::EndEventMismatch` replaced by `IllFormedError::MismatchedEndTag` in some cases
+  - `Error::EndEventMismatch` replaced by `IllFormedError::UnmatchedEndTag` in some cases
   - `Error::TextNotFound` was removed because not used
   - `Error::UnexpectedBang` replaced by `SyntaxError`
   - `Error::UnexpectedEof` replaced by `SyntaxError` in some cases
   - `Error::UnexpectedEof` replaced by `IllFormedError` in some cases
   - `Error::UnexpectedToken` replaced by `IllFormedError::DoubleHyphenInComment`
+  - `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.
+- [#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`.
 
 [#513]: https://github.com/tafia/quick-xml/issues/513
 [#622]: https://github.com/tafia/quick-xml/issues/622
 [#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
 
 
 ## 0.31.0 -- 2023-10-22
diff --git a/src/de/mod.rs b/src/de/mod.rs
index 239ec25..185f98a 100644
--- a/src/de/mod.rs
+++ b/src/de/mod.rs
@@ -2649,7 +2649,7 @@ where
     /// |[`DeEvent::Start`]|`<any-tag>...</any-tag>`   |Emits [`UnexpectedStart("any-tag")`](DeError::UnexpectedStart)
     /// |[`DeEvent::End`]  |`</tag>`                   |Returns an empty slice. The reader guarantee that tag will match the open one
     /// |[`DeEvent::Text`] |`text content` or `<![CDATA[cdata content]]>` (probably mixed)|Returns event content unchanged, expects the `</tag>` after that
-    /// |[`DeEvent::Eof`]  |                           |Emits [`InvalidXml(IllFormed(MissedEnd))`](DeError::InvalidXml)
+    /// |[`DeEvent::Eof`]  |                           |Emits [`InvalidXml(IllFormed(MissingEndTag))`](DeError::InvalidXml)
     ///
     /// [`Text`]: Event::Text
     /// [`CData`]: Event::CData
@@ -3642,7 +3642,7 @@ mod tests {
 
             match de.read_to_end(QName(b"tag")) {
                 Err(DeError::InvalidXml(Error::IllFormed(cause))) => {
-                    assert_eq!(cause, IllFormedError::MissedEnd("tag".into()))
+                    assert_eq!(cause, IllFormedError::MissingEndTag("tag".into()))
                 }
                 x => panic!(
                     "Expected `Err(InvalidXml(IllFormed(_)))`, but got `{:?}`",
@@ -3661,7 +3661,7 @@ mod tests {
 
             match de.read_to_end(QName(b"tag")) {
                 Err(DeError::InvalidXml(Error::IllFormed(cause))) => {
-                    assert_eq!(cause, IllFormedError::MissedEnd("tag".into()))
+                    assert_eq!(cause, IllFormedError::MissingEndTag("tag".into()))
                 }
                 x => panic!(
                     "Expected `Err(InvalidXml(IllFormed(_)))`, but got `{:?}`",
@@ -3756,7 +3756,7 @@ mod tests {
     fn read_string() {
         match from_str::<String>(r#"</root>"#) {
             Err(DeError::InvalidXml(Error::IllFormed(cause))) => {
-                assert_eq!(cause, IllFormedError::UnmatchedEnd("root".into()));
+                assert_eq!(cause, IllFormedError::UnmatchedEndTag("root".into()));
             }
             x => panic!(
                 "Expected `Err(InvalidXml(IllFormed(_)))`, but got `{:?}`",
@@ -3770,7 +3770,7 @@ mod tests {
         match from_str::<String>(r#"<root></other>"#) {
             Err(DeError::InvalidXml(Error::IllFormed(cause))) => assert_eq!(
                 cause,
-                IllFormedError::MismatchedEnd {
+                IllFormedError::MismatchedEndTag {
                     expected: "root".into(),
                     found: "other".into(),
                 }
@@ -4098,7 +4098,7 @@ mod tests {
                     assert_eq!(de.next().unwrap(), DeEvent::End(BytesEnd::new("tag")));
                     match de.next() {
                         Err(DeError::InvalidXml(Error::IllFormed(cause))) => {
-                            assert_eq!(cause, IllFormedError::UnmatchedEnd("tag2".into()));
+                            assert_eq!(cause, IllFormedError::UnmatchedEndTag("tag2".into()));
                         }
                         x => panic!(
                             "Expected `Err(InvalidXml(IllFormed(_)))`, but got `{:?}`",
@@ -4241,7 +4241,7 @@ mod tests {
             let mut de = make_de("</tag>");
             match de.next() {
                 Err(DeError::InvalidXml(Error::IllFormed(cause))) => {
-                    assert_eq!(cause, IllFormedError::UnmatchedEnd("tag".into()));
+                    assert_eq!(cause, IllFormedError::UnmatchedEndTag("tag".into()));
                 }
                 x => panic!(
                     "Expected `Err(InvalidXml(IllFormed(_)))`, but got `{:?}`",
@@ -4320,7 +4320,7 @@ mod tests {
                 assert_eq!(de.next().unwrap(), DeEvent::Text("text".into()));
                 match de.next() {
                     Err(DeError::InvalidXml(Error::IllFormed(cause))) => {
-                        assert_eq!(cause, IllFormedError::UnmatchedEnd("tag".into()));
+                        assert_eq!(cause, IllFormedError::UnmatchedEndTag("tag".into()));
                     }
                     x => panic!(
                         "Expected `Err(InvalidXml(IllFormed(_)))`, but got `{:?}`",
@@ -4352,7 +4352,7 @@ mod tests {
                     assert_eq!(de.next().unwrap(), DeEvent::Text("text  cdata ".into()));
                     match de.next() {
                         Err(DeError::InvalidXml(Error::IllFormed(cause))) => {
-                            assert_eq!(cause, IllFormedError::UnmatchedEnd("tag".into()));
+                            assert_eq!(cause, IllFormedError::UnmatchedEndTag("tag".into()));
                         }
                         x => panic!(
                             "Expected `Err(InvalidXml(IllFormed(_)))`, but got `{:?}`",
@@ -4458,7 +4458,7 @@ mod tests {
                 assert_eq!(de.next().unwrap(), DeEvent::Text(" cdata ".into()));
                 match de.next() {
                     Err(DeError::InvalidXml(Error::IllFormed(cause))) => {
-                        assert_eq!(cause, IllFormedError::UnmatchedEnd("tag".into()));
+                        assert_eq!(cause, IllFormedError::UnmatchedEndTag("tag".into()));
                     }
                     x => panic!(
                         "Expected `Err(InvalidXml(IllFormed(_)))`, but got `{:?}`",
@@ -4488,7 +4488,7 @@ mod tests {
                     assert_eq!(de.next().unwrap(), DeEvent::Text(" cdata  text".into()));
                     match de.next() {
                         Err(DeError::InvalidXml(Error::IllFormed(cause))) => {
-                            assert_eq!(cause, IllFormedError::UnmatchedEnd("tag".into()));
+                            assert_eq!(cause, IllFormedError::UnmatchedEndTag("tag".into()));
                         }
                         x => panic!(
                             "Expected `Err(InvalidXml(IllFormed(_)))`, but got `{:?}`",
@@ -4538,7 +4538,7 @@ mod tests {
                     assert_eq!(de.next().unwrap(), DeEvent::Text(" cdata  cdata2 ".into()));
                     match de.next() {
                         Err(DeError::InvalidXml(Error::IllFormed(cause))) => {
-                            assert_eq!(cause, IllFormedError::UnmatchedEnd("tag".into()));
+                            assert_eq!(cause, IllFormedError::UnmatchedEndTag("tag".into()));
                         }
                         x => panic!(
                             "Expected `Err(InvalidXml(IllFormed(_)))`, but got `{:?}`",
diff --git a/src/errors.rs b/src/errors.rs
index 78658ee..df65739 100644
--- a/src/errors.rs
+++ b/src/errors.rs
@@ -71,17 +71,36 @@ impl std::error::Error for SyntaxError {}
 /// [well-formed]: https://www.w3.org/TR/xml11/#dt-wellformed
 #[derive(Clone, Debug, PartialEq, Eq)]
 pub enum IllFormedError {
+    /// A `version` attribute was not found in an XML declaration or is not the
+    /// first attribute.
+    ///
+    /// According to the [specification], the XML declaration (`<?xml ?>`) MUST contain
+    /// a `version` attribute and it MUST be the first attribute. This error indicates,
+    /// that the declaration does not contain attributes at all (if contains `None`)
+    /// or either `version` attribute is not present or not the first attribute in
+    /// the declaration. In the last case it contains the name of the found attribute.
+    ///
+    /// [specification]: https://www.w3.org/TR/xml11/#sec-prolog-dtd
+    MissingDeclVersion(Option<String>),
+    /// A document type definition (DTD) does not contain a name of a root element.
+    ///
+    /// According to the [specification], document type definition (`<!DOCTYPE foo>`)
+    /// MUST contain a name which defines a document type (`foo`). If that name
+    /// is missed, this error is returned.
+    ///
+    /// [specification]: https://www.w3.org/TR/xml11/#NT-doctypedecl
+    MissingDoctypeName,
     /// The end tag was not found during reading of a sub-tree of elements due to
     /// encountering an EOF from the underlying reader. This error is returned from
     /// [`Reader::read_to_end`].
     ///
     /// [`Reader::read_to_end`]: crate::reader::Reader::read_to_end
-    MissedEnd(String),
+    MissingEndTag(String),
     /// The specified end tag was encountered without corresponding open tag at the
     /// same level of hierarchy
-    UnmatchedEnd(String),
+    UnmatchedEndTag(String),
     /// The specified end tag does not match the start tag at that nesting level.
-    MismatchedEnd {
+    MismatchedEndTag {
         /// Name of open tag, that is expected to be closed
         expected: String,
         /// Name of actually closed tag
@@ -103,15 +122,25 @@ pub enum IllFormedError {
 impl fmt::Display for IllFormedError {
     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
         match self {
-            Self::MissedEnd(tag) => write!(
+            Self::MissingDeclVersion(None) => {
+                write!(f, "an XML declaration does not contain `version` attribute")
+            }
+            Self::MissingDeclVersion(Some(attr)) => {
+                write!(f, "an XML declaration must start with `version` attribute, but in starts with `{}`", attr)
+            }
+            Self::MissingDoctypeName => write!(
+                f,
+                "`<!DOCTYPE>` declaration does not contain a name of a document type"
+            ),
+            Self::MissingEndTag(tag) => write!(
                 f,
                 "start tag not closed: `</{}>` not found before end of input",
                 tag,
             ),
-            Self::UnmatchedEnd(tag) => {
+            Self::UnmatchedEndTag(tag) => {
                 write!(f, "close tag `</{}>` does not match any open tag", tag)
             }
-            Self::MismatchedEnd { expected, found } => write!(
+            Self::MismatchedEndTag { expected, found } => write!(
                 f,
                 "expected `</{}>`, but `</{}>` was found",
                 expected, found,
@@ -143,25 +172,6 @@ pub enum Error {
     ///
     /// [`encoding`]: index.html#encoding
     NonDecodable(Option<Utf8Error>),
-    /// A `version` attribute was not found in an XML declaration or is not the
-    /// first attribute.
-    ///
-    /// According to the [specification], the XML declaration (`<?xml ?>`) MUST contain
-    /// a `version` attribute and it MUST be the first attribute. This error indicates,
-    /// that the declaration does not contain attributes at all (if contains `None`)
-    /// or either `version` attribute is not present or not the first attribute in
-    /// the declaration. In the last case it contains the name of the found attribute.
-    ///
-    /// [specification]: https://www.w3.org/TR/xml11/#sec-prolog-dtd
-    XmlDeclWithoutVersion(Option<String>),
-    /// A document type definition (DTD) does not contain a name of a root element.
-    ///
-    /// According to the [specification], document type definition (`<!doctype foo>`)
-    /// MUST contain a name which defines a document type. If that name is missed,
-    /// this error is returned.
-    ///
-    /// [specification]: https://www.w3.org/TR/xml11/#NT-doctypedecl
-    EmptyDocType,
     /// Attribute parsing error
     InvalidAttr(AttrError),
     /// Escape error
@@ -189,7 +199,7 @@ pub enum Error {
 impl Error {
     pub(crate) fn missed_end(name: QName, decoder: Decoder) -> Self {
         match decoder.decode(name.as_ref()) {
-            Ok(name) => IllFormedError::MissedEnd(name.into()).into(),
+            Ok(name) => IllFormedError::MissingEndTag(name.into()).into(),
             Err(err) => err.into(),
         }
     }
@@ -261,16 +271,6 @@ impl fmt::Display for Error {
             Error::IllFormed(e) => write!(f, "ill-formed document: {}", e),
             Error::NonDecodable(None) => write!(f, "Malformed input, decoding impossible"),
             Error::NonDecodable(Some(e)) => write!(f, "Malformed UTF-8 input: {}", e),
-            Error::XmlDeclWithoutVersion(None) => {
-                write!(f, "an XML declaration does not contain `version` attribute")
-            }
-            Error::XmlDeclWithoutVersion(Some(attr)) => {
-                write!(f, "an XML declaration must start with `version` attribute, but in starts with `{}`", attr)
-            }
-            Error::EmptyDocType => write!(
-                f,
-                "`<!DOCTYPE>` declaration does not contain a name of a document type"
-            ),
             Error::InvalidAttr(e) => write!(f, "error while parsing attribute: {}", e),
             Error::EscapeError(e) => write!(f, "{}", e),
             Error::UnknownPrefix(prefix) => {
diff --git a/src/events/mod.rs b/src/events/mod.rs
index 7a484aa..d574089 100644
--- a/src/events/mod.rs
+++ b/src/events/mod.rs
@@ -45,7 +45,7 @@ use std::ops::Deref;
 use std::str::from_utf8;
 
 use crate::encoding::Decoder;
-use crate::errors::{Error, Result};
+use crate::errors::{Error, IllFormedError, Result};
 use crate::escape::{escape, partial_escape, unescape_with};
 use crate::name::{LocalName, QName};
 use crate::reader::is_whitespace;
@@ -391,12 +391,12 @@ impl<'a> BytesDecl<'a> {
     /// In case of multiple attributes value of the first one is returned.
     ///
     /// If version is missed in the declaration, or the first thing is not a version,
-    /// [`Error::XmlDeclWithoutVersion`] will be returned.
+    /// [`IllFormedError::MissingDeclVersion`] will be returned.
     ///
     /// # Examples
     ///
     /// ```
-    /// use quick_xml::Error;
+    /// use quick_xml::errors::{Error, IllFormedError};
     /// use quick_xml::events::{BytesDecl, BytesStart};
     ///
     /// // <?xml version='1.1'?>
@@ -410,21 +410,21 @@ impl<'a> BytesDecl<'a> {
     /// // <?xml encoding='utf-8'?>
     /// let decl = BytesDecl::from_start(BytesStart::from_content(" encoding='utf-8'", 0));
     /// match decl.version() {
-    ///     Err(Error::XmlDeclWithoutVersion(Some(key))) => assert_eq!(key, "encoding"),
+    ///     Err(Error::IllFormed(IllFormedError::MissingDeclVersion(Some(key)))) => assert_eq!(key, "encoding"),
     ///     _ => assert!(false),
     /// }
     ///
     /// // <?xml encoding='utf-8' version='1.1'?>
     /// let decl = BytesDecl::from_start(BytesStart::from_content(" encoding='utf-8' version='1.1'", 0));
     /// match decl.version() {
-    ///     Err(Error::XmlDeclWithoutVersion(Some(key))) => assert_eq!(key, "encoding"),
+    ///     Err(Error::IllFormed(IllFormedError::MissingDeclVersion(Some(key)))) => assert_eq!(key, "encoding"),
     ///     _ => assert!(false),
     /// }
     ///
     /// // <?xml?>
     /// let decl = BytesDecl::from_start(BytesStart::from_content("", 0));
     /// match decl.version() {
-    ///     Err(Error::XmlDeclWithoutVersion(None)) => {},
+    ///     Err(Error::IllFormed(IllFormedError::MissingDeclVersion(None))) => {},
     ///     _ => assert!(false),
     /// }
     /// ```
@@ -437,12 +437,14 @@ impl<'a> BytesDecl<'a> {
             // first attribute was not "version"
             Some(Ok(a)) => {
                 let found = from_utf8(a.key.as_ref())?.to_string();
-                Err(Error::XmlDeclWithoutVersion(Some(found)))
+                Err(Error::IllFormed(IllFormedError::MissingDeclVersion(Some(
+                    found,
+                ))))
             }
             // error parsing attributes
             Some(Err(e)) => Err(e.into()),
             // no attributes
-            None => Err(Error::XmlDeclWithoutVersion(None)),
+            None => Err(Error::IllFormed(IllFormedError::MissingDeclVersion(None))),
         }
     }
 
diff --git a/src/reader/buffered_reader.rs b/src/reader/buffered_reader.rs
index 755a710..6f9a56c 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)? ?)?;
+            let bang_type = BangType::new(self.peek_one() $(.$await)? ?, position)?;
 
             loop {
                 match self $(.$reader)? .fill_buf() $(.$await)? {
@@ -139,6 +139,10 @@ 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;
             Err(bang_type.to_err())
         }
 
@@ -182,6 +186,10 @@ 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;
             Err(Error::Syntax(SyntaxError::UnclosedTag))
         }
 
diff --git a/src/reader/mod.rs b/src/reader/mod.rs
index 560b1dc..ba09044 100644
--- a/src/reader/mod.rs
+++ b/src/reader/mod.rs
@@ -40,7 +40,7 @@ pub struct Config {
     pub check_comments: bool,
 
     /// Whether mismatched closing tag names should be detected. If enabled, in
-    /// case of mismatch the [`Error::IllFormed(MismatchedEnd)`] is returned from
+    /// case of mismatch the [`Error::IllFormed(MismatchedEndTag)`] is returned from
     /// read methods.
     ///
     /// Note, that start and end tags [should match literally][spec], they cannot
@@ -71,7 +71,7 @@ pub struct Config {
     ///
     /// Default: `true`
     ///
-    /// [`Error::IllFormed(MismatchedEnd)`]: crate::errors::IllFormedError::MismatchedEnd
+    /// [`Error::IllFormed(MismatchedEndTag)`]: crate::errors::IllFormedError::MismatchedEndTag
     /// [spec]: https://www.w3.org/TR/xml11/#dt-etag
     /// [`End`]: crate::events::Event::End
     /// [`expand_empty_elements`]: Self::expand_empty_elements
@@ -177,6 +177,15 @@ impl Config {
         self.trim_text_start = trim;
         self.trim_text_end = trim;
     }
+
+    /// Turn on or off all checks for well-formedness. Currently it is that settings:
+    /// - [`check_comments`](Self::check_comments)
+    /// - [`check_end_names`](Self::check_end_names)
+    #[inline]
+    pub fn enable_all_checks(&mut self, enable: bool) {
+        self.check_comments = enable;
+        self.check_end_names = enable;
+    }
 }
 
 impl Default for Config {
@@ -337,7 +346,12 @@ macro_rules! read_until_close {
                 $(.$await)?
             {
                 Ok((bytes, true)) => $self.state.emit_end(bytes),
-                Ok((_, false)) => Err(Error::Syntax(SyntaxError::UnclosedTag)),
+                Ok((bytes, false)) => {
+                    // We want to report error at `<`, but offset was increased,
+                    // so return it back (+1 for `<`)
+                    $self.state.offset -= bytes.len() + 1;
+                    Err(Error::Syntax(SyntaxError::UnclosedTag))
+                }
                 Err(e) => Err(e),
             },
             // `<?` - processing instruction
@@ -346,7 +360,12 @@ macro_rules! read_until_close {
                 $(.$await)?
             {
                 Ok((bytes, true)) => $self.state.emit_question_mark(bytes),
-                Ok((_, false)) => Err(Error::Syntax(SyntaxError::UnclosedPIOrXmlDecl)),
+                Ok((bytes, false)) => {
+                    // We want to report error at `<`, but offset was increased,
+                    // so return it back (+1 for `<`)
+                    $self.state.offset -= bytes.len() + 1;
+                    Err(Error::Syntax(SyntaxError::UnclosedPIOrXmlDecl))
+                }
                 Err(e) => Err(e),
             },
             // `<...` - opening or self-closed tag
@@ -358,7 +377,12 @@ macro_rules! read_until_close {
                 Err(e) => Err(e),
             },
             // `<` - syntax error, tag not closed
-            Ok(None) => Err(Error::Syntax(SyntaxError::UnclosedTag)),
+            Ok(None) => {
+                // We want to report error at `<`, but offset was increased,
+                // so return it back (+1 for `<`)
+                $self.state.offset -= 1;
+                Err(Error::Syntax(SyntaxError::UnclosedTag))
+            }
             Err(e) => Err(e),
         }
     }};
@@ -846,12 +870,17 @@ enum BangType {
 }
 impl BangType {
     #[inline(always)]
-    fn new(byte: Option<u8>) -> Result<Self> {
+    fn new(byte: Option<u8>, position: &mut usize) -> Result<Self> {
         Ok(match byte {
             Some(b'[') => Self::CData,
             Some(b'-') => Self::Comment,
             Some(b'D') | Some(b'd') => Self::DocType,
-            _ => return Err(Error::Syntax(SyntaxError::InvalidBangMarkup)),
+            _ => {
+                // <!EOF
+                //  ^ - we want to report error at `<`, so we move offset to it (+1 for `<`)
+                *position -= 1;
+                return Err(Error::Syntax(SyntaxError::InvalidBangMarkup));
+            }
         })
     }
 
@@ -1108,9 +1137,9 @@ mod test {
                     #[ignore = "start CDATA sequence fully checked outside of `read_bang_element`"]
                     $($async)? fn not_properly_start() {
                         let buf = $buf;
-                        let mut position = 0;
+                        let mut position = 1;
                         let mut input = b"![]]>other content".as_ref();
-                        //                ^= 0
+                        //                ^= 1
 
                         match $source(&mut input).read_bang_element(buf, &mut position) $(.$await)? {
                             Err(Error::Syntax(SyntaxError::UnclosedCData)) => {}
@@ -1119,6 +1148,7 @@ mod test {
                                 x
                             ),
                         }
+                        // We want to report error at `<`
                         assert_eq!(position, 0);
                     }
 
@@ -1127,9 +1157,9 @@ mod test {
                     #[$test]
                     $($async)? fn not_closed() {
                         let buf = $buf;
-                        let mut position = 0;
+                        let mut position = 1;
                         let mut input = b"![CDATA[other content".as_ref();
-                        //                ^= 0
+                        //                ^= 1
 
                         match $source(&mut input).read_bang_element(buf, &mut position) $(.$await)? {
                             Err(Error::Syntax(SyntaxError::UnclosedCData)) => {}
@@ -1138,6 +1168,7 @@ mod test {
                                 x
                             ),
                         }
+                        // We want to report error at `<`
                         assert_eq!(position, 0);
                     }
 
@@ -1145,9 +1176,9 @@ mod test {
                     #[$test]
                     $($async)? fn empty() {
                         let buf = $buf;
-                        let mut position = 0;
+                        let mut position = 1;
                         let mut input = b"![CDATA[]]>other content".as_ref();
-                        //                           ^= 11
+                        //                ^= 1       ^= 12
 
                         let (ty, bytes) = $source(&mut input)
                             .read_bang_element(buf, &mut position)
@@ -1157,7 +1188,7 @@ mod test {
                             (ty, Bytes(bytes)),
                             (BangType::CData, Bytes(b"![CDATA[]]"))
                         );
-                        assert_eq!(position, 11);
+                        assert_eq!(position, 12);
                     }
 
                     /// Checks that CDATA element with content parsed successfully.
@@ -1166,9 +1197,9 @@ mod test {
                     #[$test]
                     $($async)? fn with_content() {
                         let buf = $buf;
-                        let mut position = 0;
+                        let mut position = 1;
                         let mut input = b"![CDATA[cdata]] ]>content]]>other content]]>".as_ref();
-                        //                                            ^= 28
+                        //                ^= 1                        ^= 29
 
                         let (ty, bytes) = $source(&mut input)
                             .read_bang_element(buf, &mut position)
@@ -1178,7 +1209,7 @@ mod test {
                             (ty, Bytes(bytes)),
                             (BangType::CData, Bytes(b"![CDATA[cdata]] ]>content]]"))
                         );
-                        assert_eq!(position, 28);
+                        assert_eq!(position, 29);
                     }
                 }
 
@@ -1206,9 +1237,9 @@ mod test {
                     #[ignore = "start comment sequence fully checked outside of `read_bang_element`"]
                     $($async)? fn not_properly_start() {
                         let buf = $buf;
-                        let mut position = 0;
+                        let mut position = 1;
                         let mut input = b"!- -->other content".as_ref();
-                        //                ^= 0
+                        //                ^= 1
 
                         match $source(&mut input).read_bang_element(buf, &mut position) $(.$await)? {
                             Err(Error::Syntax(SyntaxError::UnclosedComment)) => {}
@@ -1217,15 +1248,16 @@ mod test {
                                 x
                             ),
                         }
+                        // We want to report error at `<`
                         assert_eq!(position, 0);
                     }
 
                     #[$test]
                     $($async)? fn not_properly_end() {
                         let buf = $buf;
-                        let mut position = 0;
+                        let mut position = 1;
                         let mut input = b"!->other content".as_ref();
-                        //                ^= 0
+                        //                ^= 1
 
                         match $source(&mut input).read_bang_element(buf, &mut position) $(.$await)? {
                             Err(Error::Syntax(SyntaxError::UnclosedComment)) => {}
@@ -1234,15 +1266,16 @@ mod test {
                                 x
                             ),
                         }
+                        // We want to report error at `<`
                         assert_eq!(position, 0);
                     }
 
                     #[$test]
                     $($async)? fn not_closed1() {
                         let buf = $buf;
-                        let mut position = 0;
+                        let mut position = 1;
                         let mut input = b"!--other content".as_ref();
-                        //                ^= 0
+                        //                ^= 1
 
                         match $source(&mut input).read_bang_element(buf, &mut position) $(.$await)? {
                             Err(Error::Syntax(SyntaxError::UnclosedComment)) => {}
@@ -1251,15 +1284,16 @@ mod test {
                                 x
                             ),
                         }
+                        // We want to report error at `<`
                         assert_eq!(position, 0);
                     }
 
                     #[$test]
                     $($async)? fn not_closed2() {
                         let buf = $buf;
-                        let mut position = 0;
+                        let mut position = 1;
                         let mut input = b"!-->other content".as_ref();
-                        //                ^= 0
+                        //                ^= 1
 
                         match $source(&mut input).read_bang_element(buf, &mut position) $(.$await)? {
                             Err(Error::Syntax(SyntaxError::UnclosedComment)) => {}
@@ -1268,15 +1302,16 @@ mod test {
                                 x
                             ),
                         }
+                        // We want to report error at `<`
                         assert_eq!(position, 0);
                     }
 
                     #[$test]
                     $($async)? fn not_closed3() {
                         let buf = $buf;
-                        let mut position = 0;
+                        let mut position = 1;
                         let mut input = b"!--->other content".as_ref();
-                        //                ^= 0
+                        //                ^= 1
 
                         match $source(&mut input).read_bang_element(buf, &mut position) $(.$await)? {
                             Err(Error::Syntax(SyntaxError::UnclosedComment)) => {}
@@ -1285,15 +1320,16 @@ mod test {
                                 x
                             ),
                         }
+                        // We want to report error at `<`
                         assert_eq!(position, 0);
                     }
 
                     #[$test]
                     $($async)? fn empty() {
                         let buf = $buf;
-                        let mut position = 0;
+                        let mut position = 1;
                         let mut input = b"!---->other content".as_ref();
-                        //                      ^= 6
+                        //                ^= 1  ^= 7
 
                         let (ty, bytes) = $source(&mut input)
                             .read_bang_element(buf, &mut position)
@@ -1303,15 +1339,15 @@ mod test {
                             (ty, Bytes(bytes)),
                             (BangType::Comment, Bytes(b"!----"))
                         );
-                        assert_eq!(position, 6);
+                        assert_eq!(position, 7);
                     }
 
                     #[$test]
                     $($async)? fn with_content() {
                         let buf = $buf;
-                        let mut position = 0;
+                        let mut position = 1;
                         let mut input = b"!--->comment<--->other content".as_ref();
-                        //                                 ^= 17
+                        //                ^= 1             ^= 18
 
                         let (ty, bytes) = $source(&mut input)
                             .read_bang_element(buf, &mut position)
@@ -1321,7 +1357,7 @@ mod test {
                             (ty, Bytes(bytes)),
                             (BangType::Comment, Bytes(b"!--->comment<---"))
                         );
-                        assert_eq!(position, 17);
+                        assert_eq!(position, 18);
                     }
                 }
 
@@ -1336,9 +1372,9 @@ mod test {
                         #[$test]
                         $($async)? fn not_properly_start() {
                             let buf = $buf;
-                            let mut position = 0;
+                            let mut position = 1;
                             let mut input = b"!D other content".as_ref();
-                            //                ^= 0
+                            //                ^= 1
 
                             match $source(&mut input).read_bang_element(buf, &mut position) $(.$await)? {
                                 Err(Error::Syntax(SyntaxError::UnclosedDoctype)) => {}
@@ -1347,15 +1383,16 @@ mod test {
                                     x
                                 ),
                             }
+                            // We want to report error at `<`
                             assert_eq!(position, 0);
                         }
 
                         #[$test]
                         $($async)? fn without_space() {
                             let buf = $buf;
-                            let mut position = 0;
+                            let mut position = 1;
                             let mut input = b"!DOCTYPEother content".as_ref();
-                            //                ^= 0
+                            //                ^= 1
 
                             match $source(&mut input).read_bang_element(buf, &mut position) $(.$await)? {
                                 Err(Error::Syntax(SyntaxError::UnclosedDoctype)) => {}
@@ -1364,15 +1401,16 @@ mod test {
                                     x
                                 ),
                             }
+                            // We want to report error at `<`
                             assert_eq!(position, 0);
                         }
 
                         #[$test]
                         $($async)? fn empty() {
                             let buf = $buf;
-                            let mut position = 0;
+                            let mut position = 1;
                             let mut input = b"!DOCTYPE>other content".as_ref();
-                            //                         ^= 9
+                            //                ^= 1     ^= 10
 
                             let (ty, bytes) = $source(&mut input)
                                 .read_bang_element(buf, &mut position)
@@ -1382,15 +1420,15 @@ mod test {
                                 (ty, Bytes(bytes)),
                                 (BangType::DocType, Bytes(b"!DOCTYPE"))
                             );
-                            assert_eq!(position, 9);
+                            assert_eq!(position, 10);
                         }
 
                         #[$test]
                         $($async)? fn not_closed() {
                             let buf = $buf;
-                            let mut position = 0;
+                            let mut position = 1;
                             let mut input = b"!DOCTYPE other content".as_ref();
-                            //                ^= 0
+                            //                ^= 1
 
                             match $source(&mut input).read_bang_element(buf, &mut position) $(.$await)? {
                                 Err(Error::Syntax(SyntaxError::UnclosedDoctype)) => {}
@@ -1399,6 +1437,7 @@ mod test {
                                     x
                                 ),
                             }
+                            // We want to report error at `<`
                             assert_eq!(position, 0);
                         }
                     }
@@ -1410,9 +1449,9 @@ mod test {
                         #[$test]
                         $($async)? fn not_properly_start() {
                             let buf = $buf;
-                            let mut position = 0;
+                            let mut position = 1;
                             let mut input = b"!d other content".as_ref();
-                            //                ^= 0
+                            //                ^= 1
 
                             match $source(&mut input).read_bang_element(buf, &mut position) $(.$await)? {
                                 Err(Error::Syntax(SyntaxError::UnclosedDoctype)) => {}
@@ -1421,15 +1460,16 @@ mod test {
                                     x
                                 ),
                             }
+                            // We want to report error at `<`
                             assert_eq!(position, 0);
                         }
 
                         #[$test]
                         $($async)? fn without_space() {
                             let buf = $buf;
-                            let mut position = 0;
+                            let mut position = 1;
                             let mut input = b"!doctypeother content".as_ref();
-                            //                ^= 0
+                            //                ^= 1
 
                             match $source(&mut input).read_bang_element(buf, &mut position) $(.$await)? {
                                 Err(Error::Syntax(SyntaxError::UnclosedDoctype)) => {}
@@ -1438,15 +1478,16 @@ mod test {
                                     x
                                 ),
                             }
+                            // We want to report error at `<`
                             assert_eq!(position, 0);
                         }
 
                         #[$test]
                         $($async)? fn empty() {
                             let buf = $buf;
-                            let mut position = 0;
+                            let mut position = 1;
                             let mut input = b"!doctype>other content".as_ref();
-                            //                         ^= 9
+                            //                ^= 1     ^= 10
 
                             let (ty, bytes) = $source(&mut input)
                                 .read_bang_element(buf, &mut position)
@@ -1456,15 +1497,15 @@ mod test {
                                 (ty, Bytes(bytes)),
                                 (BangType::DocType, Bytes(b"!doctype"))
                             );
-                            assert_eq!(position, 9);
+                            assert_eq!(position, 10);
                         }
 
                         #[$test]
                         $($async)? fn not_closed() {
                             let buf = $buf;
-                            let mut position = 0;
+                            let mut position = 1;
                             let mut input = b"!doctype other content".as_ref();
-                            //                ^= 0
+                            //                ^= 1
 
                             match $source(&mut input).read_bang_element(buf, &mut position) $(.$await)? {
                                 Err(Error::Syntax(SyntaxError::UnclosedDoctype)) => {}
@@ -1473,6 +1514,7 @@ mod test {
                                     x
                                 ),
                             }
+                            // We want to report error at `<`
                             assert_eq!(position, 0);
                         }
                     }
@@ -1489,9 +1531,9 @@ mod test {
                 #[$test]
                 $($async)? fn empty() {
                     let buf = $buf;
-                    let mut position = 0;
+                    let mut position = 1;
                     let mut input = b"".as_ref();
-                    //                ^= 0
+                    //                ^= 1
 
                     match $source(&mut input).read_element(buf, &mut position) $(.$await)? {
                         Err(Error::Syntax(SyntaxError::UnclosedTag)) => {}
@@ -1500,6 +1542,7 @@ mod test {
                             x
                         ),
                     }
+                    // We want to report error at `<`
                     assert_eq!(position, 0);
                 }
 
@@ -1510,71 +1553,71 @@ mod test {
                     #[$test]
                     $($async)? fn empty_tag() {
                         let buf = $buf;
-                        let mut position = 0;
+                        let mut position = 1;
                         let mut input = b">".as_ref();
-                        //                 ^= 1
+                        //                 ^= 2
 
                         assert_eq!(
                             Bytes($source(&mut input).read_element(buf, &mut position) $(.$await)? .unwrap()),
                             Bytes(b"")
                         );
-                        assert_eq!(position, 1);
+                        assert_eq!(position, 2);
                     }
 
                     #[$test]
                     $($async)? fn normal() {
                         let buf = $buf;
-                        let mut position = 0;
+                        let mut position = 1;
                         let mut input = b"tag>".as_ref();
-                        //                    ^= 4
+                        //                    ^= 5
 
                         assert_eq!(
                             Bytes($source(&mut input).read_element(buf, &mut position) $(.$await)? .unwrap()),
                             Bytes(b"tag")
                         );
-                        assert_eq!(position, 4);
+                        assert_eq!(position, 5);
                     }
 
                     #[$test]
                     $($async)? fn empty_ns_empty_tag() {
                         let buf = $buf;
-                        let mut position = 0;
+                        let mut position = 1;
                         let mut input = b":>".as_ref();
-                        //                  ^= 2
+                        //                  ^= 3
 
                         assert_eq!(
                             Bytes($source(&mut input).read_element(buf, &mut position) $(.$await)? .unwrap()),
                             Bytes(b":")
                         );
-                        assert_eq!(position, 2);
+                        assert_eq!(position, 3);
                     }
 
                     #[$test]
                     $($async)? fn empty_ns() {
                         let buf = $buf;
-                        let mut position = 0;
+                        let mut position = 1;
                         let mut input = b":tag>".as_ref();
-                        //                     ^= 5
+                        //                     ^= 6
 
                         assert_eq!(
                             Bytes($source(&mut input).read_element(buf, &mut position) $(.$await)? .unwrap()),
                             Bytes(b":tag")
                         );
-                        assert_eq!(position, 5);
+                        assert_eq!(position, 6);
                     }
 
                     #[$test]
                     $($async)? fn with_attributes() {
                         let buf = $buf;
-                        let mut position = 0;
+                        let mut position = 1;
                         let mut input = br#"tag  attr-1=">"  attr2  =  '>'  3attr>"#.as_ref();
-                        //                                                        ^= 38
+                        //                                                        ^= 39
 
                         assert_eq!(
                             Bytes($source(&mut input).read_element(buf, &mut position) $(.$await)? .unwrap()),
                             Bytes(br#"tag  attr-1=">"  attr2  =  '>'  3attr"#)
                         );
-                        assert_eq!(position, 38);
+                        assert_eq!(position, 39);
                     }
                 }
 
@@ -1585,71 +1628,71 @@ mod test {
                     #[$test]
                     $($async)? fn empty_tag() {
                         let buf = $buf;
-                        let mut position = 0;
+                        let mut position = 1;
                         let mut input = b"/>".as_ref();
-                        //                  ^= 2
+                        //                  ^= 3
 
                         assert_eq!(
                             Bytes($source(&mut input).read_element(buf, &mut position) $(.$await)? .unwrap()),
                             Bytes(b"/")
                         );
-                        assert_eq!(position, 2);
+                        assert_eq!(position, 3);
                     }
 
                     #[$test]
                     $($async)? fn normal() {
                         let buf = $buf;
-                        let mut position = 0;
+                        let mut position = 1;
                         let mut input = b"tag/>".as_ref();
-                        //                     ^= 5
+                        //                     ^= 6
 
                         assert_eq!(
                             Bytes($source(&mut input).read_element(buf, &mut position) $(.$await)? .unwrap()),
                             Bytes(b"tag/")
                         );
-                        assert_eq!(position, 5);
+                        assert_eq!(position, 6);
                     }
 
                     #[$test]
                     $($async)? fn empty_ns_empty_tag() {
                         let buf = $buf;
-                        let mut position = 0;
+                        let mut position = 1;
                         let mut input = b":/>".as_ref();
-                        //                   ^= 3
+                        //                   ^= 4
 
                         assert_eq!(
                             Bytes($source(&mut input).read_element(buf, &mut position) $(.$await)? .unwrap()),
                             Bytes(b":/")
                         );
-                        assert_eq!(position, 3);
+                        assert_eq!(position, 4);
                     }
 
                     #[$test]
                     $($async)? fn empty_ns() {
                         let buf = $buf;
-                        let mut position = 0;
+                        let mut position = 1;
                         let mut input = b":tag/>".as_ref();
-                        //                      ^= 6
+                        //                      ^= 7
 
                         assert_eq!(
                             Bytes($source(&mut input).read_element(buf, &mut position) $(.$await)? .unwrap()),
                             Bytes(b":tag/")
                         );
-                        assert_eq!(position, 6);
+                        assert_eq!(position, 7);
                     }
 
                     #[$test]
                     $($async)? fn with_attributes() {
                         let buf = $buf;
-                        let mut position = 0;
+                        let mut position = 1;
                         let mut input = br#"tag  attr-1="/>"  attr2  =  '/>'  3attr/>"#.as_ref();
-                        //                                                           ^= 41
+                        //                                                           ^= 42
 
                         assert_eq!(
                             Bytes($source(&mut input).read_element(buf, &mut position) $(.$await)? .unwrap()),
                             Bytes(br#"tag  attr-1="/>"  attr2  =  '/>'  3attr/"#)
                         );
-                        assert_eq!(position, 41);
+                        assert_eq!(position, 42);
                     }
                 }
             }
diff --git a/src/reader/slice_reader.rs b/src/reader/slice_reader.rs
index 1042130..eb3961c 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())?;
+        let bang_type = BangType::new(self[1..].first().copied(), position)?;
 
         if let Some((bytes, i)) = bang_type.parse(&[], self) {
             *position += i;
@@ -294,8 +294,10 @@ impl<'a> XmlSource<'a, ()> for &'a [u8] {
             return Ok((bang_type, bytes));
         }
 
-        // Note: Do not update position, so the error points to
-        // somewhere sane rather than at the EOF
+        // <!....EOF
+        //  ^^^^^ - `self` does not contains `<`, but we want to report error at `<`,
+        //          so we move offset to it (+1 for `<`)
+        *position -= 1;
         Err(bang_type.to_err())
     }
 
@@ -309,8 +311,10 @@ impl<'a> XmlSource<'a, ()> for &'a [u8] {
             return Ok(bytes);
         }
 
-        // Note: Do not update position, so the error points to a sane place
-        // rather than at the EOF.
+        // <.....EOF
+        //  ^^^^^ - `self` does not contains `<`, but we want to report error at `<`,
+        //          so we move offset to it (+1 for `<`)
+        *position -= 1;
         Err(Error::Syntax(SyntaxError::UnclosedTag))
     }
 
diff --git a/src/reader/state.rs b/src/reader/state.rs
index 3597b3d..9ac62c9 100644
--- a/src/reader/state.rs
+++ b/src/reader/state.rs
@@ -89,11 +89,16 @@ impl ReaderState {
                 debug_assert!(buf.ends_with(b"--"));
                 if self.config.check_comments {
                     // search if '--' not in comments
-                    if let Some(p) = memchr::memchr_iter(b'-', &buf[3..len - 2])
-                        .position(|p| buf[3 + p + 1] == b'-')
-                    {
-                        self.offset += len - p;
-                        return Err(Error::IllFormed(IllFormedError::DoubleHyphenInComment));
+                    let mut haystack = &buf[3..len - 2];
+                    let mut off = 0;
+                    while let Some(p) = memchr::memchr(b'-', haystack) {
+                        off += p + 1;
+                        // if next byte after `-` is also `-`, return an error
+                        if buf[3 + off] == b'-' {
+                            self.offset -= len - 2 - p;
+                            return Err(Error::IllFormed(IllFormedError::DoubleHyphenInComment));
+                        }
+                        haystack = &haystack[p + 1..];
                     }
                 }
                 Ok(Event::Comment(BytesText::wrap(
@@ -109,19 +114,27 @@ impl ReaderState {
                 )))
             }
             BangType::DocType if uncased_starts_with(buf, b"!DOCTYPE") => {
-                let start = buf[8..]
-                    .iter()
-                    .position(|b| !is_whitespace(*b))
-                    .unwrap_or(len - 8);
-                if start + 8 >= len {
-                    return Err(Error::EmptyDocType);
+                match buf[8..].iter().position(|&b| !is_whitespace(b)) {
+                    Some(start) => Ok(Event::DocType(BytesText::wrap(
+                        &buf[8 + start..],
+                        self.decoder(),
+                    ))),
+                    None => {
+                        // 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;
+                        return Err(Error::IllFormed(IllFormedError::MissingDoctypeName));
+                    }
                 }
-                Ok(Event::DocType(BytesText::wrap(
-                    &buf[8 + start..],
-                    self.decoder(),
-                )))
             }
-            _ => Err(bang_type.to_err()),
+            _ => {
+                // <!....
+                //  ^^^^^ - `buf` does not contains `<`, but we want to report error at `<`,
+                //          so we move offset to it (+1 for `<`)
+                self.offset -= 1;
+                Err(bang_type.to_err())
+            }
         }
     }
 
@@ -157,7 +170,7 @@ impl ReaderState {
                         // Report error at start of the end tag at `<` character
                         // +2 for `<` and `>`
                         self.offset -= buf.len() + 2;
-                        return Err(Error::IllFormed(IllFormedError::MismatchedEnd {
+                        return Err(Error::IllFormed(IllFormedError::MismatchedEndTag {
                             expected,
                             found: decoder.decode(name).unwrap_or_default().into_owned(),
                         }));
@@ -170,7 +183,7 @@ impl ReaderState {
                 // Report error at start of the end tag at `<` character
                 // +2 for `<` and `>`
                 self.offset -= buf.len() + 2;
-                return Err(Error::IllFormed(IllFormedError::UnmatchedEnd(
+                return Err(Error::IllFormed(IllFormedError::UnmatchedEndTag(
                     decoder.decode(name).unwrap_or_default().into_owned(),
                 )));
             }
@@ -179,13 +192,23 @@ impl ReaderState {
         Ok(Event::End(BytesEnd::wrap(name.into())))
     }
 
-    /// reads `BytesElement` starting with a `?`,
-    /// return `Decl` or `PI` event
+    /// `buf` contains data between `<` and `>` and the first byte is `?`.
+    /// `self.offset` already after the `>`
+    ///
+    /// Returns `Decl` or `PI` event
     pub fn emit_question_mark<'b>(&mut self, buf: &'b [u8]) -> Result<Event<'b>> {
+        debug_assert!(buf.len() > 0);
+        debug_assert_eq!(buf[0], b'?');
+
         let len = buf.len();
-        if len > 2 && buf[len - 1] == b'?' {
-            if len > 5 && &buf[1..4] == b"xml" && is_whitespace(buf[4]) {
-                let event = BytesDecl::from_start(BytesStart::wrap(&buf[1..len - 1], 3));
+        // We accept at least <??>
+        //                     ~~ - len = 2
+        if len > 1 && buf[len - 1] == b'?' {
+            let content = &buf[1..len - 1];
+            let len = content.len();
+
+            if content.starts_with(b"xml") && (len == 3 || is_whitespace(content[3])) {
+                let event = BytesDecl::from_start(BytesStart::wrap(content, 3));
 
                 // Try getting encoding from the declaration event
                 #[cfg(feature = "encoding")]
@@ -197,10 +220,13 @@ impl ReaderState {
 
                 Ok(Event::Decl(event))
             } else {
-                Ok(Event::PI(BytesText::wrap(&buf[1..len - 1], self.decoder())))
+                Ok(Event::PI(BytesText::wrap(content, self.decoder())))
             }
         } else {
-            self.offset -= len;
+            // <?....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;
             Err(Error::Syntax(SyntaxError::UnclosedPIOrXmlDecl))
         }
     }


Test patch:
diff --git a/tests/fuzzing.rs b/tests/fuzzing.rs
index 9189d66..4324253 100644
--- a/tests/fuzzing.rs
+++ b/tests/fuzzing.rs
@@ -1,8 +1,8 @@
 //! Cases that was found by fuzzing
 
+use quick_xml::errors::{Error, IllFormedError};
 use quick_xml::events::Event;
 use quick_xml::reader::Reader;
-use quick_xml::Error;
 
 #[test]
 fn fuzz_53() {
@@ -56,7 +56,7 @@ fn fuzz_empty_doctype() {
     let mut buf = Vec::new();
     assert!(matches!(
         reader.read_event_into(&mut buf).unwrap_err(),
-        Error::EmptyDocType
+        Error::IllFormed(IllFormedError::MissingDoctypeName)
     ));
     assert_eq!(reader.read_event_into(&mut buf).unwrap(), Event::Eof);
 }
diff --git a/tests/issues.rs b/tests/issues.rs
index 320b528..7f93025 100644
--- a/tests/issues.rs
+++ b/tests/issues.rs
@@ -146,7 +146,7 @@ mod issue514 {
         match reader.read_event() {
             Err(Error::IllFormed(cause)) => assert_eq!(
                 cause,
-                IllFormedError::MismatchedEnd {
+                IllFormedError::MismatchedEndTag {
                     expected: "some-tag".into(),
                     found: "other-tag".into(),
                 }
diff --git a/tests/reader-config.rs b/tests/reader-config.rs
index 7701b0d..d872467 100644
--- a/tests/reader-config.rs
+++ b/tests/reader-config.rs
@@ -330,7 +330,7 @@ mod check_end_names {
             match reader.read_event() {
                 Err(Error::IllFormed(cause)) => assert_eq!(
                     cause,
-                    IllFormedError::MismatchedEnd {
+                    IllFormedError::MismatchedEndTag {
                         expected: "tag".into(),
                         found: "mismatched".into(),
                     }
@@ -421,7 +421,7 @@ mod trim_markup_names_in_closing_tags {
             match reader.read_event() {
                 Err(Error::IllFormed(cause)) => assert_eq!(
                     cause,
-                    IllFormedError::MismatchedEnd {
+                    IllFormedError::MismatchedEndTag {
                         expected: "root".into(),
                         found: "root \t\r\n".into(),
                     }
diff --git a/tests/reader-errors.rs b/tests/reader-errors.rs
new file mode 100644
index 0000000..698401f
--- /dev/null
+++ b/tests/reader-errors.rs
@@ -0,0 +1,646 @@
+//! Contains tests that produces errors during parsing XML.
+
+use quick_xml::errors::{Error, SyntaxError};
+use quick_xml::events::{BytesCData, BytesDecl, BytesEnd, BytesStart, BytesText, Event};
+use quick_xml::reader::{NsReader, Reader};
+
+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);
+
+                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 = 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
+                );
+
+                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
+                );
+            }
+        }
+    };
+}
+
+mod syntax {
+    use super::*;
+
+    macro_rules! err {
+        ($test:ident($xml:literal) => $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);
+                        }
+                        x => panic!("Expected `Err(Syntax(_))`, but got {:?}", x),
+                    }
+                    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);
+                        }
+                        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
+                    );
+                }
+
+                #[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);
+                        }
+                        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
+                    );
+
+                    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);
+                        }
+                        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
+                    );
+                }
+
+                #[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);
+                        }
+                        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
+                    );
+
+                    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);
+                        }
+                        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
+                    );
+                }
+            }
+        };
+    }
+
+    mod tag {
+        use super::*;
+
+        err!(unclosed1("<")   => SyntaxError::UnclosedTag);
+        err!(unclosed2("</")  => SyntaxError::UnclosedTag);
+        err!(unclosed3("<x")  => SyntaxError::UnclosedTag);
+        err!(unclosed4("< ")  => SyntaxError::UnclosedTag);
+        err!(unclosed5("<\t") => SyntaxError::UnclosedTag);
+        err!(unclosed6("<\r") => SyntaxError::UnclosedTag);
+        err!(unclosed7("<\n") => SyntaxError::UnclosedTag);
+
+        /// Closed tags can be tested only in pair with open tags, because otherwise
+        /// `IllFormedError::UnmatchedEndTag` will be raised
+        mod normal {
+            use super::*;
+            use pretty_assertions::assert_eq;
+
+            #[test]
+            fn borrowed() {
+                let mut reader = Reader::from_str("<></>");
+                assert_eq!(
+                    reader.read_event().unwrap(),
+                    Event::Start(BytesStart::new(""))
+                );
+                assert_eq!(reader.read_event().unwrap(), Event::End(BytesEnd::new("")));
+            }
+
+            #[test]
+            fn buffered() {
+                let mut buf = Vec::new();
+                let mut reader = Reader::from_str("<></>");
+                assert_eq!(
+                    reader.read_event_into(&mut buf).unwrap(),
+                    Event::Start(BytesStart::new(""))
+                );
+                assert_eq!(
+                    reader.read_event_into(&mut buf).unwrap(),
+                    Event::End(BytesEnd::new(""))
+                );
+            }
+
+            #[cfg(feature = "async-tokio")]
+            #[tokio::test]
+            async fn async_tokio() {
+                let mut buf = Vec::new();
+                let mut reader = Reader::from_str("<></>");
+                assert_eq!(
+                    reader.read_event_into_async(&mut buf).await.unwrap(),
+                    Event::Start(BytesStart::new(""))
+                );
+                assert_eq!(
+                    reader.read_event_into_async(&mut buf).await.unwrap(),
+                    Event::End(BytesEnd::new(""))
+                );
+            }
+        }
+    }
+
+    err!(unclosed_bang1("<!")  => SyntaxError::InvalidBangMarkup);
+    err!(unclosed_bang2("<!>") => 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);
+
+        ok!(normal("<!---->") => Event::Comment(BytesText::new("")));
+    }
+
+    /// https://www.w3.org/TR/xml11/#NT-CDSect
+    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);
+
+        ok!(normal("<![CDATA[]]>") => Event::CData(BytesCData::new("")));
+    }
+
+    /// According to the grammar, only upper-case letters allowed for DOCTYPE writing.
+    ///
+    /// https://www.w3.org/TR/xml11/#NT-doctypedecl
+    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);
+
+        // 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.
+        ok!(normal("<!DOCTYPE e>") => Event::DocType(BytesText::new("e")));
+    }
+
+    /// https://www.w3.org/TR/xml11/#NT-PI
+    mod pi {
+        use super::*;
+
+        err!(unclosed1("<?")    => SyntaxError::UnclosedPIOrXmlDecl);
+        err!(unclosed2("<??")   => SyntaxError::UnclosedPIOrXmlDecl);
+        err!(unclosed3("<?>")   => SyntaxError::UnclosedPIOrXmlDecl);
+        err!(unclosed4("<?<")   => SyntaxError::UnclosedPIOrXmlDecl);
+        err!(unclosed5("<?&")   => SyntaxError::UnclosedPIOrXmlDecl);
+        err!(unclosed6("<?p")   => SyntaxError::UnclosedPIOrXmlDecl);
+        err!(unclosed7("<? ")   => SyntaxError::UnclosedPIOrXmlDecl);
+        err!(unclosed8("<?\t")  => SyntaxError::UnclosedPIOrXmlDecl);
+        err!(unclosed9("<?\r")  => SyntaxError::UnclosedPIOrXmlDecl);
+        err!(unclosed10("<?\n") => SyntaxError::UnclosedPIOrXmlDecl);
+
+        // According to the grammar, processing instruction MUST contain a non-empty
+        // target name, but we do not consider this as a _syntax_ error.
+        ok!(normal_empty("<??>")    => Event::PI(BytesText::new("")));
+        ok!(normal_xmlx("<?xmlx?>") => Event::PI(BytesText::new("xmlx")));
+    }
+
+    /// https://www.w3.org/TR/xml11/#NT-prolog
+    mod decl {
+        use super::*;
+
+        err!(unclosed1("<?x")    => SyntaxError::UnclosedPIOrXmlDecl);
+        err!(unclosed2("<?xm")   => SyntaxError::UnclosedPIOrXmlDecl);
+        err!(unclosed3("<?xml")  => SyntaxError::UnclosedPIOrXmlDecl);
+        err!(unclosed4("<?xml?") => SyntaxError::UnclosedPIOrXmlDecl);
+
+        // According to the grammar, XML declaration MUST contain at least one space
+        // and `version` attribute, but we do not consider this as a _syntax_ error.
+        ok!(normal1("<?xml?>")   => Event::Decl(BytesDecl::from_start(BytesStart::new("xml"))));
+        ok!(normal2("<?xml ?>")  => Event::Decl(BytesDecl::from_start(BytesStart::from_content("xml ", 3))));
+        ok!(normal3("<?xml\t?>") => Event::Decl(BytesDecl::from_start(BytesStart::from_content("xml\t", 3))));
+        ok!(normal4("<?xml\r?>") => Event::Decl(BytesDecl::from_start(BytesStart::from_content("xml\r", 3))));
+        ok!(normal5("<?xml\n?>") => Event::Decl(BytesDecl::from_start(BytesStart::from_content("xml\n", 3))));
+    }
+}
+
+mod ill_formed {
+    use super::*;
+    use quick_xml::errors::IllFormedError;
+
+    macro_rules! err {
+        ($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);
+                        }
+                        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"))
+                    );
+
+                    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);
+                        }
+                        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"))
+                    );
+                }
+
+                #[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);
+                        }
+                        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"))
+                    );
+
+                    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);
+                        }
+                        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"))
+                    );
+                }
+
+                #[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);
+                        }
+                        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"))
+                    );
+
+                    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);
+                        }
+                        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"))
+                    );
+                }
+            }
+        };
+    }
+
+    /// Performs 3 reads, the first and third ones should be successful
+    macro_rules! err2 {
+        ($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);
+                        }
+                        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"))
+                    );
+
+                    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);
+                        }
+                        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"))
+                    );
+                }
+
+                #[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);
+                        }
+                        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"))
+                    );
+
+                    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!(
+                        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);
+                        }
+                        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"))
+                    );
+
+                    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!(
+                        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"))
+                    );
+                }
+            }
+        };
+    }
+
+    // IllFormedError::MissingDeclVersion is generated lazily when you call `BytesDecl::version()`
+
+    err!(missing_doctype_name1("<!DOCTYPE>") => 9: IllFormedError::MissingDoctypeName);
+    //                                  ^= 9
+    err!(missing_doctype_name2("<!DOCTYPE \t\r\n>") => 13: IllFormedError::MissingDoctypeName);
+    //                                         ^= 13
+    ok!(missing_doctype_name3("<!DOCTYPE \t\r\nx>") => Event::DocType(BytesText::new("x")));
+
+    err!(unmatched_end_tag1("</>") => 0: IllFormedError::UnmatchedEndTag("".to_string()));
+    err!(unmatched_end_tag2("</end>") => 0: IllFormedError::UnmatchedEndTag("end".to_string()));
+    err!(unmatched_end_tag3("</end >") => 0: IllFormedError::UnmatchedEndTag("end".to_string()));
+
+    ok!(mismatched_end_tag1("<start></start>") => Event::Start(BytesStart::new("start")));
+    err2!(mismatched_end_tag2("<start></>") => 7: IllFormedError::MismatchedEndTag {
+        //                        ^= 7
+        expected: "start".to_string(),
+        found: "".to_string(),
+    });
+    err2!(mismatched_end_tag3("<start></end>") => 7: IllFormedError::MismatchedEndTag {
+        //                        ^= 7
+        expected: "start".to_string(),
+        found: "end".to_string(),
+    });
+    err2!(mismatched_end_tag4("<start></end >") => 7: IllFormedError::MismatchedEndTag {
+        //                        ^= 7
+        expected: "start".to_string(),
+        found: "end".to_string(),
+    });
+
+    ok!(double_hyphen_in_comment1("<!---->") => Event::Comment(BytesText::new("")));
+    err!(double_hyphen_in_comment2("<!----->") => 4: IllFormedError::DoubleHyphenInComment);
+    //                                  ^= 4
+    err!(double_hyphen_in_comment3("<!-- --->") => 5: IllFormedError::DoubleHyphenInComment);
+    //                                   ^= 5
+    err!(double_hyphen_in_comment4("<!-- -- -->") => 5: IllFormedError::DoubleHyphenInComment);
+    //                                   ^= 5
+}
diff --git a/tests/serde-de.rs b/tests/serde-de.rs
index 1aa00c9..f0a4d07 100644
--- a/tests/serde-de.rs
+++ b/tests/serde-de.rs
@@ -568,7 +568,7 @@ macro_rules! maplike_errors {
 
                 match data {
                     Err(DeError::InvalidXml(Error::IllFormed(cause))) => {
-                        assert_eq!(cause, IllFormedError::MissedEnd("root".into()))
+                        assert_eq!(cause, IllFormedError::MissingEndTag("root".into()))
                     }
                     x => panic!(
                         "Expected `Err(InvalidXml(IllFormed(_)))`, but got `{:?}`",
@@ -583,7 +583,7 @@ macro_rules! maplike_errors {
 
                 match data {
                     Err(DeError::InvalidXml(Error::IllFormed(cause))) => {
-                        assert_eq!(cause, IllFormedError::MissedEnd("root".into()))
+                        assert_eq!(cause, IllFormedError::MissingEndTag("root".into()))
                     }
                     x => panic!(
                         "Expected `Err(InvalidXml(IllFormed(_)))`, but got `{:?}`",
@@ -598,7 +598,7 @@ macro_rules! maplike_errors {
 
                 match data {
                     Err(DeError::InvalidXml(Error::IllFormed(cause))) => {
-                        assert_eq!(cause, IllFormedError::MissedEnd("root".into()))
+                        assert_eq!(cause, IllFormedError::MissingEndTag("root".into()))
                     }
                     x => panic!(
                         "Expected `Err(InvalidXml(IllFormed(_)))`, but got `{:?}`",
@@ -611,7 +611,7 @@ macro_rules! maplike_errors {
 
                 match data {
                     Err(DeError::InvalidXml(Error::IllFormed(cause))) => {
-                        assert_eq!(cause, IllFormedError::MissedEnd("root".into()))
+                        assert_eq!(cause, IllFormedError::MissingEndTag("root".into()))
                     }
                     x => panic!(
                         "Expected `Err(InvalidXml(IllFormed(_)))`, but got `{:?}`",
@@ -627,7 +627,7 @@ macro_rules! maplike_errors {
 
                 match data {
                     Err(DeError::InvalidXml(Error::IllFormed(cause))) => {
-                        assert_eq!(cause, IllFormedError::MissedEnd("string".into()))
+                        assert_eq!(cause, IllFormedError::MissingEndTag("string".into()))
                     }
                     x => panic!(
                         "Expected `Err(InvalidXml(IllFormed(_)))`, but got `{:?}`",
@@ -640,7 +640,7 @@ macro_rules! maplike_errors {
 
                 match data {
                     Err(DeError::InvalidXml(Error::IllFormed(cause))) => {
-                        assert_eq!(cause, IllFormedError::MissedEnd("string".into()))
+                        assert_eq!(cause, IllFormedError::MissingEndTag("string".into()))
                     }
                     x => panic!(
                         "Expected `Err(InvalidXml(IllFormed(_)))`, but got `{:?}`",
@@ -664,7 +664,7 @@ macro_rules! maplike_errors {
                 match data {
                     Err(DeError::InvalidXml(Error::IllFormed(cause))) => assert_eq!(
                         cause,
-                        IllFormedError::MismatchedEnd {
+                        IllFormedError::MismatchedEndTag {
                             expected: "root".into(),
                             found: "mismatched".into(),
                         }
@@ -686,7 +686,7 @@ macro_rules! maplike_errors {
                 match data {
                     Err(DeError::InvalidXml(Error::IllFormed(cause))) => assert_eq!(
                         cause,
-                        IllFormedError::MismatchedEnd {
+                        IllFormedError::MismatchedEndTag {
                             expected: "root".into(),
                             found: "mismatched".into(),
                         }
@@ -708,7 +708,7 @@ macro_rules! maplike_errors {
                 match data {
                     Err(DeError::InvalidXml(Error::IllFormed(cause))) => assert_eq!(
                         cause,
-                        IllFormedError::MismatchedEnd {
+                        IllFormedError::MismatchedEndTag {
                             expected: "root".into(),
                             found: "mismatched".into(),
                         }
@@ -730,7 +730,7 @@ macro_rules! maplike_errors {
                 match data {
                     Err(DeError::InvalidXml(Error::IllFormed(cause))) => assert_eq!(
                         cause,
-                        IllFormedError::MismatchedEnd {
+                        IllFormedError::MismatchedEndTag {
                             expected: "string".into(),
                             found: "mismatched".into(),
                         }
diff --git a/tests/unit_tests.rs b/tests/unit_tests.rs
index cbb9633..9dec515 100644
--- a/tests/unit_tests.rs
+++ b/tests/unit_tests.rs
@@ -53,13 +53,6 @@ macro_rules! next_eq {
     };
 }
 
-#[test]
-fn test_start() {
-    let mut r = Reader::from_str("<a>");
-    r.config_mut().trim_text(true);
-    next_eq!(r, Start, b"a");
-}
-
 #[test]
 fn test_start_end() {
     let mut r = Reader::from_str("<a></a>");
@@ -389,48 +382,6 @@ fn test_offset_err_end_element() {
     }
 }
 
-#[test]
-fn test_offset_err_comment() {
-    let mut r = Reader::from_str("<a><!--b>");
-    r.config_mut().trim_text(true);
-
-    next_eq!(r, Start, b"a");
-    assert_eq!(r.buffer_position(), 3);
-
-    match r.read_event() {
-        // error at char 4: no closing --> tag found
-        Err(e) => assert_eq!(
-            r.buffer_position(),
-            4,
-            "expecting buf_pos = 4, found {}, err {:?}",
-            r.buffer_position(),
-            e
-        ),
-        e => panic!("expecting error, found {:?}", e),
-    }
-}
-
-#[test]
-fn test_offset_err_comment_trim_text() {
-    let mut r = Reader::from_str("<a>\r\n <!--b>");
-    r.config_mut().trim_text(true);
-
-    next_eq!(r, Start, b"a");
-    assert_eq!(r.buffer_position(), 3);
-
-    match r.read_event() {
-        // error at char 7: no closing --> tag found
-        Err(e) => assert_eq!(
-            r.buffer_position(),
-            7,
-            "expecting buf_pos = 7, 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>");
diff --git a/tests/xmlrs_reader_tests.rs b/tests/xmlrs_reader_tests.rs
index a13906e..d438f24 100644
--- a/tests/xmlrs_reader_tests.rs
+++ b/tests/xmlrs_reader_tests.rs
@@ -178,24 +178,6 @@ fn sample_ns_short() {
     );
 }
 
-#[test]
-fn eof_1() {
-    test(
-        r#"<?xml"#,
-        r#"Error: syntax error: processing instruction or xml declaration not closed: `?>` not found before end of input"#,
-        true,
-    );
-}
-
-#[test]
-fn bad_1() {
-    test(
-        r#"<?xml&.,"#,
-        r#"1:6 Error: syntax error: processing instruction or xml declaration not closed: `?>` not found before end of input"#,
-        true,
-    );
-}
-
 #[test]
 fn tabs_1() {
     test(