Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
fd82481
Only use dlltool.exe on MinGW if -Cdlltool is passed
bjorn3 Aug 13, 2026
299213f
PassWrapper: handle LLVM 24 change in function types
durin42 Aug 14, 2026
50b867f
PassWrapper: clang-format
durin42 Aug 14, 2026
af7623a
add crashtests
cyrgani Aug 18, 2026
afcb367
Don't treat slashes as path seps after drive letters in verbatim paths
maxdexh Aug 24, 2026
97967de
Add new `unescaped_pipe_in_table_cell` rustdoc lint
GuillaumeGomez Jul 19, 2026
dbe2c8e
Add ui regression test for new rustdoc `unescaped_pipe_in_table_cell`…
GuillaumeGomez Jul 19, 2026
f74d66c
Add documentation for rustdoc `unescaped_pipe_in_table_cell` lint
GuillaumeGomez Jul 19, 2026
fe90bb2
Rename lint `unescaped_pipe_in_table_cell` into `invalid_markdown_table`
GuillaumeGomez Jul 24, 2026
67bfe3a
Also warn in case there is content after the last table cell
GuillaumeGomez Jul 24, 2026
df734af
rustdoc: clean up some unneeded table lint code
notriddle Jul 24, 2026
aeb0d4a
Update lint to new rustdoc table lint
GuillaumeGomez Aug 24, 2026
1f7f241
Fix whitespace.
durin42 Aug 24, 2026
8f88afc
Rollup merge of #161294 - cyrgani:tests-6, r=fmease
JonathanBrouwer Aug 24, 2026
b13da0f
Rollup merge of #161050 - bjorn3:default_no_dlltool, r=mati865
JonathanBrouwer Aug 24, 2026
8efb610
Rollup merge of #159583 - GuillaumeGomez:unescaped_pipe_in_table_cell…
JonathanBrouwer Aug 24, 2026
b663b2d
Rollup merge of #161098 - durin42:llvm-24-llvm-any-change, r=cuviper
JonathanBrouwer Aug 24, 2026
31b2dbf
Rollup merge of #161661 - maxdexh:least-cursed-windows-feature, r=Chr…
JonathanBrouwer Aug 24, 2026
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
12 changes: 6 additions & 6 deletions compiler/rustc_codegen_ssa/src/back/archive.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,12 +87,12 @@ pub trait ArchiveBuilderBuilder {
items: Vec<ImportLibraryItem>,
output_path: &Path,
) {
if common::is_mingw_gnu_toolchain(&sess.target) {
// The binutils linker used on -windows-gnu targets cannot read the import
// libraries generated by LLVM: in our attempts, the linker produced an .EXE
// that loaded but crashed with an AV upon calling one of the imported
// functions. Therefore, use binutils to create the import library instead,
// by writing a .DEF file to the temp dir and calling binutils's dlltool.
if common::is_mingw_gnu_toolchain(&sess.target) && sess.opts.cg.dlltool.is_some() {
// Previously we always used dlltool on -windows-gnu targets due to the binutils
// linker not entirely correctly handling import libraries generated by
// LLVM/ar_archive_writer. This has since been fixed. To ease the transition, will
// temporarily still use dlltool if explicitly specified, but use ar_archive_writer
// like on MSVC if not.
create_mingw_dll_import_lib(sess, lib_name, items, output_path);
} else {
trace!("creating import library");
Expand Down
45 changes: 39 additions & 6 deletions compiler/rustc_llvm/llvm-wrapper/PassWrapper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -529,6 +529,17 @@ extern "C" typedef void (*LLVMRustSelfProfileBeforePassCallback)(
extern "C" typedef void (*LLVMRustSelfProfileAfterPassCallback)(
void *); // LlvmSelfProfiler

#if LLVM_VERSION_GE(24, 0)
std::string LLVMRustwrappedIrGetName(const llvm::IRUnitRef &WrappedIr) {
if (const auto *Cast = dyn_cast<Module>(WrappedIr))
return Cast->getName().str();
if (const auto *Cast = dyn_cast<Function>(WrappedIr))
return Cast->getName().str();
if (const auto *Cast = dyn_cast<Loop>(WrappedIr))
return Cast->getName().str();
if (const auto *Cast = dyn_cast<LazyCallGraph::SCC>(WrappedIr))
return Cast->getName();
#else
std::string LLVMRustwrappedIrGetName(const llvm::Any &WrappedIr) {
if (const auto *Cast = any_cast<const Module *>(&WrappedIr))
return (*Cast)->getName().str();
Expand All @@ -538,6 +549,7 @@ std::string LLVMRustwrappedIrGetName(const llvm::Any &WrappedIr) {
return (*Cast)->getName().str();
if (const auto *Cast = any_cast<const LazyCallGraph::SCC *>(&WrappedIr))
return (*Cast)->getName();
#endif
return "<UNKNOWN>";
}

Expand All @@ -546,15 +558,26 @@ void LLVMSelfProfileInitializeCallbacks(
LLVMRustSelfProfileBeforePassCallback BeforePassCallback,
LLVMRustSelfProfileAfterPassCallback AfterPassCallback) {
PIC.registerBeforeNonSkippedPassCallback(
#if LLVM_VERSION_GE(24, 0)
[LlvmSelfProfiler, BeforePassCallback](StringRef Pass,
llvm::IRUnitRef Ir) {
#else
[LlvmSelfProfiler, BeforePassCallback](StringRef Pass, llvm::Any Ir) {
#endif
std::string PassName = Pass.str();
std::string IrName = LLVMRustwrappedIrGetName(Ir);
BeforePassCallback(LlvmSelfProfiler, PassName.c_str(), IrName.c_str());
});

PIC.registerAfterPassCallback(
#if LLVM_VERSION_GE(24, 0)
[LlvmSelfProfiler,
AfterPassCallback](StringRef Pass, llvm::IRUnitRef IR,
const PreservedAnalyses &Preserved) {
#else
[LlvmSelfProfiler, AfterPassCallback](
StringRef Pass, llvm::Any IR, const PreservedAnalyses &Preserved) {
#endif
AfterPassCallback(LlvmSelfProfiler);
});

Expand All @@ -564,17 +587,27 @@ void LLVMSelfProfileInitializeCallbacks(
AfterPassCallback(LlvmSelfProfiler);
});

#if LLVM_VERSION_GE(24, 0)
PIC.registerBeforeAnalysisCallback([LlvmSelfProfiler, BeforePassCallback](
StringRef Pass, llvm::IRUnitRef Ir) {
#else
PIC.registerBeforeAnalysisCallback(
[LlvmSelfProfiler, BeforePassCallback](StringRef Pass, llvm::Any Ir) {
std::string PassName = Pass.str();
std::string IrName = LLVMRustwrappedIrGetName(Ir);
BeforePassCallback(LlvmSelfProfiler, PassName.c_str(), IrName.c_str());
});
#endif
std::string PassName = Pass.str();
std::string IrName = LLVMRustwrappedIrGetName(Ir);
BeforePassCallback(LlvmSelfProfiler, PassName.c_str(), IrName.c_str());
});

#if LLVM_VERSION_GE(24, 0)
PIC.registerAfterAnalysisCallback([LlvmSelfProfiler, AfterPassCallback](
StringRef Pass, llvm::IRUnitRef Ir) {
#else
PIC.registerAfterAnalysisCallback(
[LlvmSelfProfiler, AfterPassCallback](StringRef Pass, llvm::Any Ir) {
AfterPassCallback(LlvmSelfProfiler);
});
#endif
AfterPassCallback(LlvmSelfProfiler);
});
}

enum class LLVMRustOptStage {
Expand Down
23 changes: 20 additions & 3 deletions library/std/src/sys/path/windows/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,9 @@ fn verbatim() {
// Make sure opening a drive will work.
check("Z:", "Z:");

// Verbatim drive paths begin with `LETTER:\`. `/` is just a regular character here
check(r"\\?\C:/path\somewhere", r"\\?\C:/path\somewhere");

// A path that contains null is not a valid path.
assert!(maybe_verbatim(Path::new("\0")).is_err());
}
Expand All @@ -93,9 +96,23 @@ fn parse_prefix(path: &str) -> Option<Prefix<'_>> {

#[test]
fn test_parse_prefix_verbatim() {
let prefix = Some(Prefix::VerbatimDisk(b'C'));
assert_eq!(prefix, parse_prefix(r"\\?\C:/windows/system32/notepad.exe"));
assert_eq!(prefix, parse_prefix(r"\\?\C:\windows\system32\notepad.exe"));
assert_eq!(
parse_prefix(r"\\?\C:\windows\system32\notepad.exe"),
Some(Prefix::VerbatimDisk(b'C')),
);
}

#[test]
fn test_verbatim_disk_issue_161651() {
use crate::path::Path;

// This is not a `VerbatimDisk` path, because `/` is not a separator in verbatim paths!
assert_eq!(
parse_prefix(r"\\?\C:/windows\system32"),
Some(Prefix::Verbatim(OsStr::new("C:/windows"))),
);

assert_ne!(Path::new(r"\\?\C:/foo"), Path::new(r"\\?\C:\foo"));
}

#[test]
Expand Down
2 changes: 1 addition & 1 deletion library/std/src/sys/path/windows_prefix.rs
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ fn parse_drive(path: &OsStr) -> Option<u8> {
// Parses a drive prefix exactly, e.g. "C:"
fn parse_drive_exact(path: &OsStr) -> Option<u8> {
// only parse two bytes: the drive letter and the drive separator
if path.as_encoded_bytes().get(2).map(|&x| is_sep_byte(x)).unwrap_or(true) {
if path.as_encoded_bytes().get(2).map(|&x| is_verbatim_sep(x)).unwrap_or(true) {
parse_drive(path)
} else {
None
Expand Down
10 changes: 5 additions & 5 deletions library/std/tests/path.rs
Original file line number Diff line number Diff line change
Expand Up @@ -989,14 +989,14 @@ pub fn test_decompositions_windows() {
);

t!("\\\\?\\C:/foo/bar",
iter: ["\\\\?\\C:", "\\", "foo/bar"],
iter: ["\\\\?\\C:/foo/bar"],
has_root: true,
is_absolute: true,
parent: Some("\\\\?\\C:/"),
file_name: Some("foo/bar"),
file_stem: Some("foo/bar"),
parent: None,
file_name: None,
file_stem: None,
extension: None,
file_prefix: Some("foo/bar")
file_prefix: None
);

t!("\\\\.\\foo\\bar",
Expand Down
28 changes: 28 additions & 0 deletions src/doc/rustdoc/src/lints.md
Original file line number Diff line number Diff line change
Expand Up @@ -456,3 +456,31 @@ note: the lint level is defined here
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
= help: Remove explicit link instead
```

## `invalid_markdown_table`

This lint is **warn-by-default**. It detects unescaped pipes (`|`) in table rows which
lead to some row cells being ignored. For example:

```rust
//! | col1 |
//! | ---- |
//! | `code_with(|arg| arg)` |
```

Which will give:

```text
error: table row has too many columns
--> $DIR/foo.rs:5:18
|
5 | //! | `code_with(|arg| arg)` |
| ^ help: any content after this column divider is discarded
|
= help: to escape `|` characters in tables, add a `\` before them like `\|`
note: the lint level is defined here
--> $DIR/foo.rs:1:9
|
1 | #![deny(rustdoc::invalid_markdown_table)]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
```
12 changes: 12 additions & 0 deletions src/librustdoc/lint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,17 @@ declare_rustdoc_lint! {
"detects unused footnote definitions"
}

declare_rustdoc_lint! {
/// This lint is **warn-by-default**. It detects unescaped pipes in table rows which
/// lead to some row cells being ignored. This is a `rustdoc` only lint, see the
/// documentation in the [rustdoc book].
///
/// [rustdoc book]: ../../../rustdoc/lints.html#invalid_markdown_table
INVALID_MARKDOWN_TABLE,
Warn,
"detects unescaped pipe in table rows in doc comments"
}

pub(crate) static RUSTDOC_LINTS: Lazy<Vec<&'static Lint>> = Lazy::new(|| {
vec![
BROKEN_INTRA_DOC_LINKS,
Expand All @@ -224,6 +235,7 @@ pub(crate) static RUSTDOC_LINTS: Lazy<Vec<&'static Lint>> = Lazy::new(|| {
REDUNDANT_EXPLICIT_LINKS,
BROKEN_FOOTNOTE,
UNUSED_FOOTNOTE_DEFINITION,
INVALID_MARKDOWN_TABLE,
]
});

Expand Down
5 changes: 5 additions & 0 deletions src/librustdoc/passes/lint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ mod bare_urls;
mod check_code_block_syntax;
mod footnotes;
mod html_tags;
mod invalid_markdown_table;
mod redundant_explicit_links;
mod unescaped_backticks;

Expand Down Expand Up @@ -35,6 +36,7 @@ impl DocVisitor<'_> for Linter<'_, '_> {
if !dox.is_empty() {
let may_have_link = dox.contains(&[':', '['][..]);
let may_have_block_comment_or_html = dox.contains(['<', '>']);
let may_have_table = dox.contains(&['|'][..]);
// ~~~rust
// // This is a real, supported commonmark syntax for block code
// ~~~
Expand All @@ -51,6 +53,9 @@ impl DocVisitor<'_> for Linter<'_, '_> {
if may_have_block_comment_or_html {
html_tags::visit_item(self.cx, item, hir_id, &dox);
}
if may_have_table {
invalid_markdown_table::visit_item(self.cx, item, hir_id, &dox);
}
}

self.visit_item_recur(item)
Expand Down
120 changes: 120 additions & 0 deletions src/librustdoc/passes/lint/invalid_markdown_table.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
//! Detects table rows where some content seems to have been discarded because there are too many
//! pipe characters.

use std::ops::Range;

use rustc_hir::HirId;
use rustc_macros::Diagnostic;
use rustc_resolve::rustdoc::pulldown_cmark::{Event, Parser, Tag, TagEnd};
use rustc_resolve::rustdoc::source_span_for_markdown_range;

use crate::clean::*;
use crate::core::DocContext;
use crate::html::markdown::main_body_opts;

#[derive(Diagnostic)]
#[diag("table row has too many columns")]
#[help(r"to escape `|` characters in tables, add a `\` before them like `\|`")]
struct UnescapedPipeInTableCell {
#[primary_span]
#[label("any content after this column divider is discarded")]
span: rustc_span::Span,
}

#[derive(Diagnostic)]
#[diag("unused content after last table cell")]
struct ContentAfterLastPipe {
#[primary_span]
#[label("this content is discarded")]
span: rustc_span::Span,
}

pub(crate) fn visit_item(cx: &DocContext<'_>, item: &Item, hir_id: HirId, dox: &str) {
let mut p = Parser::new_ext(dox, main_body_opts()).into_offset_iter();

while let Some((event, _range)) = p.next() {
if Event::Start(Tag::TableRow) == event {
let mut prev_range = None;
while let Some((event, range)) = p.next() {
match event {
Event::End(TagEnd::TableCell) => {
prev_range = Some(range);
}
Event::End(TagEnd::TableRow) => {
if let Some(prev_range) = &prev_range
// So here what is happening: when `pulldown-cmark` is parsing a table
// and a table row has too many cells, it doesn't emit events for the
// extra cells. So the only way for us to know these extra cells exist
// is to compare the row's span with the last emitted cell event's span.
// If the span ends don't match, then there are extra cells.
&& prev_range.end + 1 < range.end
{
// Something seems wrong, the range diff doesn't match, some content
// was left out.
let mut after_last_cell_range =
Range { start: prev_range.end + 1, end: range.end };
if dox[after_last_cell_range.clone()].trim().is_empty() {
// Seems all good so let's ignore it and continue;.
continue;
}
// Check if any pipes appear after the end of the row.
let mut iter = dox[after_last_cell_range.clone()].bytes().peekable();
let mut found_divider = false;
while let Some(c) = iter.next() {
// the sequence `\\|` still escapes the pipe because GFM
// processes block structures like tables in its own pass
if c == b'\\' && iter.peek() == Some(&b'|') {
iter.next();
} else if c == b'|' {
found_divider = true;
break;
}
}
if found_divider {
// Seems like a pipe was not escaped as it should have been.
let last_cell_separator =
Range { start: prev_range.end, end: prev_range.end + 1 };

if let Some((span, _)) = source_span_for_markdown_range(
cx.tcx,
dox,
&last_cell_separator,
&item.attrs.doc_strings,
) {
cx.tcx.emit_node_span_lint(
crate::lint::INVALID_MARKDOWN_TABLE,
hir_id,
span,
UnescapedPipeInTableCell { span },
);
}
} else {
// An unclosed cell maybe? There is content after the last cell so
// let's lint about it.
let content = &dox[after_last_cell_range.clone()];
after_last_cell_range.end -=
content.len() - content.trim_end().len();

if let Some((span, _)) = source_span_for_markdown_range(
cx.tcx,
dox,
&after_last_cell_range,
&item.attrs.doc_strings,
) {
cx.tcx.emit_node_span_lint(
crate::lint::INVALID_MARKDOWN_TABLE,
hir_id,
span,
ContentAfterLastPipe { span },
);
}
}
}
}
Event::End(TagEnd::Table) => break,
_ => {}
}
}
}
}
}
2 changes: 1 addition & 1 deletion tests/crashes/108428.rs → tests/crashes/108248.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
//@ known-bug: #108428
//@ known-bug: #108248
//@ needs-rustc-debug-assertions
//@ compile-flags: -Wunused-lifetimes
fn main() {
Expand Down
12 changes: 12 additions & 0 deletions tests/crashes/138262.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
//@ known-bug: #138262
//@ compile-flags: -Zsanitizer=cfi -Ccodegen-units=1 -Clto -Clink-dead-code=true -Cunsafe-allow-abi-mismatch=sanitizer -Ctarget-feature=-crt-static
//@ ignore-backends: gcc
//@ needs-sanitizer-cfi
fn foo<const N: usize>() {}

core::arch::global_asm!("/* {} */", sym foo::<{
|| {};
0
}>);

fn main() {}
Loading
Loading