diff options
Diffstat (limited to 'compiler/rustc_mir_transform')
| -rw-r--r-- | compiler/rustc_mir_transform/messages.ftl | 7 | ||||
| -rw-r--r-- | compiler/rustc_mir_transform/src/check_undefined_transmutes.rs | 77 | ||||
| -rw-r--r-- | compiler/rustc_mir_transform/src/coverage/counters.rs | 42 | ||||
| -rw-r--r-- | compiler/rustc_mir_transform/src/coverage/mappings.rs | 241 | ||||
| -rw-r--r-- | compiler/rustc_mir_transform/src/coverage/mod.rs | 165 | ||||
| -rw-r--r-- | compiler/rustc_mir_transform/src/errors.rs | 15 | ||||
| -rw-r--r-- | compiler/rustc_mir_transform/src/gvn.rs | 36 | ||||
| -rw-r--r-- | compiler/rustc_mir_transform/src/jump_threading.rs | 8 | ||||
| -rw-r--r-- | compiler/rustc_mir_transform/src/known_panics_lint.rs | 16 | ||||
| -rw-r--r-- | compiler/rustc_mir_transform/src/lib.rs | 2 |
10 files changed, 452 insertions, 157 deletions
diff --git a/compiler/rustc_mir_transform/messages.ftl b/compiler/rustc_mir_transform/messages.ftl index f9b79d72b05..c8992b8b834 100644 --- a/compiler/rustc_mir_transform/messages.ftl +++ b/compiler/rustc_mir_transform/messages.ftl @@ -9,6 +9,8 @@ mir_transform_const_mut_borrow = taking a mutable reference to a `const` item .note2 = the mutable reference will refer to this temporary, not the original `const` item .note3 = mutable reference created due to call to this method +mir_transform_exceeds_mcdc_test_vector_limit = number of total test vectors in one function will exceed limit ({$max_num_test_vectors}) if this decision is instrumented, so MC/DC analysis ignores it + mir_transform_ffi_unwind_call = call to {$foreign -> [true] foreign function *[false] function pointer @@ -27,3 +29,8 @@ mir_transform_unaligned_packed_ref = reference to packed field is unaligned .note = packed structs are only aligned by one byte, and many modern architectures penalize unaligned field accesses .note_ub = creating a misaligned reference is undefined behavior (even if that reference is never dereferenced) .help = copy the field contents to a local variable, or replace the reference with a raw pointer and use `read_unaligned`/`write_unaligned` (loads and stores via `*p` must be properly aligned even when using raw pointers) + +mir_transform_undefined_transmute = pointers cannot be transmuted to integers during const eval + .note = at compile-time, pointers do not have an integer value + .note2 = avoiding this restriction via `union` or raw pointers leads to compile-time undefined behavior + .help = for more information, see https://doc.rust-lang.org/std/mem/fn.transmute.html diff --git a/compiler/rustc_mir_transform/src/check_undefined_transmutes.rs b/compiler/rustc_mir_transform/src/check_undefined_transmutes.rs new file mode 100644 index 00000000000..8ba14a1158e --- /dev/null +++ b/compiler/rustc_mir_transform/src/check_undefined_transmutes.rs @@ -0,0 +1,77 @@ +use rustc_middle::mir::visit::Visitor; +use rustc_middle::mir::{Body, Location, Operand, Terminator, TerminatorKind}; +use rustc_middle::ty::{AssocItem, AssocKind, TyCtxt}; +use rustc_session::lint::builtin::PTR_TO_INTEGER_TRANSMUTE_IN_CONSTS; +use rustc_span::sym; + +use crate::errors; + +/// Check for transmutes that exhibit undefined behavior. +/// For example, transmuting pointers to integers in a const context. +pub(super) struct CheckUndefinedTransmutes; + +impl<'tcx> crate::MirLint<'tcx> for CheckUndefinedTransmutes { + fn run_lint(&self, tcx: TyCtxt<'tcx>, body: &Body<'tcx>) { + let mut checker = UndefinedTransmutesChecker { body, tcx }; + checker.visit_body(body); + } +} + +struct UndefinedTransmutesChecker<'a, 'tcx> { + body: &'a Body<'tcx>, + tcx: TyCtxt<'tcx>, +} + +impl<'a, 'tcx> UndefinedTransmutesChecker<'a, 'tcx> { + // This functions checks two things: + // 1. `function` takes a raw pointer as input and returns an integer as output. + // 2. `function` is called from a const function or an associated constant. + // + // Why do we consider const functions and associated constants only? + // + // Generally, undefined behavior in const items are handled by the evaluator. + // But, const functions and associated constants are evaluated only when referenced. + // This can result in undefined behavior in a library going unnoticed until + // the function or constant is actually used. + // + // Therefore, we only consider const functions and associated constants here and leave + // other const items to be handled by the evaluator. + fn is_ptr_to_int_in_const(&self, function: &Operand<'tcx>) -> bool { + let def_id = self.body.source.def_id(); + + if self.tcx.is_const_fn(def_id) + || matches!( + self.tcx.opt_associated_item(def_id), + Some(AssocItem { kind: AssocKind::Const, .. }) + ) + { + let fn_sig = function.ty(self.body, self.tcx).fn_sig(self.tcx).skip_binder(); + if let [input] = fn_sig.inputs() { + return input.is_unsafe_ptr() && fn_sig.output().is_integral(); + } + } + false + } +} + +impl<'tcx> Visitor<'tcx> for UndefinedTransmutesChecker<'_, 'tcx> { + // Check each block's terminator for calls to pointer to integer transmutes + // in const functions or associated constants and emit a lint. + fn visit_terminator(&mut self, terminator: &Terminator<'tcx>, location: Location) { + if let TerminatorKind::Call { func, .. } = &terminator.kind + && let Some((func_def_id, _)) = func.const_fn_def() + && self.tcx.is_intrinsic(func_def_id, sym::transmute) + && self.is_ptr_to_int_in_const(func) + && let Some(call_id) = self.body.source.def_id().as_local() + { + let hir_id = self.tcx.local_def_id_to_hir_id(call_id); + let span = self.body.source_info(location).span; + self.tcx.emit_node_span_lint( + PTR_TO_INTEGER_TRANSMUTE_IN_CONSTS, + hir_id, + span, + errors::UndefinedTransmute, + ); + } + } +} diff --git a/compiler/rustc_mir_transform/src/coverage/counters.rs b/compiler/rustc_mir_transform/src/coverage/counters.rs index ef4031c5c03..94088156756 100644 --- a/compiler/rustc_mir_transform/src/coverage/counters.rs +++ b/compiler/rustc_mir_transform/src/coverage/counters.rs @@ -4,6 +4,7 @@ use rustc_data_structures::captures::Captures; use rustc_data_structures::fx::FxHashMap; use rustc_data_structures::graph::DirectedGraph; use rustc_index::IndexVec; +use rustc_index::bit_set::BitSet; use rustc_middle::bug; use rustc_middle::mir::coverage::{CounterId, CovTerm, Expression, ExpressionId, Op}; use tracing::{debug, debug_span, instrument}; @@ -13,13 +14,13 @@ use crate::coverage::graph::{BasicCoverageBlock, CoverageGraph, TraverseCoverage /// The coverage counter or counter expression associated with a particular /// BCB node or BCB edge. #[derive(Clone, Copy, PartialEq, Eq, Hash)] -pub(super) enum BcbCounter { +enum BcbCounter { Counter { id: CounterId }, Expression { id: ExpressionId }, } impl BcbCounter { - pub(super) fn as_term(&self) -> CovTerm { + fn as_term(&self) -> CovTerm { match *self { BcbCounter::Counter { id, .. } => CovTerm::Counter(id), BcbCounter::Expression { id, .. } => CovTerm::Expression(id), @@ -78,21 +79,22 @@ impl CoverageCounters { /// counters or counter expressions for nodes and edges as required. pub(super) fn make_bcb_counters( basic_coverage_blocks: &CoverageGraph, - bcb_needs_counter: impl Fn(BasicCoverageBlock) -> bool, + bcb_needs_counter: &BitSet<BasicCoverageBlock>, ) -> Self { - let num_bcbs = basic_coverage_blocks.num_nodes(); + let mut counters = MakeBcbCounters::new(basic_coverage_blocks, bcb_needs_counter); + counters.make_bcb_counters(); - let mut this = Self { + counters.coverage_counters + } + + fn with_num_bcbs(num_bcbs: usize) -> Self { + Self { counter_increment_sites: IndexVec::new(), bcb_counters: IndexVec::from_elem_n(None, num_bcbs), bcb_edge_counters: FxHashMap::default(), expressions: IndexVec::new(), expressions_memo: FxHashMap::default(), - }; - - MakeBcbCounters::new(&mut this, basic_coverage_blocks).make_bcb_counters(bcb_needs_counter); - - this + } } /// Shared helper used by [`Self::make_phys_node_counter`] and @@ -218,8 +220,8 @@ impl CoverageCounters { } } - pub(super) fn bcb_counter(&self, bcb: BasicCoverageBlock) -> Option<BcbCounter> { - self.bcb_counters[bcb] + pub(super) fn term_for_bcb(&self, bcb: BasicCoverageBlock) -> Option<CovTerm> { + self.bcb_counters[bcb].map(|counter| counter.as_term()) } /// Returns an iterator over all the nodes/edges in the coverage graph that @@ -265,19 +267,25 @@ impl CoverageCounters { /// Helper struct that allows counter creation to inspect the BCB graph. struct MakeBcbCounters<'a> { - coverage_counters: &'a mut CoverageCounters, + coverage_counters: CoverageCounters, basic_coverage_blocks: &'a CoverageGraph, + bcb_needs_counter: &'a BitSet<BasicCoverageBlock>, } impl<'a> MakeBcbCounters<'a> { fn new( - coverage_counters: &'a mut CoverageCounters, basic_coverage_blocks: &'a CoverageGraph, + bcb_needs_counter: &'a BitSet<BasicCoverageBlock>, ) -> Self { - Self { coverage_counters, basic_coverage_blocks } + assert_eq!(basic_coverage_blocks.num_nodes(), bcb_needs_counter.domain_size()); + Self { + coverage_counters: CoverageCounters::with_num_bcbs(basic_coverage_blocks.num_nodes()), + basic_coverage_blocks, + bcb_needs_counter, + } } - fn make_bcb_counters(&mut self, bcb_needs_counter: impl Fn(BasicCoverageBlock) -> bool) { + fn make_bcb_counters(&mut self) { debug!("make_bcb_counters(): adding a counter or expression to each BasicCoverageBlock"); // Traverse the coverage graph, ensuring that every node that needs a @@ -290,7 +298,7 @@ impl<'a> MakeBcbCounters<'a> { let mut traversal = TraverseCoverageGraphWithLoops::new(self.basic_coverage_blocks); while let Some(bcb) = traversal.next() { let _span = debug_span!("traversal", ?bcb).entered(); - if bcb_needs_counter(bcb) { + if self.bcb_needs_counter.contains(bcb) { self.make_node_counter_and_out_edge_counters(&traversal, bcb); } } diff --git a/compiler/rustc_mir_transform/src/coverage/mappings.rs b/compiler/rustc_mir_transform/src/coverage/mappings.rs index ec5ba354805..bc86ae22a0d 100644 --- a/compiler/rustc_mir_transform/src/coverage/mappings.rs +++ b/compiler/rustc_mir_transform/src/coverage/mappings.rs @@ -1,10 +1,11 @@ use std::collections::BTreeSet; +use rustc_data_structures::fx::FxIndexMap; use rustc_data_structures::graph::DirectedGraph; use rustc_index::IndexVec; use rustc_index::bit_set::BitSet; use rustc_middle::mir::coverage::{ - BlockMarkerId, BranchSpan, ConditionInfo, CoverageInfoHi, CoverageKind, + BlockMarkerId, BranchSpan, ConditionId, ConditionInfo, CoverageInfoHi, CoverageKind, }; use rustc_middle::mir::{self, BasicBlock, StatementKind}; use rustc_middle::ty::TyCtxt; @@ -14,6 +15,7 @@ use crate::coverage::ExtractedHirInfo; use crate::coverage::graph::{BasicCoverageBlock, CoverageGraph, START_BCB}; use crate::coverage::spans::extract_refined_covspans; use crate::coverage::unexpand::unexpand_into_body_span; +use crate::errors::MCDCExceedsTestVectorLimit; /// Associates an ordinary executable code span with its corresponding BCB. #[derive(Debug)] @@ -38,10 +40,11 @@ pub(super) struct MCDCBranch { pub(super) span: Span, pub(super) true_bcb: BasicCoverageBlock, pub(super) false_bcb: BasicCoverageBlock, - /// If `None`, this actually represents a normal branch mapping inserted - /// for code that was too complex for MC/DC. - pub(super) condition_info: Option<ConditionInfo>, - pub(super) decision_depth: u16, + pub(super) condition_info: ConditionInfo, + // Offset added to test vector idx if this branch is evaluated to true. + pub(super) true_index: usize, + // Offset added to test vector idx if this branch is evaluated to false. + pub(super) false_index: usize, } /// Associates an MC/DC decision with its join BCBs. @@ -49,11 +52,15 @@ pub(super) struct MCDCBranch { pub(super) struct MCDCDecision { pub(super) span: Span, pub(super) end_bcbs: BTreeSet<BasicCoverageBlock>, - pub(super) bitmap_idx: u32, - pub(super) num_conditions: u16, + pub(super) bitmap_idx: usize, + pub(super) num_test_vectors: usize, pub(super) decision_depth: u16, } +// LLVM uses `i32` to index the bitmap. Thus `i32::MAX` is the hard limit for number of all test vectors +// in a function. +const MCDC_MAX_BITMAP_SIZE: usize = i32::MAX as usize; + #[derive(Default)] pub(super) struct ExtractedMappings { /// Store our own copy of [`CoverageGraph::num_nodes`], so that we don't @@ -62,9 +69,9 @@ pub(super) struct ExtractedMappings { pub(super) num_bcbs: usize, pub(super) code_mappings: Vec<CodeMapping>, pub(super) branch_pairs: Vec<BranchPair>, - pub(super) mcdc_bitmap_bytes: u32, - pub(super) mcdc_branches: Vec<MCDCBranch>, - pub(super) mcdc_decisions: Vec<MCDCDecision>, + pub(super) mcdc_bitmap_bits: usize, + pub(super) mcdc_degraded_branches: Vec<MCDCBranch>, + pub(super) mcdc_mappings: Vec<(MCDCDecision, Vec<MCDCBranch>)>, } /// Extracts coverage-relevant spans from MIR, and associates them with @@ -77,9 +84,9 @@ pub(super) fn extract_all_mapping_info_from_mir<'tcx>( ) -> ExtractedMappings { let mut code_mappings = vec![]; let mut branch_pairs = vec![]; - let mut mcdc_bitmap_bytes = 0; - let mut mcdc_branches = vec![]; - let mut mcdc_decisions = vec![]; + let mut mcdc_bitmap_bits = 0; + let mut mcdc_degraded_branches = vec![]; + let mut mcdc_mappings = vec![]; if hir_info.is_async_fn || tcx.sess.coverage_no_mir_spans() { // An async function desugars into a function that returns a future, @@ -102,20 +109,21 @@ pub(super) fn extract_all_mapping_info_from_mir<'tcx>( extract_mcdc_mappings( mir_body, + tcx, hir_info.body_span, basic_coverage_blocks, - &mut mcdc_bitmap_bytes, - &mut mcdc_branches, - &mut mcdc_decisions, + &mut mcdc_bitmap_bits, + &mut mcdc_degraded_branches, + &mut mcdc_mappings, ); ExtractedMappings { num_bcbs: basic_coverage_blocks.num_nodes(), code_mappings, branch_pairs, - mcdc_bitmap_bytes, - mcdc_branches, - mcdc_decisions, + mcdc_bitmap_bits, + mcdc_degraded_branches, + mcdc_mappings, } } @@ -126,9 +134,9 @@ impl ExtractedMappings { num_bcbs, code_mappings, branch_pairs, - mcdc_bitmap_bytes: _, - mcdc_branches, - mcdc_decisions, + mcdc_bitmap_bits: _, + mcdc_degraded_branches, + mcdc_mappings, } = self; // Identify which BCBs have one or more mappings. @@ -144,7 +152,10 @@ impl ExtractedMappings { insert(true_bcb); insert(false_bcb); } - for &MCDCBranch { true_bcb, false_bcb, .. } in mcdc_branches { + for &MCDCBranch { true_bcb, false_bcb, .. } in mcdc_degraded_branches + .iter() + .chain(mcdc_mappings.iter().map(|(_, branches)| branches.into_iter()).flatten()) + { insert(true_bcb); insert(false_bcb); } @@ -152,8 +163,8 @@ impl ExtractedMappings { // MC/DC decisions refer to BCBs, but don't require those BCBs to have counters. if bcbs_with_counter_mappings.is_empty() { debug_assert!( - mcdc_decisions.is_empty(), - "A function with no counter mappings shouldn't have any decisions: {mcdc_decisions:?}", + mcdc_mappings.is_empty(), + "A function with no counter mappings shouldn't have any decisions: {mcdc_mappings:?}", ); } @@ -230,11 +241,12 @@ pub(super) fn extract_branch_pairs( pub(super) fn extract_mcdc_mappings( mir_body: &mir::Body<'_>, + tcx: TyCtxt<'_>, body_span: Span, basic_coverage_blocks: &CoverageGraph, - mcdc_bitmap_bytes: &mut u32, - mcdc_branches: &mut impl Extend<MCDCBranch>, - mcdc_decisions: &mut impl Extend<MCDCDecision>, + mcdc_bitmap_bits: &mut usize, + mcdc_degraded_branches: &mut impl Extend<MCDCBranch>, + mcdc_mappings: &mut impl Extend<(MCDCDecision, Vec<MCDCBranch>)>, ) { let Some(coverage_info_hi) = mir_body.coverage_info_hi.as_deref() else { return }; @@ -257,43 +269,146 @@ pub(super) fn extract_mcdc_mappings( Some((span, true_bcb, false_bcb)) }; - mcdc_branches.extend(coverage_info_hi.mcdc_branch_spans.iter().filter_map( - |&mir::coverage::MCDCBranchSpan { - span: raw_span, - condition_info, - true_marker, - false_marker, - decision_depth, - }| { - let (span, true_bcb, false_bcb) = - check_branch_bcb(raw_span, true_marker, false_marker)?; - Some(MCDCBranch { span, true_bcb, false_bcb, condition_info, decision_depth }) - }, - )); - - mcdc_decisions.extend(coverage_info_hi.mcdc_decision_spans.iter().filter_map( - |decision: &mir::coverage::MCDCDecisionSpan| { - let span = unexpand_into_body_span(decision.span, body_span)?; - - let end_bcbs = decision - .end_markers - .iter() - .map(|&marker| bcb_from_marker(marker)) - .collect::<Option<_>>()?; - - // Each decision containing N conditions needs 2^N bits of space in - // the bitmap, rounded up to a whole number of bytes. - // The decision's "bitmap index" points to its first byte in the bitmap. - let bitmap_idx = *mcdc_bitmap_bytes; - *mcdc_bitmap_bytes += (1_u32 << decision.num_conditions).div_ceil(8); - - Some(MCDCDecision { + let to_mcdc_branch = |&mir::coverage::MCDCBranchSpan { + span: raw_span, + condition_info, + true_marker, + false_marker, + }| { + let (span, true_bcb, false_bcb) = check_branch_bcb(raw_span, true_marker, false_marker)?; + Some(MCDCBranch { + span, + true_bcb, + false_bcb, + condition_info, + true_index: usize::MAX, + false_index: usize::MAX, + }) + }; + + let mut get_bitmap_idx = |num_test_vectors: usize| -> Option<usize> { + let bitmap_idx = *mcdc_bitmap_bits; + let next_bitmap_bits = bitmap_idx.saturating_add(num_test_vectors); + (next_bitmap_bits <= MCDC_MAX_BITMAP_SIZE).then(|| { + *mcdc_bitmap_bits = next_bitmap_bits; + bitmap_idx + }) + }; + mcdc_degraded_branches + .extend(coverage_info_hi.mcdc_degraded_branch_spans.iter().filter_map(to_mcdc_branch)); + + mcdc_mappings.extend(coverage_info_hi.mcdc_spans.iter().filter_map(|(decision, branches)| { + if branches.len() == 0 { + return None; + } + let decision_span = unexpand_into_body_span(decision.span, body_span)?; + + let end_bcbs = decision + .end_markers + .iter() + .map(|&marker| bcb_from_marker(marker)) + .collect::<Option<_>>()?; + let mut branch_mappings: Vec<_> = branches.into_iter().filter_map(to_mcdc_branch).collect(); + if branch_mappings.len() != branches.len() { + mcdc_degraded_branches.extend(branch_mappings); + return None; + } + let num_test_vectors = calc_test_vectors_index(&mut branch_mappings); + let Some(bitmap_idx) = get_bitmap_idx(num_test_vectors) else { + tcx.dcx().emit_warn(MCDCExceedsTestVectorLimit { + span: decision_span, + max_num_test_vectors: MCDC_MAX_BITMAP_SIZE, + }); + mcdc_degraded_branches.extend(branch_mappings); + return None; + }; + // LLVM requires span of the decision contains all spans of its conditions. + // Usually the decision span meets the requirement well but in cases like macros it may not. + let span = branch_mappings + .iter() + .map(|branch| branch.span) + .reduce(|lhs, rhs| lhs.to(rhs)) + .map( + |joint_span| { + if decision_span.contains(joint_span) { decision_span } else { joint_span } + }, + ) + .expect("branch mappings are ensured to be non-empty as checked above"); + Some(( + MCDCDecision { span, end_bcbs, bitmap_idx, - num_conditions: decision.num_conditions as u16, + num_test_vectors, decision_depth: decision.decision_depth, - }) - }, - )); + }, + branch_mappings, + )) + })); +} + +// LLVM checks the executed test vector by accumulating indices of tested branches. +// We calculate number of all possible test vectors of the decision and assign indices +// to branches here. +// See [the rfc](https://discourse.llvm.org/t/rfc-coverage-new-algorithm-and-file-format-for-mc-dc/76798/) +// for more details about the algorithm. +// This function is mostly like [`TVIdxBuilder::TvIdxBuilder`](https://github.com/llvm/llvm-project/blob/d594d9f7f4dc6eb748b3261917db689fdc348b96/llvm/lib/ProfileData/Coverage/CoverageMapping.cpp#L226) +fn calc_test_vectors_index(conditions: &mut Vec<MCDCBranch>) -> usize { + let mut indegree_stats = IndexVec::<ConditionId, usize>::from_elem_n(0, conditions.len()); + // `num_paths` is `width` described at the llvm rfc, which indicates how many paths reaching the condition node. + let mut num_paths_stats = IndexVec::<ConditionId, usize>::from_elem_n(0, conditions.len()); + let mut next_conditions = conditions + .iter_mut() + .map(|branch| { + let ConditionInfo { condition_id, true_next_id, false_next_id } = branch.condition_info; + [true_next_id, false_next_id] + .into_iter() + .filter_map(std::convert::identity) + .for_each(|next_id| indegree_stats[next_id] += 1); + (condition_id, branch) + }) + .collect::<FxIndexMap<_, _>>(); + + let mut queue = std::collections::VecDeque::from_iter( + next_conditions.swap_remove(&ConditionId::START).into_iter(), + ); + num_paths_stats[ConditionId::START] = 1; + let mut decision_end_nodes = Vec::new(); + while let Some(branch) = queue.pop_front() { + let ConditionInfo { condition_id, true_next_id, false_next_id } = branch.condition_info; + let (false_index, true_index) = (&mut branch.false_index, &mut branch.true_index); + let this_paths_count = num_paths_stats[condition_id]; + // Note. First check the false next to ensure conditions are touched in same order with llvm-cov. + for (next, index) in [(false_next_id, false_index), (true_next_id, true_index)] { + if let Some(next_id) = next { + let next_paths_count = &mut num_paths_stats[next_id]; + *index = *next_paths_count; + *next_paths_count = next_paths_count.saturating_add(this_paths_count); + let next_indegree = &mut indegree_stats[next_id]; + *next_indegree -= 1; + if *next_indegree == 0 { + queue.push_back(next_conditions.swap_remove(&next_id).expect( + "conditions with non-zero indegree before must be in next_conditions", + )); + } + } else { + decision_end_nodes.push((this_paths_count, condition_id, index)); + } + } + } + assert!(next_conditions.is_empty(), "the decision tree has untouched nodes"); + let mut cur_idx = 0; + // LLVM hopes the end nodes are sorted in descending order by `num_paths` so that it can + // optimize bitmap size for decisions in tree form such as `a && b && c && d && ...`. + decision_end_nodes.sort_by_key(|(num_paths, _, _)| usize::MAX - *num_paths); + for (num_paths, condition_id, index) in decision_end_nodes { + assert_eq!( + num_paths, num_paths_stats[condition_id], + "end nodes should not be updated since they were visited" + ); + assert_eq!(*index, usize::MAX, "end nodes should not be assigned index before"); + *index = cur_idx; + cur_idx += num_paths; + } + cur_idx } diff --git a/compiler/rustc_mir_transform/src/coverage/mod.rs b/compiler/rustc_mir_transform/src/coverage/mod.rs index 104f340c8d6..d0f30314e79 100644 --- a/compiler/rustc_mir_transform/src/coverage/mod.rs +++ b/compiler/rustc_mir_transform/src/coverage/mod.rs @@ -94,9 +94,8 @@ fn instrument_function_for_coverage<'tcx>(tcx: TyCtxt<'tcx>, mir_body: &mut mir: return; } - let bcb_has_counter_mappings = |bcb| bcbs_with_counter_mappings.contains(bcb); let coverage_counters = - CoverageCounters::make_bcb_counters(&basic_coverage_blocks, bcb_has_counter_mappings); + CoverageCounters::make_bcb_counters(&basic_coverage_blocks, &bcbs_with_counter_mappings); let mappings = create_mappings(tcx, &hir_info, &extracted_mappings, &coverage_counters); if mappings.is_empty() { @@ -115,16 +114,16 @@ fn instrument_function_for_coverage<'tcx>(tcx: TyCtxt<'tcx>, mir_body: &mut mir: inject_mcdc_statements(mir_body, &basic_coverage_blocks, &extracted_mappings); let mcdc_num_condition_bitmaps = extracted_mappings - .mcdc_decisions + .mcdc_mappings .iter() - .map(|&mappings::MCDCDecision { decision_depth, .. }| decision_depth) + .map(|&(mappings::MCDCDecision { decision_depth, .. }, _)| decision_depth) .max() .map_or(0, |max| usize::from(max) + 1); mir_body.function_coverage_info = Some(Box::new(FunctionCoverageInfo { function_source_hash: hir_info.function_source_hash, num_counters: coverage_counters.num_counters(), - mcdc_bitmap_bytes: extracted_mappings.mcdc_bitmap_bytes, + mcdc_bitmap_bits: extracted_mappings.mcdc_bitmap_bits, expressions: coverage_counters.into_expressions(), mappings, mcdc_num_condition_bitmaps, @@ -153,12 +152,8 @@ fn create_mappings<'tcx>( &source_file.name.for_scope(tcx.sess, RemapPathScopeComponents::MACRO).to_string_lossy(), ); - let term_for_bcb = |bcb| { - coverage_counters - .bcb_counter(bcb) - .expect("all BCBs with spans were given counters") - .as_term() - }; + let term_for_bcb = + |bcb| coverage_counters.term_for_bcb(bcb).expect("all BCBs with spans were given counters"); let region_for_span = |span: Span| make_source_region(source_map, file_name, span, body_span); // Fully destructure the mappings struct to make sure we don't miss any kinds. @@ -166,9 +161,9 @@ fn create_mappings<'tcx>( num_bcbs: _, code_mappings, branch_pairs, - mcdc_bitmap_bytes: _, - mcdc_branches, - mcdc_decisions, + mcdc_bitmap_bits: _, + mcdc_degraded_branches, + mcdc_mappings, } = extracted_mappings; let mut mappings = Vec::new(); @@ -191,26 +186,79 @@ fn create_mappings<'tcx>( }, )); - mappings.extend(mcdc_branches.iter().filter_map( - |&mappings::MCDCBranch { span, true_bcb, false_bcb, condition_info, decision_depth: _ }| { + let term_for_bcb = + |bcb| coverage_counters.term_for_bcb(bcb).expect("all BCBs with spans were given counters"); + + // MCDC branch mappings are appended with their decisions in case decisions were ignored. + mappings.extend(mcdc_degraded_branches.iter().filter_map( + |&mappings::MCDCBranch { + span, + true_bcb, + false_bcb, + condition_info: _, + true_index: _, + false_index: _, + }| { let source_region = region_for_span(span)?; let true_term = term_for_bcb(true_bcb); let false_term = term_for_bcb(false_bcb); - let kind = match condition_info { - Some(mcdc_params) => MappingKind::MCDCBranch { true_term, false_term, mcdc_params }, - None => MappingKind::Branch { true_term, false_term }, - }; - Some(Mapping { kind, source_region }) + Some(Mapping { kind: MappingKind::Branch { true_term, false_term }, source_region }) }, )); - mappings.extend(mcdc_decisions.iter().filter_map( - |&mappings::MCDCDecision { span, bitmap_idx, num_conditions, .. }| { - let source_region = region_for_span(span)?; - let kind = MappingKind::MCDCDecision(DecisionInfo { bitmap_idx, num_conditions }); - Some(Mapping { kind, source_region }) - }, - )); + for (decision, branches) in mcdc_mappings { + let num_conditions = branches.len() as u16; + let conditions = branches + .into_iter() + .filter_map( + |&mappings::MCDCBranch { + span, + true_bcb, + false_bcb, + condition_info, + true_index: _, + false_index: _, + }| { + let source_region = region_for_span(span)?; + let true_term = term_for_bcb(true_bcb); + let false_term = term_for_bcb(false_bcb); + Some(Mapping { + kind: MappingKind::MCDCBranch { + true_term, + false_term, + mcdc_params: condition_info, + }, + source_region, + }) + }, + ) + .collect::<Vec<_>>(); + + if conditions.len() == num_conditions as usize + && let Some(source_region) = region_for_span(decision.span) + { + // LLVM requires end index for counter mapping regions. + let kind = MappingKind::MCDCDecision(DecisionInfo { + bitmap_idx: (decision.bitmap_idx + decision.num_test_vectors) as u32, + num_conditions, + }); + mappings.extend( + std::iter::once(Mapping { kind, source_region }).chain(conditions.into_iter()), + ); + } else { + mappings.extend(conditions.into_iter().map(|mapping| { + let MappingKind::MCDCBranch { true_term, false_term, mcdc_params: _ } = + mapping.kind + else { + unreachable!("all mappings here are MCDCBranch as shown above"); + }; + Mapping { + kind: MappingKind::Branch { true_term, false_term }, + source_region: mapping.source_region, + } + })) + } + } mappings } @@ -279,44 +327,41 @@ fn inject_mcdc_statements<'tcx>( basic_coverage_blocks: &CoverageGraph, extracted_mappings: &ExtractedMappings, ) { - // Inject test vector update first because `inject_statement` always insert new statement at - // head. - for &mappings::MCDCDecision { - span: _, - ref end_bcbs, - bitmap_idx, - num_conditions: _, - decision_depth, - } in &extracted_mappings.mcdc_decisions - { - for end in end_bcbs { - let end_bb = basic_coverage_blocks[*end].leader_bb(); + for (decision, conditions) in &extracted_mappings.mcdc_mappings { + // Inject test vector update first because `inject_statement` always insert new statement at head. + for &end in &decision.end_bcbs { + let end_bb = basic_coverage_blocks[end].leader_bb(); inject_statement( mir_body, - CoverageKind::TestVectorBitmapUpdate { bitmap_idx, decision_depth }, + CoverageKind::TestVectorBitmapUpdate { + bitmap_idx: decision.bitmap_idx as u32, + decision_depth: decision.decision_depth, + }, end_bb, ); } - } - for &mappings::MCDCBranch { span: _, true_bcb, false_bcb, condition_info, decision_depth } in - &extracted_mappings.mcdc_branches - { - let Some(condition_info) = condition_info else { continue }; - let id = condition_info.condition_id; - - let true_bb = basic_coverage_blocks[true_bcb].leader_bb(); - inject_statement( - mir_body, - CoverageKind::CondBitmapUpdate { id, value: true, decision_depth }, - true_bb, - ); - let false_bb = basic_coverage_blocks[false_bcb].leader_bb(); - inject_statement( - mir_body, - CoverageKind::CondBitmapUpdate { id, value: false, decision_depth }, - false_bb, - ); + for &mappings::MCDCBranch { + span: _, + true_bcb, + false_bcb, + condition_info: _, + true_index, + false_index, + } in conditions + { + for (index, bcb) in [(false_index, false_bcb), (true_index, true_bcb)] { + let bb = basic_coverage_blocks[bcb].leader_bb(); + inject_statement( + mir_body, + CoverageKind::CondBitmapUpdate { + index: index as u32, + decision_depth: decision.decision_depth, + }, + bb, + ); + } + } } } diff --git a/compiler/rustc_mir_transform/src/errors.rs b/compiler/rustc_mir_transform/src/errors.rs index 84d44c2ab4c..8b309147c64 100644 --- a/compiler/rustc_mir_transform/src/errors.rs +++ b/compiler/rustc_mir_transform/src/errors.rs @@ -89,6 +89,14 @@ pub(crate) struct FnItemRef { pub ident: String, } +#[derive(Diagnostic)] +#[diag(mir_transform_exceeds_mcdc_test_vector_limit)] +pub(crate) struct MCDCExceedsTestVectorLimit { + #[primary_span] + pub(crate) span: Span, + pub(crate) max_num_test_vectors: usize, +} + pub(crate) struct MustNotSupend<'a, 'tcx> { pub tcx: TyCtxt<'tcx>, pub yield_sp: Span, @@ -121,3 +129,10 @@ pub(crate) struct MustNotSuspendReason { pub span: Span, pub reason: String, } + +#[derive(LintDiagnostic)] +#[diag(mir_transform_undefined_transmute)] +#[note] +#[note(mir_transform_note2)] +#[help] +pub(crate) struct UndefinedTransmute; diff --git a/compiler/rustc_mir_transform/src/gvn.rs b/compiler/rustc_mir_transform/src/gvn.rs index 50c9702cb9b..daf868559bc 100644 --- a/compiler/rustc_mir_transform/src/gvn.rs +++ b/compiler/rustc_mir_transform/src/gvn.rs @@ -103,7 +103,7 @@ use rustc_middle::ty::layout::{HasParamEnv, LayoutOf}; use rustc_middle::ty::{self, Ty, TyCtxt}; use rustc_span::DUMMY_SP; use rustc_span::def_id::DefId; -use rustc_target::abi::{self, Abi, FIRST_VARIANT, FieldIdx, Size, VariantIdx}; +use rustc_target::abi::{self, Abi, FIRST_VARIANT, FieldIdx, Primitive, Size, VariantIdx}; use smallvec::SmallVec; use tracing::{debug, instrument, trace}; @@ -568,13 +568,29 @@ impl<'body, 'tcx> VnState<'body, 'tcx> { CastKind::Transmute => { let value = self.evaluated[value].as_ref()?; let to = self.ecx.layout_of(to).ok()?; - // `offset` for immediates only supports scalar/scalar-pair ABIs, - // so bail out if the target is not one. + // `offset` for immediates generally only supports projections that match the + // type of the immediate. However, as a HACK, we exploit that it can also do + // limited transmutes: it only works between types with the same layout, and + // cannot transmute pointers to integers. if value.as_mplace_or_imm().is_right() { - match (value.layout.abi, to.abi) { - (Abi::Scalar(..), Abi::Scalar(..)) => {} - (Abi::ScalarPair(..), Abi::ScalarPair(..)) => {} - _ => return None, + let can_transmute = match (value.layout.abi, to.abi) { + (Abi::Scalar(s1), Abi::Scalar(s2)) => { + s1.size(&self.ecx) == s2.size(&self.ecx) + && !matches!(s1.primitive(), Primitive::Pointer(..)) + } + (Abi::ScalarPair(a1, b1), Abi::ScalarPair(a2, b2)) => { + a1.size(&self.ecx) == a2.size(&self.ecx) && + b1.size(&self.ecx) == b2.size(&self.ecx) && + // The alignment of the second component determines its offset, so that also needs to match. + b1.align(&self.ecx) == b2.align(&self.ecx) && + // None of the inputs may be a pointer. + !matches!(a1.primitive(), Primitive::Pointer(..)) + && !matches!(b1.primitive(), Primitive::Pointer(..)) + } + _ => false, + }; + if !can_transmute { + return None; } } value.offset(Size::ZERO, to, &self.ecx).discard_err()? @@ -1137,7 +1153,7 @@ impl<'body, 'tcx> VnState<'body, 'tcx> { (UnOp::PtrMetadata, Value::Aggregate(AggregateTy::RawPtr { .. }, _, fields)) => { return Some(fields[1]); } - // We have an unsizing cast, which assigns the length to fat pointer metadata. + // We have an unsizing cast, which assigns the length to wide pointer metadata. ( UnOp::PtrMetadata, Value::Cast { @@ -1421,7 +1437,7 @@ impl<'body, 'tcx> VnState<'body, 'tcx> { let mut inner = self.simplify_place_value(place, location)?; - // The length information is stored in the fat pointer. + // The length information is stored in the wide pointer. // Reborrowing copies length information from one pointer to the other. while let Value::Address { place: borrowed, .. } = self.get(inner) && let [PlaceElem::Deref] = borrowed.projection[..] @@ -1430,7 +1446,7 @@ impl<'body, 'tcx> VnState<'body, 'tcx> { inner = borrowed; } - // We have an unsizing cast, which assigns the length to fat pointer metadata. + // We have an unsizing cast, which assigns the length to wide pointer metadata. if let Value::Cast { kind, from, to, .. } = self.get(inner) && let CastKind::PointerCoercion(ty::adjustment::PointerCoercion::Unsize, _) = kind && let Some(from) = from.builtin_deref(true) diff --git a/compiler/rustc_mir_transform/src/jump_threading.rs b/compiler/rustc_mir_transform/src/jump_threading.rs index 1844b97887a..9b9b0b705bf 100644 --- a/compiler/rustc_mir_transform/src/jump_threading.rs +++ b/compiler/rustc_mir_transform/src/jump_threading.rs @@ -494,8 +494,16 @@ impl<'a, 'tcx> TOFinder<'a, 'tcx> { } // Transfer the conditions on the copy rhs, after inversing polarity. Rvalue::UnaryOp(UnOp::Not, Operand::Move(place) | Operand::Copy(place)) => { + if !place.ty(self.body, self.tcx).ty.is_bool() { + // Constructing the conditions by inverting the polarity + // of equality is only correct for bools. That is to say, + // `!a == b` is not `a != b` for integers greater than 1 bit. + return; + } let Some(conditions) = state.try_get_idx(lhs, &self.map) else { return }; let Some(place) = self.map.find(place.as_ref()) else { return }; + // FIXME: I think This could be generalized to not bool if we + // actually perform a logical not on the condition's value. let conds = conditions.map(self.arena, Condition::inv); state.insert_value_idx(place, conds, &self.map); } diff --git a/compiler/rustc_mir_transform/src/known_panics_lint.rs b/compiler/rustc_mir_transform/src/known_panics_lint.rs index ccc029b1e28..8f490094d60 100644 --- a/compiler/rustc_mir_transform/src/known_panics_lint.rs +++ b/compiler/rustc_mir_transform/src/known_panics_lint.rs @@ -600,13 +600,15 @@ impl<'mir, 'tcx> ConstPropagator<'mir, 'tcx> { } Len(place) => { - let len = match self.get_const(place)? { - Value::Immediate(src) => src.len(&self.ecx).discard_err()?, - Value::Aggregate { fields, .. } => fields.len() as u64, - Value::Uninit => match place.ty(self.local_decls(), self.tcx).ty.kind() { - ty::Array(_, n) => n.try_eval_target_usize(self.tcx, self.param_env)?, - _ => return None, - }, + let len = if let ty::Array(_, n) = place.ty(self.local_decls(), self.tcx).ty.kind() + { + n.try_eval_target_usize(self.tcx, self.param_env)? + } else { + match self.get_const(place)? { + Value::Immediate(src) => src.len(&self.ecx).discard_err()?, + Value::Aggregate { fields, .. } => fields.len() as u64, + Value::Uninit => return None, + } }; ImmTy::from_scalar(Scalar::from_target_usize(len, self), layout).into() } diff --git a/compiler/rustc_mir_transform/src/lib.rs b/compiler/rustc_mir_transform/src/lib.rs index 4c090665992..d184328748f 100644 --- a/compiler/rustc_mir_transform/src/lib.rs +++ b/compiler/rustc_mir_transform/src/lib.rs @@ -51,6 +51,7 @@ mod add_subtyping_projections; mod check_alignment; mod check_const_item_mutation; mod check_packed_ref; +mod check_undefined_transmutes; // This pass is public to allow external drivers to perform MIR cleanup pub mod cleanup_post_borrowck; mod copy_prop; @@ -298,6 +299,7 @@ fn mir_built(tcx: TyCtxt<'_>, def: LocalDefId) -> &Steal<Body<'_>> { &Lint(check_packed_ref::CheckPackedRef), &Lint(check_const_item_mutation::CheckConstItemMutation), &Lint(function_item_references::FunctionItemReferences), + &Lint(check_undefined_transmutes::CheckUndefinedTransmutes), // What we need to do constant evaluation. &simplify::SimplifyCfg::Initial, &Lint(sanity_check::SanityCheck), |
