Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

rustdoc: Avoid panic when parsing codeblocks for playground links #67818

Merged
merged 1 commit into from
Jan 5, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
148 changes: 72 additions & 76 deletions src/librustdoc/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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`.
Expand Down Expand Up @@ -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>,
Expand Down Expand Up @@ -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
Expand Down
21 changes: 21 additions & 0 deletions src/test/rustdoc/playground-syntax-error.rs
Original file line number Diff line number Diff line change
@@ -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'