Skip to content

Commit 3548d98

Browse files
authored
Rollup merge of #67818 - ollie27:rustdoc_playground_syntax_error, r=GuillaumeGomez
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. Fixes #63016 r? @GuillaumeGomez
2 parents 7785834 + efb876f commit 3548d98

File tree

2 files changed

+93
-76
lines changed

2 files changed

+93
-76
lines changed

src/librustdoc/test.rs

+72-76
Original file line numberDiff line numberDiff line change
@@ -202,17 +202,7 @@ fn run_test(
202202
opts: &TestOptions,
203203
edition: Edition,
204204
) -> Result<(), TestFailure> {
205-
let (test, line_offset) = match panic::catch_unwind(|| {
206-
make_test(test, Some(cratename), as_test_harness, opts, edition)
207-
}) {
208-
Ok((test, line_offset)) => (test, line_offset),
209-
Err(cause) if cause.is::<errors::FatalErrorMarker>() => {
210-
// If the parser used by `make_test` panicked due to a fatal error, pass the test code
211-
// through unchanged. The error will be reported during compilation.
212-
(test.to_owned(), 0)
213-
}
214-
Err(cause) => panic::resume_unwind(cause),
215-
};
205+
let (test, line_offset) = make_test(test, Some(cratename), as_test_harness, opts, edition);
216206

217207
// FIXME(#44940): if doctests ever support path remapping, then this filename
218208
// needs to be the result of `SourceMap::span_to_unmapped_path`.
@@ -362,11 +352,6 @@ fn run_test(
362352

363353
/// Transforms a test into code that can be compiled into a Rust binary, and returns the number of
364354
/// lines before the test code begins.
365-
///
366-
/// # Panics
367-
///
368-
/// This function uses the compiler's parser internally. The parser will panic if it encounters a
369-
/// fatal error while parsing the test.
370355
pub fn make_test(
371356
s: &str,
372357
cratename: Option<&str>,
@@ -401,83 +386,94 @@ pub fn make_test(
401386

402387
// Uses libsyntax to parse the doctest and find if there's a main fn and the extern
403388
// crate already is included.
404-
let (already_has_main, already_has_extern_crate, found_macro) = with_globals(edition, || {
405-
use errors::emitter::EmitterWriter;
406-
use errors::Handler;
407-
use rustc_parse::maybe_new_parser_from_source_str;
408-
use rustc_span::source_map::FilePathMapping;
409-
use syntax::sess::ParseSess;
410-
411-
let filename = FileName::anon_source_code(s);
412-
let source = crates + &everything_else;
413-
414-
// Any errors in parsing should also appear when the doctest is compiled for real, so just
415-
// send all the errors that libsyntax emits directly into a `Sink` instead of stderr.
416-
let cm = Lrc::new(SourceMap::new(FilePathMapping::empty()));
417-
let emitter = EmitterWriter::new(box io::sink(), None, false, false, false, None, false);
418-
// FIXME(misdreavus): pass `-Z treat-err-as-bug` to the doctest parser
419-
let handler = Handler::with_emitter(false, None, box emitter);
420-
let sess = ParseSess::with_span_handler(handler, cm);
421-
422-
let mut found_main = false;
423-
let mut found_extern_crate = cratename.is_none();
424-
let mut found_macro = false;
425-
426-
let mut parser = match maybe_new_parser_from_source_str(&sess, filename, source) {
427-
Ok(p) => p,
428-
Err(errs) => {
429-
for mut err in errs {
430-
err.cancel();
389+
let result = rustc_driver::catch_fatal_errors(|| {
390+
with_globals(edition, || {
391+
use errors::emitter::EmitterWriter;
392+
use errors::Handler;
393+
use rustc_parse::maybe_new_parser_from_source_str;
394+
use rustc_span::source_map::FilePathMapping;
395+
use syntax::sess::ParseSess;
396+
397+
let filename = FileName::anon_source_code(s);
398+
let source = crates + &everything_else;
399+
400+
// Any errors in parsing should also appear when the doctest is compiled for real, so just
401+
// send all the errors that libsyntax emits directly into a `Sink` instead of stderr.
402+
let cm = Lrc::new(SourceMap::new(FilePathMapping::empty()));
403+
let emitter =
404+
EmitterWriter::new(box io::sink(), None, false, false, false, None, false);
405+
// FIXME(misdreavus): pass `-Z treat-err-as-bug` to the doctest parser
406+
let handler = Handler::with_emitter(false, None, box emitter);
407+
let sess = ParseSess::with_span_handler(handler, cm);
408+
409+
let mut found_main = false;
410+
let mut found_extern_crate = cratename.is_none();
411+
let mut found_macro = false;
412+
413+
let mut parser = match maybe_new_parser_from_source_str(&sess, filename, source) {
414+
Ok(p) => p,
415+
Err(errs) => {
416+
for mut err in errs {
417+
err.cancel();
418+
}
419+
420+
return (found_main, found_extern_crate, found_macro);
431421
}
422+
};
423+
424+
loop {
425+
match parser.parse_item() {
426+
Ok(Some(item)) => {
427+
if !found_main {
428+
if let ast::ItemKind::Fn(..) = item.kind {
429+
if item.ident.name == sym::main {
430+
found_main = true;
431+
}
432+
}
433+
}
432434

433-
return (found_main, found_extern_crate, found_macro);
434-
}
435-
};
435+
if !found_extern_crate {
436+
if let ast::ItemKind::ExternCrate(original) = item.kind {
437+
// This code will never be reached if `cratename` is none because
438+
// `found_extern_crate` is initialized to `true` if it is none.
439+
let cratename = cratename.unwrap();
436440

437-
loop {
438-
match parser.parse_item() {
439-
Ok(Some(item)) => {
440-
if !found_main {
441-
if let ast::ItemKind::Fn(..) = item.kind {
442-
if item.ident.name == sym::main {
443-
found_main = true;
441+
match original {
442+
Some(name) => found_extern_crate = name.as_str() == cratename,
443+
None => found_extern_crate = item.ident.as_str() == cratename,
444+
}
444445
}
445446
}
446-
}
447-
448-
if !found_extern_crate {
449-
if let ast::ItemKind::ExternCrate(original) = item.kind {
450-
// This code will never be reached if `cratename` is none because
451-
// `found_extern_crate` is initialized to `true` if it is none.
452-
let cratename = cratename.unwrap();
453447

454-
match original {
455-
Some(name) => found_extern_crate = name.as_str() == cratename,
456-
None => found_extern_crate = item.ident.as_str() == cratename,
448+
if !found_macro {
449+
if let ast::ItemKind::Mac(..) = item.kind {
450+
found_macro = true;
457451
}
458452
}
459-
}
460453

461-
if !found_macro {
462-
if let ast::ItemKind::Mac(..) = item.kind {
463-
found_macro = true;
454+
if found_main && found_extern_crate {
455+
break;
464456
}
465457
}
466-
467-
if found_main && found_extern_crate {
458+
Ok(None) => break,
459+
Err(mut e) => {
460+
e.cancel();
468461
break;
469462
}
470463
}
471-
Ok(None) => break,
472-
Err(mut e) => {
473-
e.cancel();
474-
break;
475-
}
476464
}
477-
}
478465

479-
(found_main, found_extern_crate, found_macro)
466+
(found_main, found_extern_crate, found_macro)
467+
})
480468
});
469+
let (already_has_main, already_has_extern_crate, found_macro) = match result {
470+
Ok(result) => result,
471+
Err(ErrorReported) => {
472+
// If the parser panicked due to a fatal error, pass the test code through unchanged.
473+
// The error will be reported during compilation.
474+
return (s.to_owned(), 0);
475+
}
476+
};
481477

482478
// If a doctest's `fn main` is being masked by a wrapper macro, the parsing loop above won't
483479
// see it. In that case, run the old text-based scan to see if they at least have a main
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
#![crate_name = "foo"]
2+
#![doc(html_playground_url = "https://play.rust-lang.org/")]
3+
4+
/// bar docs
5+
///
6+
/// ```edition2015
7+
/// use std::future::Future;
8+
/// use std::pin::Pin;
9+
/// fn foo_recursive(n: usize) -> Pin<Box<dyn Future<Output = ()>>> {
10+
/// Box::pin(async move {
11+
/// if n > 0 {
12+
/// foo_recursive(n - 1).await;
13+
/// }
14+
/// })
15+
/// }
16+
/// ```
17+
pub fn bar() {}
18+
19+
// @has foo/fn.bar.html
20+
// @has - '//a[@class="test-arrow"]' "Run"
21+
// @has - '//*[@class="docblock"]' 'foo_recursive'

0 commit comments

Comments
 (0)