about summary refs log tree commit diff
diff options
context:
space:
mode:
authorOliver Middleton <olliemail27@gmail.com>2020-01-02 23:34:00 +0000
committerOliver Middleton <olliemail27@gmail.com>2020-01-04 18:42:06 +0000
commitefb876f5578f874c3e19462aac14dc262232c4ad (patch)
tree43d17714d0a21b22a8c5c623a19895e81e2c856e
parentcd8377d37e9bc47f9a5a982c41705a7800cbb51d (diff)
downloadrust-efb876f5578f874c3e19462aac14dc262232c4ad.tar.gz
rust-efb876f5578f874c3e19462aac14dc262232c4ad.zip
rustdoc: Avoid panic when parsing codeblocks for playground links
`make_test` is also called when parsing codeblocks for the playground links so it should handle unwinds from the parser internally.
-rw-r--r--src/librustdoc/test.rs148
-rw-r--r--src/test/rustdoc/playground-syntax-error.rs21
2 files changed, 93 insertions, 76 deletions
diff --git a/src/librustdoc/test.rs b/src/librustdoc/test.rs
index d7285b9d0bb..db66b7530b2 100644
--- a/src/librustdoc/test.rs
+++ b/src/librustdoc/test.rs
@@ -202,17 +202,7 @@ fn run_test(
     opts: &TestOptions,
     edition: Edition,
 ) -> Result<(), TestFailure> {
-    let (test, line_offset) = match panic::catch_unwind(|| {
-        make_test(test, Some(cratename), as_test_harness, opts, edition)
-    }) {
-        Ok((test, line_offset)) => (test, line_offset),
-        Err(cause) if cause.is::<errors::FatalErrorMarker>() => {
-            // If the parser used by `make_test` panicked due to a fatal error, pass the test code
-            // through unchanged. The error will be reported during compilation.
-            (test.to_owned(), 0)
-        }
-        Err(cause) => panic::resume_unwind(cause),
-    };
+    let (test, line_offset) = make_test(test, Some(cratename), as_test_harness, opts, edition);
 
     // FIXME(#44940): if doctests ever support path remapping, then this filename
     // needs to be the result of `SourceMap::span_to_unmapped_path`.
@@ -362,11 +352,6 @@ fn run_test(
 
 /// Transforms a test into code that can be compiled into a Rust binary, and returns the number of
 /// lines before the test code begins.
-///
-/// # Panics
-///
-/// This function uses the compiler's parser internally. The parser will panic if it encounters a
-/// fatal error while parsing the test.
 pub fn make_test(
     s: &str,
     cratename: Option<&str>,
@@ -401,83 +386,94 @@ pub fn make_test(
 
     // Uses libsyntax to parse the doctest and find if there's a main fn and the extern
     // crate already is included.
-    let (already_has_main, already_has_extern_crate, found_macro) = with_globals(edition, || {
-        use errors::emitter::EmitterWriter;
-        use errors::Handler;
-        use rustc_parse::maybe_new_parser_from_source_str;
-        use rustc_span::source_map::FilePathMapping;
-        use syntax::sess::ParseSess;
-
-        let filename = FileName::anon_source_code(s);
-        let source = crates + &everything_else;
-
-        // Any errors in parsing should also appear when the doctest is compiled for real, so just
-        // send all the errors that libsyntax emits directly into a `Sink` instead of stderr.
-        let cm = Lrc::new(SourceMap::new(FilePathMapping::empty()));
-        let emitter = EmitterWriter::new(box io::sink(), None, false, false, false, None, false);
-        // FIXME(misdreavus): pass `-Z treat-err-as-bug` to the doctest parser
-        let handler = Handler::with_emitter(false, None, box emitter);
-        let sess = ParseSess::with_span_handler(handler, cm);
-
-        let mut found_main = false;
-        let mut found_extern_crate = cratename.is_none();
-        let mut found_macro = false;
-
-        let mut parser = match maybe_new_parser_from_source_str(&sess, filename, source) {
-            Ok(p) => p,
-            Err(errs) => {
-                for mut err in errs {
-                    err.cancel();
+    let result = rustc_driver::catch_fatal_errors(|| {
+        with_globals(edition, || {
+            use errors::emitter::EmitterWriter;
+            use errors::Handler;
+            use rustc_parse::maybe_new_parser_from_source_str;
+            use rustc_span::source_map::FilePathMapping;
+            use syntax::sess::ParseSess;
+
+            let filename = FileName::anon_source_code(s);
+            let source = crates + &everything_else;
+
+            // Any errors in parsing should also appear when the doctest is compiled for real, so just
+            // send all the errors that libsyntax emits directly into a `Sink` instead of stderr.
+            let cm = Lrc::new(SourceMap::new(FilePathMapping::empty()));
+            let emitter =
+                EmitterWriter::new(box io::sink(), None, false, false, false, None, false);
+            // FIXME(misdreavus): pass `-Z treat-err-as-bug` to the doctest parser
+            let handler = Handler::with_emitter(false, None, box emitter);
+            let sess = ParseSess::with_span_handler(handler, cm);
+
+            let mut found_main = false;
+            let mut found_extern_crate = cratename.is_none();
+            let mut found_macro = false;
+
+            let mut parser = match maybe_new_parser_from_source_str(&sess, filename, source) {
+                Ok(p) => p,
+                Err(errs) => {
+                    for mut err in errs {
+                        err.cancel();
+                    }
+
+                    return (found_main, found_extern_crate, found_macro);
                 }
+            };
+
+            loop {
+                match parser.parse_item() {
+                    Ok(Some(item)) => {
+                        if !found_main {
+                            if let ast::ItemKind::Fn(..) = item.kind {
+                                if item.ident.name == sym::main {
+                                    found_main = true;
+                                }
+                            }
+                        }
 
-                return (found_main, found_extern_crate, found_macro);
-            }
-        };
+                        if !found_extern_crate {
+                            if let ast::ItemKind::ExternCrate(original) = item.kind {
+                                // This code will never be reached if `cratename` is none because
+                                // `found_extern_crate` is initialized to `true` if it is none.
+                                let cratename = cratename.unwrap();
 
-        loop {
-            match parser.parse_item() {
-                Ok(Some(item)) => {
-                    if !found_main {
-                        if let ast::ItemKind::Fn(..) = item.kind {
-                            if item.ident.name == sym::main {
-                                found_main = true;
+                                match original {
+                                    Some(name) => found_extern_crate = name.as_str() == cratename,
+                                    None => found_extern_crate = item.ident.as_str() == cratename,
+                                }
                             }
                         }
-                    }
-
-                    if !found_extern_crate {
-                        if let ast::ItemKind::ExternCrate(original) = item.kind {
-                            // This code will never be reached if `cratename` is none because
-                            // `found_extern_crate` is initialized to `true` if it is none.
-                            let cratename = cratename.unwrap();
 
-                            match original {
-                                Some(name) => found_extern_crate = name.as_str() == cratename,
-                                None => found_extern_crate = item.ident.as_str() == cratename,
+                        if !found_macro {
+                            if let ast::ItemKind::Mac(..) = item.kind {
+                                found_macro = true;
                             }
                         }
-                    }
 
-                    if !found_macro {
-                        if let ast::ItemKind::Mac(..) = item.kind {
-                            found_macro = true;
+                        if found_main && found_extern_crate {
+                            break;
                         }
                     }
-
-                    if found_main && found_extern_crate {
+                    Ok(None) => break,
+                    Err(mut e) => {
+                        e.cancel();
                         break;
                     }
                 }
-                Ok(None) => break,
-                Err(mut e) => {
-                    e.cancel();
-                    break;
-                }
             }
-        }
 
-        (found_main, found_extern_crate, found_macro)
+            (found_main, found_extern_crate, found_macro)
+        })
     });
+    let (already_has_main, already_has_extern_crate, found_macro) = match result {
+        Ok(result) => result,
+        Err(ErrorReported) => {
+            // If the parser panicked due to a fatal error, pass the test code through unchanged.
+            // The error will be reported during compilation.
+            return (s.to_owned(), 0);
+        }
+    };
 
     // If a doctest's `fn main` is being masked by a wrapper macro, the parsing loop above won't
     // see it. In that case, run the old text-based scan to see if they at least have a main
diff --git a/src/test/rustdoc/playground-syntax-error.rs b/src/test/rustdoc/playground-syntax-error.rs
new file mode 100644
index 00000000000..8918ae874f8
--- /dev/null
+++ b/src/test/rustdoc/playground-syntax-error.rs
@@ -0,0 +1,21 @@
+#![crate_name = "foo"]
+#![doc(html_playground_url = "https://play.rust-lang.org/")]
+
+/// bar docs
+///
+/// ```edition2015
+/// use std::future::Future;
+/// use std::pin::Pin;
+/// fn foo_recursive(n: usize) -> Pin<Box<dyn Future<Output = ()>>> {
+///     Box::pin(async move {
+///         if n > 0 {
+///             foo_recursive(n - 1).await;
+///         }
+///     })
+/// }
+/// ```
+pub fn bar() {}
+
+// @has foo/fn.bar.html
+// @has - '//a[@class="test-arrow"]' "Run"
+// @has - '//*[@class="docblock"]' 'foo_recursive'