Skip to content

Commit e5a2a6a

Browse files
committed
Auto merge of rust-lang#141243 - Zalathar:rollup-x5xt80l, r=Zalathar
Rollup of 5 pull requests Successful merges: - rust-lang#140847 (coverage: Detect unused local file IDs to avoid an LLVM assertion) - rust-lang#141117 (opt-dist: fix deprecated BOLT -icf=1 option) - rust-lang#141225 (more ice tests) - rust-lang#141239 (dladdr cannot leave dli_fname to be null) - rust-lang#141242 (in `tests/ui/asm/aarch64/parse-error.rs`, only test cases specific to that target) r? `@ghost` `@rustbot` modify labels: rollup
2 parents 7068c8b + 315874c commit e5a2a6a

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

57 files changed

+563
-540
lines changed

compiler/rustc_codegen_llvm/src/coverageinfo/ffi.rs

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,20 @@ pub(crate) struct Regions {
155155
impl Regions {
156156
/// Returns true if none of this structure's tables contain any regions.
157157
pub(crate) fn has_no_regions(&self) -> bool {
158+
// Every region has a span, so if there are no spans then there are no regions.
159+
self.all_cov_spans().next().is_none()
160+
}
161+
162+
pub(crate) fn all_cov_spans(&self) -> impl Iterator<Item = &CoverageSpan> {
163+
macro_rules! iter_cov_spans {
164+
( $( $regions:expr ),* $(,)? ) => {
165+
std::iter::empty()
166+
$(
167+
.chain( $regions.iter().map(|region| &region.cov_span) )
168+
)*
169+
}
170+
}
171+
158172
let Self {
159173
code_regions,
160174
expansion_regions,
@@ -163,11 +177,13 @@ impl Regions {
163177
mcdc_decision_regions,
164178
} = self;
165179

166-
code_regions.is_empty()
167-
&& expansion_regions.is_empty()
168-
&& branch_regions.is_empty()
169-
&& mcdc_branch_regions.is_empty()
170-
&& mcdc_decision_regions.is_empty()
180+
iter_cov_spans!(
181+
code_regions,
182+
expansion_regions,
183+
branch_regions,
184+
mcdc_branch_regions,
185+
mcdc_decision_regions,
186+
)
171187
}
172188
}
173189

compiler/rustc_codegen_llvm/src/coverageinfo/mapgen/covfun.rs

Lines changed: 34 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ use rustc_abi::Align;
1111
use rustc_codegen_ssa::traits::{
1212
BaseTypeCodegenMethods as _, ConstCodegenMethods, StaticCodegenMethods,
1313
};
14+
use rustc_index::IndexVec;
1415
use rustc_middle::mir::coverage::{
1516
BasicCoverageBlock, CovTerm, CoverageIdsInfo, Expression, FunctionCoverageInfo, Mapping,
1617
MappingKind, Op,
@@ -104,6 +105,16 @@ fn fill_region_tables<'tcx>(
104105
ids_info: &'tcx CoverageIdsInfo,
105106
covfun: &mut CovfunRecord<'tcx>,
106107
) {
108+
// If this function is unused, replace all counters with zero.
109+
let counter_for_bcb = |bcb: BasicCoverageBlock| -> ffi::Counter {
110+
let term = if covfun.is_used {
111+
ids_info.term_for_bcb[bcb].expect("every BCB in a mapping was given a term")
112+
} else {
113+
CovTerm::Zero
114+
};
115+
ffi::Counter::from_term(term)
116+
};
117+
107118
// Currently a function's mappings must all be in the same file, so use the
108119
// first mapping's span to determine the file.
109120
let source_map = tcx.sess.source_map();
@@ -115,6 +126,12 @@ fn fill_region_tables<'tcx>(
115126

116127
let local_file_id = covfun.virtual_file_mapping.push_file(&source_file);
117128

129+
// If this testing flag is set, add an extra unused entry to the local
130+
// file table, to help test the code for detecting unused file IDs.
131+
if tcx.sess.coverage_inject_unused_local_file() {
132+
covfun.virtual_file_mapping.push_file(&source_file);
133+
}
134+
118135
// In rare cases, _all_ of a function's spans are discarded, and coverage
119136
// codegen needs to handle that gracefully to avoid #133606.
120137
// It's hard for tests to trigger this organically, so instead we set
@@ -135,16 +152,6 @@ fn fill_region_tables<'tcx>(
135152
// For each counter/region pair in this function+file, convert it to a
136153
// form suitable for FFI.
137154
for &Mapping { ref kind, span } in &fn_cov_info.mappings {
138-
// If this function is unused, replace all counters with zero.
139-
let counter_for_bcb = |bcb: BasicCoverageBlock| -> ffi::Counter {
140-
let term = if covfun.is_used {
141-
ids_info.term_for_bcb[bcb].expect("every BCB in a mapping was given a term")
142-
} else {
143-
CovTerm::Zero
144-
};
145-
ffi::Counter::from_term(term)
146-
};
147-
148155
let Some(coords) = make_coords(span) else { continue };
149156
let cov_span = coords.make_coverage_span(local_file_id);
150157

@@ -177,6 +184,19 @@ fn fill_region_tables<'tcx>(
177184
}
178185
}
179186

187+
/// LLVM requires all local file IDs to have at least one mapping region.
188+
/// If that's not the case, skip this function, to avoid an assertion failure
189+
/// (or worse) in LLVM.
190+
fn check_local_file_table(covfun: &CovfunRecord<'_>) -> bool {
191+
let mut local_file_id_seen =
192+
IndexVec::<u32, _>::from_elem_n(false, covfun.virtual_file_mapping.local_file_table.len());
193+
for cov_span in covfun.regions.all_cov_spans() {
194+
local_file_id_seen[cov_span.file_id] = true;
195+
}
196+
197+
local_file_id_seen.into_iter().all(|seen| seen)
198+
}
199+
180200
/// Generates the contents of the covfun record for this function, which
181201
/// contains the function's coverage mapping data. The record is then stored
182202
/// as a global variable in the `__llvm_covfun` section.
@@ -185,6 +205,10 @@ pub(crate) fn generate_covfun_record<'tcx>(
185205
global_file_table: &GlobalFileTable,
186206
covfun: &CovfunRecord<'tcx>,
187207
) {
208+
if !check_local_file_table(covfun) {
209+
return;
210+
}
211+
188212
let &CovfunRecord {
189213
mangled_function_name,
190214
source_hash,

compiler/rustc_codegen_llvm/src/coverageinfo/mapgen/spans.rs

Lines changed: 4 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,10 @@ impl Coords {
3939
/// or other expansions), and if it does happen then skipping a span or function is
4040
/// better than an ICE or `llvm-cov` failure that the user might have no way to avoid.
4141
pub(crate) fn make_coords(source_map: &SourceMap, file: &SourceFile, span: Span) -> Option<Coords> {
42-
let span = ensure_non_empty_span(source_map, span)?;
42+
if span.is_empty() {
43+
debug_assert!(false, "can't make coords from empty span: {span:?}");
44+
return None;
45+
}
4346

4447
let lo = span.lo();
4548
let hi = span.hi();
@@ -70,29 +73,6 @@ pub(crate) fn make_coords(source_map: &SourceMap, file: &SourceFile, span: Span)
7073
})
7174
}
7275

73-
fn ensure_non_empty_span(source_map: &SourceMap, span: Span) -> Option<Span> {
74-
if !span.is_empty() {
75-
return Some(span);
76-
}
77-
78-
// The span is empty, so try to enlarge it to cover an adjacent '{' or '}'.
79-
source_map
80-
.span_to_source(span, |src, start, end| try {
81-
// Adjusting span endpoints by `BytePos(1)` is normally a bug,
82-
// but in this case we have specifically checked that the character
83-
// we're skipping over is one of two specific ASCII characters, so
84-
// adjusting by exactly 1 byte is correct.
85-
if src.as_bytes().get(end).copied() == Some(b'{') {
86-
Some(span.with_hi(span.hi() + BytePos(1)))
87-
} else if start > 0 && src.as_bytes()[start - 1] == b'}' {
88-
Some(span.with_lo(span.lo() - BytePos(1)))
89-
} else {
90-
None
91-
}
92-
})
93-
.ok()?
94-
}
95-
9676
/// If `llvm-cov` sees a source region that is improperly ordered (end < start),
9777
/// it will immediately exit with a fatal error. To prevent that from happening,
9878
/// discard regions that are improperly ordered, or might be interpreted in a

compiler/rustc_interface/src/tests.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -776,7 +776,8 @@ fn test_unstable_options_tracking_hash() {
776776
CoverageOptions {
777777
level: CoverageLevel::Mcdc,
778778
no_mir_spans: true,
779-
discard_all_spans_in_codegen: true
779+
discard_all_spans_in_codegen: true,
780+
inject_unused_local_file: true,
780781
}
781782
);
782783
tracked!(crate_attr, vec!["abc".to_string()]);

compiler/rustc_mir_transform/src/coverage/spans.rs

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
use rustc_data_structures::fx::FxHashSet;
22
use rustc_middle::mir;
33
use rustc_middle::ty::TyCtxt;
4-
use rustc_span::{DesugaringKind, ExpnKind, MacroKind, Span};
4+
use rustc_span::source_map::SourceMap;
5+
use rustc_span::{BytePos, DesugaringKind, ExpnKind, MacroKind, Span};
56
use tracing::instrument;
67

78
use crate::coverage::graph::{BasicCoverageBlock, CoverageGraph};
@@ -83,8 +84,18 @@ pub(super) fn extract_refined_covspans<'tcx>(
8384
// Discard any span that overlaps with a hole.
8485
discard_spans_overlapping_holes(&mut covspans, &holes);
8586

86-
// Perform more refinement steps after holes have been dealt with.
87+
// Discard spans that overlap in unwanted ways.
8788
let mut covspans = remove_unwanted_overlapping_spans(covspans);
89+
90+
// For all empty spans, either enlarge them to be non-empty, or discard them.
91+
let source_map = tcx.sess.source_map();
92+
covspans.retain_mut(|covspan| {
93+
let Some(span) = ensure_non_empty_span(source_map, covspan.span) else { return false };
94+
covspan.span = span;
95+
true
96+
});
97+
98+
// Merge covspans that can be merged.
8899
covspans.dedup_by(|b, a| a.merge_if_eligible(b));
89100

90101
code_mappings.extend(covspans.into_iter().map(|Covspan { span, bcb }| {
@@ -230,3 +241,26 @@ fn compare_spans(a: Span, b: Span) -> std::cmp::Ordering {
230241
// - Both have the same start and span A extends further right
231242
.then_with(|| Ord::cmp(&a.hi(), &b.hi()).reverse())
232243
}
244+
245+
fn ensure_non_empty_span(source_map: &SourceMap, span: Span) -> Option<Span> {
246+
if !span.is_empty() {
247+
return Some(span);
248+
}
249+
250+
// The span is empty, so try to enlarge it to cover an adjacent '{' or '}'.
251+
source_map
252+
.span_to_source(span, |src, start, end| try {
253+
// Adjusting span endpoints by `BytePos(1)` is normally a bug,
254+
// but in this case we have specifically checked that the character
255+
// we're skipping over is one of two specific ASCII characters, so
256+
// adjusting by exactly 1 byte is correct.
257+
if src.as_bytes().get(end).copied() == Some(b'{') {
258+
Some(span.with_hi(span.hi() + BytePos(1)))
259+
} else if start > 0 && src.as_bytes()[start - 1] == b'}' {
260+
Some(span.with_lo(span.lo() - BytePos(1)))
261+
} else {
262+
None
263+
}
264+
})
265+
.ok()?
266+
}

compiler/rustc_session/src/config.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,11 @@ pub struct CoverageOptions {
195195
/// regression tests for #133606, because we don't have an easy way to
196196
/// reproduce it from actual source code.
197197
pub discard_all_spans_in_codegen: bool,
198+
199+
/// `-Zcoverage-options=inject-unused-local-file`: During codegen, add an
200+
/// extra dummy entry to each function's local file table, to exercise the
201+
/// code that checks for local file IDs with no mapping regions.
202+
pub inject_unused_local_file: bool,
198203
}
199204

200205
/// Controls whether branch coverage or MC/DC coverage is enabled.

compiler/rustc_session/src/filesearch.rs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -82,9 +82,7 @@ fn current_dll_path() -> Result<PathBuf, String> {
8282
let fname_ptr = info.dli_fname.as_ptr();
8383
#[cfg(not(target_os = "cygwin"))]
8484
let fname_ptr = {
85-
if info.dli_fname.is_null() {
86-
return Err("dladdr returned null pointer".into());
87-
}
85+
assert!(!info.dli_fname.is_null(), "the docs do not allow dladdr to be null");
8886
info.dli_fname
8987
};
9088
let bytes = CStr::from_ptr(fname_ptr).to_bytes();

compiler/rustc_session/src/options.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1413,6 +1413,7 @@ pub mod parse {
14131413
"mcdc" => slot.level = CoverageLevel::Mcdc,
14141414
"no-mir-spans" => slot.no_mir_spans = true,
14151415
"discard-all-spans-in-codegen" => slot.discard_all_spans_in_codegen = true,
1416+
"inject-unused-local-file" => slot.inject_unused_local_file = true,
14161417
_ => return false,
14171418
}
14181419
}

compiler/rustc_session/src/session.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -371,6 +371,11 @@ impl Session {
371371
self.opts.unstable_opts.coverage_options.discard_all_spans_in_codegen
372372
}
373373

374+
/// True if testing flag `-Zcoverage-options=inject-unused-local-file` was passed.
375+
pub fn coverage_inject_unused_local_file(&self) -> bool {
376+
self.opts.unstable_opts.coverage_options.inject_unused_local_file
377+
}
378+
374379
pub fn is_sanitizer_cfi_enabled(&self) -> bool {
375380
self.opts.unstable_opts.sanitizer.contains(SanitizerSet::CFI)
376381
}

src/tools/miri/src/shims/native_lib.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -92,8 +92,7 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
9292
fn get_func_ptr_explicitly_from_lib(&mut self, link_name: Symbol) -> Option<CodePtr> {
9393
let this = self.eval_context_mut();
9494
// Try getting the function from the shared library.
95-
// On windows `_lib_path` will be unused, hence the name starting with `_`.
96-
let (lib, _lib_path) = this.machine.native_lib.as_ref().unwrap();
95+
let (lib, lib_path) = this.machine.native_lib.as_ref().unwrap();
9796
let func: libloading::Symbol<'_, unsafe extern "C" fn()> = unsafe {
9897
match lib.get(link_name.as_str().as_bytes()) {
9998
Ok(x) => x,
@@ -114,16 +113,17 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
114113
// This code is a reimplementation of the mechanism for getting `dli_fname` in `libloading`,
115114
// from: https://docs.rs/libloading/0.7.3/src/libloading/os/unix/mod.rs.html#411
116115
// using the `libc` crate where this interface is public.
117-
let mut info = std::mem::MaybeUninit::<libc::Dl_info>::uninit();
116+
let mut info = std::mem::MaybeUninit::<libc::Dl_info>::zeroed();
118117
unsafe {
119118
if libc::dladdr(*func.deref() as *const _, info.as_mut_ptr()) != 0 {
120119
let info = info.assume_init();
121120
#[cfg(target_os = "cygwin")]
122121
let fname_ptr = info.dli_fname.as_ptr();
123122
#[cfg(not(target_os = "cygwin"))]
124123
let fname_ptr = info.dli_fname;
124+
assert!(!fname_ptr.is_null());
125125
if std::ffi::CStr::from_ptr(fname_ptr).to_str().unwrap()
126-
!= _lib_path.to_str().unwrap()
126+
!= lib_path.to_str().unwrap()
127127
{
128128
return None;
129129
}

src/tools/opt-dist/src/bolt.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ pub fn bolt_optimize(
8080
// Move jump tables to a separate section
8181
.arg("-jump-tables=move")
8282
// Fold functions with identical code
83-
.arg("-icf=1")
83+
.arg("-icf=all")
8484
// The following flag saves about 50 MiB of libLLVM.so size.
8585
// However, it succeeds very non-deterministically. To avoid frequent artifact size swings,
8686
// it is kept disabled for now.

tests/coverage/async_closure.cov-map

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -37,32 +37,29 @@ Number of file 0 mappings: 8
3737
Highest counter ID seen: c0
3838

3939
Function name: async_closure::main::{closure#0}
40-
Raw bytes (14): 0x[01, 01, 00, 02, 01, 0b, 22, 00, 23, 01, 00, 23, 00, 24]
40+
Raw bytes (9): 0x[01, 01, 00, 01, 01, 0b, 22, 00, 24]
4141
Number of files: 1
4242
- file 0 => $DIR/async_closure.rs
4343
Number of expressions: 0
44-
Number of file 0 mappings: 2
45-
- Code(Counter(0)) at (prev + 11, 34) to (start + 0, 35)
46-
- Code(Counter(0)) at (prev + 0, 35) to (start + 0, 36)
44+
Number of file 0 mappings: 1
45+
- Code(Counter(0)) at (prev + 11, 34) to (start + 0, 36)
4746
Highest counter ID seen: c0
4847

4948
Function name: async_closure::main::{closure#0}
50-
Raw bytes (14): 0x[01, 01, 00, 02, 01, 0b, 22, 00, 23, 01, 00, 23, 00, 24]
49+
Raw bytes (9): 0x[01, 01, 00, 01, 01, 0b, 22, 00, 24]
5150
Number of files: 1
5251
- file 0 => $DIR/async_closure.rs
5352
Number of expressions: 0
54-
Number of file 0 mappings: 2
55-
- Code(Counter(0)) at (prev + 11, 34) to (start + 0, 35)
56-
- Code(Counter(0)) at (prev + 0, 35) to (start + 0, 36)
53+
Number of file 0 mappings: 1
54+
- Code(Counter(0)) at (prev + 11, 34) to (start + 0, 36)
5755
Highest counter ID seen: c0
5856

5957
Function name: async_closure::main::{closure#0}::{closure#0}::<i16>
60-
Raw bytes (14): 0x[01, 01, 00, 02, 01, 0b, 22, 00, 23, 01, 00, 23, 00, 24]
58+
Raw bytes (9): 0x[01, 01, 00, 01, 01, 0b, 22, 00, 24]
6159
Number of files: 1
6260
- file 0 => $DIR/async_closure.rs
6361
Number of expressions: 0
64-
Number of file 0 mappings: 2
65-
- Code(Counter(0)) at (prev + 11, 34) to (start + 0, 35)
66-
- Code(Counter(0)) at (prev + 0, 35) to (start + 0, 36)
62+
Number of file 0 mappings: 1
63+
- Code(Counter(0)) at (prev + 11, 34) to (start + 0, 36)
6764
Highest counter ID seen: c0
6865

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
LL| |//@ edition: 2021
2+
LL| |
3+
LL| |// Force this function to be generated in its home crate, so that it ends up
4+
LL| |// with normal coverage metadata.
5+
LL| |#[inline(never)]
6+
LL| 1|pub fn external_function() {}
7+

tests/coverage/unused-local-file.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
//! If we give LLVM a local file table for a function, but some of the entries
2+
//! in that table have no associated mapping regions, then an assertion failure
3+
//! will occur in LLVM. We therefore need to detect and skip any function that
4+
//! would trigger that assertion.
5+
//!
6+
//! To test that this case is handled, even before adding code that could allow
7+
//! it to happen organically (for expansion region support), we use a special
8+
//! testing-only flag to force it to occur.
9+
10+
//@ edition: 2024
11+
//@ compile-flags: -Zcoverage-options=inject-unused-local-file
12+
13+
// The `llvm-cov` tool will complain if the test binary ends up having no
14+
// coverage metadata at all. To prevent that, we also link to instrumented
15+
// code in an auxiliary crate that doesn't have the special flag set.
16+
17+
//@ aux-build: discard_all_helper.rs
18+
extern crate discard_all_helper;
19+
20+
fn main() {
21+
discard_all_helper::external_function();
22+
}

tests/crashes/139905.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
//@ known-bug: #139905
2+
trait a<const b: bool> {}
3+
impl a<{}> for () {}
4+
trait c {}
5+
impl<const d: u8> c for () where (): a<d> {}
6+
impl c for () {}

0 commit comments

Comments
 (0)