Skip to content

Commit d662f80

Browse files
committedNov 3, 2020
Auto merge of #78697 - JohnTitor:rollup-q0fchpv, r=JohnTitor
Rollup of 8 pull requests Successful merges: - #78376 (Treat trailing semicolon as a statement in macro call) - #78400 (Fix unindent in doc comments) - #78575 (Add a test for compiletest rustc-env & unset-rustc-env directives) - #78616 (Document -Zinstrument-coverage) - #78663 (Fix ICE when a future-incompat-report has its command-line level capped) - #78664 (Fix intrinsic size_of stable link) - #78668 (inliner: Remove redundant loop) - #78676 (add mipsel-unknown-none target) Failed merges: r? `@ghost` `@rustbot` modify labels: rollup
2 parents 7b5a9e9 + 1cb137b commit d662f80

27 files changed

+578
-185
lines changed
 

‎compiler/rustc_ast/src/ast.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -905,6 +905,13 @@ pub struct Stmt {
905905
}
906906

907907
impl Stmt {
908+
pub fn has_trailing_semicolon(&self) -> bool {
909+
match &self.kind {
910+
StmtKind::Semi(_) => true,
911+
StmtKind::MacCall(mac) => matches!(mac.style, MacStmtStyle::Semicolon),
912+
_ => false,
913+
}
914+
}
908915
pub fn add_trailing_semicolon(mut self) -> Self {
909916
self.kind = match self.kind {
910917
StmtKind::Expr(expr) => StmtKind::Semi(expr),

‎compiler/rustc_expand/src/placeholders.rs

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -310,8 +310,44 @@ impl<'a, 'b> MutVisitor for PlaceholderExpander<'a, 'b> {
310310
};
311311

312312
if style == ast::MacStmtStyle::Semicolon {
313+
// Implement the proposal described in
314+
// https://github.com/rust-lang/rust/issues/61733#issuecomment-509626449
315+
//
316+
// The macro invocation expands to the list of statements.
317+
// If the list of statements is empty, then 'parse'
318+
// the trailing semicolon on the original invocation
319+
// as an empty statement. That is:
320+
//
321+
// `empty();` is parsed as a single `StmtKind::Empty`
322+
//
323+
// If the list of statements is non-empty, see if the
324+
// final statement alreayd has a trailing semicolon.
325+
//
326+
// If it doesn't have a semicolon, then 'parse' the trailing semicolon
327+
// from the invocation as part of the final statement,
328+
// using `stmt.add_trailing_semicolon()`
329+
//
330+
// If it does have a semicolon, then 'parse' the trailing semicolon
331+
// from the invocation as a new StmtKind::Empty
332+
333+
// FIXME: We will need to preserve the original
334+
// semicolon token and span as part of #15701
335+
let empty_stmt = ast::Stmt {
336+
id: ast::DUMMY_NODE_ID,
337+
kind: ast::StmtKind::Empty,
338+
span: DUMMY_SP,
339+
tokens: None,
340+
};
341+
313342
if let Some(stmt) = stmts.pop() {
314-
stmts.push(stmt.add_trailing_semicolon());
343+
if stmt.has_trailing_semicolon() {
344+
stmts.push(stmt);
345+
stmts.push(empty_stmt);
346+
} else {
347+
stmts.push(stmt.add_trailing_semicolon());
348+
}
349+
} else {
350+
stmts.push(empty_stmt);
315351
}
316352
}
317353

‎compiler/rustc_lint/src/levels.rs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ impl<'s> LintLevelsBuilder<'s> {
7474

7575
for &(ref lint_name, level) in &sess.opts.lint_opts {
7676
store.check_lint_name_cmdline(sess, &lint_name, level);
77+
let orig_level = level;
7778

7879
// If the cap is less than this specified level, e.g., if we've got
7980
// `--cap-lints allow` but we've also got `-D foo` then we ignore
@@ -88,7 +89,7 @@ impl<'s> LintLevelsBuilder<'s> {
8889
};
8990
for id in ids {
9091
self.check_gated_lint(id, DUMMY_SP);
91-
let src = LintSource::CommandLine(lint_flag_val);
92+
let src = LintSource::CommandLine(lint_flag_val, orig_level);
9293
specs.insert(id, (level, src));
9394
}
9495
}
@@ -123,7 +124,7 @@ impl<'s> LintLevelsBuilder<'s> {
123124
diag_builder.note(&rationale.as_str());
124125
}
125126
}
126-
LintSource::CommandLine(_) => {
127+
LintSource::CommandLine(_, _) => {
127128
diag_builder.note("`forbid` lint level was set on command line");
128129
}
129130
}
@@ -422,7 +423,7 @@ impl<'s> LintLevelsBuilder<'s> {
422423
let forbidden_lint_name = match forbid_src {
423424
LintSource::Default => id.to_string(),
424425
LintSource::Node(name, _, _) => name.to_string(),
425-
LintSource::CommandLine(name) => name.to_string(),
426+
LintSource::CommandLine(name, _) => name.to_string(),
426427
};
427428
let (lint_attr_name, lint_attr_span) = match *src {
428429
LintSource::Node(name, span, _) => (name, span),
@@ -446,7 +447,7 @@ impl<'s> LintLevelsBuilder<'s> {
446447
diag_builder.note(&rationale.as_str());
447448
}
448449
}
449-
LintSource::CommandLine(_) => {
450+
LintSource::CommandLine(_, _) => {
450451
diag_builder.note("`forbid` lint level was set on command line");
451452
}
452453
}

‎compiler/rustc_lint/src/redundant_semicolon.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,11 @@ impl EarlyLintPass for RedundantSemicolons {
4242

4343
fn maybe_lint_redundant_semis(cx: &EarlyContext<'_>, seq: &mut Option<(Span, bool)>) {
4444
if let Some((span, multiple)) = seq.take() {
45+
// FIXME: Find a better way of ignoring the trailing
46+
// semicolon from macro expansion
47+
if span == rustc_span::DUMMY_SP {
48+
return;
49+
}
4550
cx.struct_span_lint(REDUNDANT_SEMICOLONS, span, |lint| {
4651
let (msg, rem) = if multiple {
4752
("unnecessary trailing semicolons", "remove these semicolons")

‎compiler/rustc_middle/src/lint.rs

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,23 +22,25 @@ pub enum LintSource {
2222
Node(Symbol, Span, Option<Symbol> /* RFC 2383 reason */),
2323

2424
/// Lint level was set by a command-line flag.
25-
CommandLine(Symbol),
25+
/// The provided `Level` is the level specified on the command line -
26+
/// the actual level may be lower due to `--cap-lints`
27+
CommandLine(Symbol, Level),
2628
}
2729

2830
impl LintSource {
2931
pub fn name(&self) -> Symbol {
3032
match *self {
3133
LintSource::Default => symbol::kw::Default,
3234
LintSource::Node(name, _, _) => name,
33-
LintSource::CommandLine(name) => name,
35+
LintSource::CommandLine(name, _) => name,
3436
}
3537
}
3638

3739
pub fn span(&self) -> Span {
3840
match *self {
3941
LintSource::Default => DUMMY_SP,
4042
LintSource::Node(_, span, _) => span,
41-
LintSource::CommandLine(_) => DUMMY_SP,
43+
LintSource::CommandLine(_, _) => DUMMY_SP,
4244
}
4345
}
4446
}
@@ -279,12 +281,12 @@ pub fn struct_lint_level<'s, 'd>(
279281
&format!("`#[{}({})]` on by default", level.as_str(), name),
280282
);
281283
}
282-
LintSource::CommandLine(lint_flag_val) => {
283-
let flag = match level {
284+
LintSource::CommandLine(lint_flag_val, orig_level) => {
285+
let flag = match orig_level {
284286
Level::Warn => "-W",
285287
Level::Deny => "-D",
286288
Level::Forbid => "-F",
287-
Level::Allow => panic!(),
289+
Level::Allow => "-A",
288290
};
289291
let hyphen_case_lint_name = name.replace("_", "-");
290292
if lint_flag_val.as_str() == name {

‎compiler/rustc_mir/src/transform/inline.rs

Lines changed: 60 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -93,96 +93,79 @@ impl Inliner<'tcx> {
9393
return;
9494
}
9595

96-
let mut local_change;
9796
let mut changed = false;
97+
while let Some(callsite) = callsites.pop_front() {
98+
debug!("checking whether to inline callsite {:?}", callsite);
9899

99-
loop {
100-
local_change = false;
101-
while let Some(callsite) = callsites.pop_front() {
102-
debug!("checking whether to inline callsite {:?}", callsite);
103-
104-
if let InstanceDef::Item(_) = callsite.callee.def {
105-
if !self.tcx.is_mir_available(callsite.callee.def_id()) {
106-
debug!(
107-
"checking whether to inline callsite {:?} - MIR unavailable",
108-
callsite,
109-
);
110-
continue;
111-
}
100+
if let InstanceDef::Item(_) = callsite.callee.def {
101+
if !self.tcx.is_mir_available(callsite.callee.def_id()) {
102+
debug!("checking whether to inline callsite {:?} - MIR unavailable", callsite,);
103+
continue;
112104
}
105+
}
113106

114-
let callee_body = if let Some(callee_def_id) = callsite.callee.def_id().as_local() {
115-
let callee_hir_id = self.tcx.hir().local_def_id_to_hir_id(callee_def_id);
116-
// Avoid a cycle here by only using `instance_mir` only if we have
117-
// a lower `HirId` than the callee. This ensures that the callee will
118-
// not inline us. This trick only works without incremental compilation.
119-
// So don't do it if that is enabled. Also avoid inlining into generators,
120-
// since their `optimized_mir` is used for layout computation, which can
121-
// create a cycle, even when no attempt is made to inline the function
122-
// in the other direction.
123-
if !self.tcx.dep_graph.is_fully_enabled()
124-
&& self_hir_id < callee_hir_id
125-
&& caller_body.generator_kind.is_none()
126-
{
127-
self.tcx.instance_mir(callsite.callee.def)
128-
} else {
129-
continue;
130-
}
131-
} else {
132-
// This cannot result in a cycle since the callee MIR is from another crate
133-
// and is already optimized.
107+
let callee_body = if let Some(callee_def_id) = callsite.callee.def_id().as_local() {
108+
let callee_hir_id = self.tcx.hir().local_def_id_to_hir_id(callee_def_id);
109+
// Avoid a cycle here by only using `instance_mir` only if we have
110+
// a lower `HirId` than the callee. This ensures that the callee will
111+
// not inline us. This trick only works without incremental compilation.
112+
// So don't do it if that is enabled. Also avoid inlining into generators,
113+
// since their `optimized_mir` is used for layout computation, which can
114+
// create a cycle, even when no attempt is made to inline the function
115+
// in the other direction.
116+
if !self.tcx.dep_graph.is_fully_enabled()
117+
&& self_hir_id < callee_hir_id
118+
&& caller_body.generator_kind.is_none()
119+
{
134120
self.tcx.instance_mir(callsite.callee.def)
135-
};
136-
137-
let callee_body: &Body<'tcx> = &*callee_body;
138-
139-
let callee_body = if self.consider_optimizing(callsite, callee_body) {
140-
self.tcx.subst_and_normalize_erasing_regions(
141-
&callsite.callee.substs,
142-
self.param_env,
143-
callee_body,
144-
)
145121
} else {
146122
continue;
147-
};
123+
}
124+
} else {
125+
// This cannot result in a cycle since the callee MIR is from another crate
126+
// and is already optimized.
127+
self.tcx.instance_mir(callsite.callee.def)
128+
};
148129

149-
// Copy only unevaluated constants from the callee_body into the caller_body.
150-
// Although we are only pushing `ConstKind::Unevaluated` consts to
151-
// `required_consts`, here we may not only have `ConstKind::Unevaluated`
152-
// because we are calling `subst_and_normalize_erasing_regions`.
153-
caller_body.required_consts.extend(
154-
callee_body.required_consts.iter().copied().filter(|&constant| {
155-
matches!(constant.literal.val, ConstKind::Unevaluated(_, _, _))
156-
}),
157-
);
130+
let callee_body: &Body<'tcx> = &*callee_body;
158131

159-
let start = caller_body.basic_blocks().len();
160-
debug!("attempting to inline callsite {:?} - body={:?}", callsite, callee_body);
161-
if !self.inline_call(callsite, caller_body, callee_body) {
162-
debug!("attempting to inline callsite {:?} - failure", callsite);
163-
continue;
164-
}
165-
debug!("attempting to inline callsite {:?} - success", callsite);
166-
167-
// Add callsites from inlined function
168-
for (bb, bb_data) in caller_body.basic_blocks().iter_enumerated().skip(start) {
169-
if let Some(new_callsite) =
170-
self.get_valid_function_call(bb, bb_data, caller_body)
171-
{
172-
// Don't inline the same function multiple times.
173-
if callsite.callee != new_callsite.callee {
174-
callsites.push_back(new_callsite);
175-
}
132+
let callee_body = if self.consider_optimizing(callsite, callee_body) {
133+
self.tcx.subst_and_normalize_erasing_regions(
134+
&callsite.callee.substs,
135+
self.param_env,
136+
callee_body,
137+
)
138+
} else {
139+
continue;
140+
};
141+
142+
// Copy only unevaluated constants from the callee_body into the caller_body.
143+
// Although we are only pushing `ConstKind::Unevaluated` consts to
144+
// `required_consts`, here we may not only have `ConstKind::Unevaluated`
145+
// because we are calling `subst_and_normalize_erasing_regions`.
146+
caller_body.required_consts.extend(callee_body.required_consts.iter().copied().filter(
147+
|&constant| matches!(constant.literal.val, ConstKind::Unevaluated(_, _, _)),
148+
));
149+
150+
let start = caller_body.basic_blocks().len();
151+
debug!("attempting to inline callsite {:?} - body={:?}", callsite, callee_body);
152+
if !self.inline_call(callsite, caller_body, callee_body) {
153+
debug!("attempting to inline callsite {:?} - failure", callsite);
154+
continue;
155+
}
156+
debug!("attempting to inline callsite {:?} - success", callsite);
157+
158+
// Add callsites from inlined function
159+
for (bb, bb_data) in caller_body.basic_blocks().iter_enumerated().skip(start) {
160+
if let Some(new_callsite) = self.get_valid_function_call(bb, bb_data, caller_body) {
161+
// Don't inline the same function multiple times.
162+
if callsite.callee != new_callsite.callee {
163+
callsites.push_back(new_callsite);
176164
}
177165
}
178-
179-
local_change = true;
180-
changed = true;
181166
}
182167

183-
if !local_change {
184-
break;
185-
}
168+
changed = true;
186169
}
187170

188171
// Simplify if we inlined anything.
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
//! Bare MIPS32r2, little endian, softfloat, O32 calling convention
2+
//!
3+
//! Can be used for MIPS M4K core (e.g. on PIC32MX devices)
4+
5+
use crate::spec::abi::Abi;
6+
use crate::spec::{LinkerFlavor, LldFlavor, RelocModel};
7+
use crate::spec::{PanicStrategy, Target, TargetOptions};
8+
9+
pub fn target() -> Target {
10+
Target {
11+
llvm_target: "mipsel-unknown-none".to_string(),
12+
target_endian: "little".to_string(),
13+
pointer_width: 32,
14+
target_c_int_width: "32".to_string(),
15+
data_layout: "e-m:m-p:32:32-i8:8:32-i16:16:32-i64:64-n32-S64".to_string(),
16+
arch: "mips".to_string(),
17+
target_os: "none".to_string(),
18+
target_env: String::new(),
19+
target_vendor: String::new(),
20+
linker_flavor: LinkerFlavor::Lld(LldFlavor::Ld),
21+
22+
options: TargetOptions {
23+
cpu: "mips32r2".to_string(),
24+
features: "+mips32r2,+soft-float,+noabicalls".to_string(),
25+
max_atomic_width: Some(32),
26+
executables: true,
27+
linker: Some("rust-lld".to_owned()),
28+
panic_strategy: PanicStrategy::Abort,
29+
relocation_model: RelocModel::Static,
30+
unsupported_abis: vec![
31+
Abi::Stdcall,
32+
Abi::Fastcall,
33+
Abi::Vectorcall,
34+
Abi::Thiscall,
35+
Abi::Win64,
36+
Abi::SysV64,
37+
],
38+
emit_debug_gdb_scripts: false,
39+
..Default::default()
40+
},
41+
}
42+
}

‎compiler/rustc_target/src/spec/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -653,6 +653,7 @@ supported_targets! {
653653
("powerpc64-wrs-vxworks", powerpc64_wrs_vxworks),
654654

655655
("mipsel-sony-psp", mipsel_sony_psp),
656+
("mipsel-unknown-none", mipsel_unknown_none),
656657
("thumbv4t-none-eabi", thumbv4t_none_eabi),
657658
}
658659

‎library/core/src/intrinsics.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -764,7 +764,7 @@ extern "rust-intrinsic" {
764764
/// More specifically, this is the offset in bytes between successive
765765
/// items of the same type, including alignment padding.
766766
///
767-
/// The stabilized version of this intrinsic is [`size_of`].
767+
/// The stabilized version of this intrinsic is [`crate::mem::size_of`].
768768
#[rustc_const_stable(feature = "const_size_of", since = "1.40.0")]
769769
pub fn size_of<T>() -> usize;
770770

‎src/doc/rustc/src/platform-support.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,7 @@ target | std | host | notes
180180
`i686-wrs-vxworks` | ? | |
181181
`mips-unknown-linux-uclibc` | ✓ | | MIPS Linux with uClibc
182182
`mipsel-unknown-linux-uclibc` | ✓ | | MIPS (LE) Linux with uClibc
183+
`mipsel-unknown-none` | * | | Bare MIPS (LE) softfloat
183184
`mipsel-sony-psp` | * | | MIPS (LE) Sony PlayStation Portable (PSP)
184185
`mipsisa32r6-unknown-linux-gnu` | ? | |
185186
`mipsisa32r6el-unknown-linux-gnu` | ? | |
Loading
Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
# `source-based-code-coverage`
2+
3+
The feature request for this feature is: [#34701]
4+
5+
The Major Change Proposal (MCP) for this feature is: [#278](https://github.com/rust-lang/compiler-team/issues/278)
6+
7+
------------------------
8+
9+
## Introduction
10+
11+
The Rust compiler includes two code coverage implementations:
12+
13+
* A GCC-compatible, gcov-based coverage implementation, enabled with [`-Zprofile`](profile.md), which operates on DebugInfo.
14+
* A source-based code coverage implementation, enabled with `-Zinstrument-coverage`, which uses LLVM's native coverage instrumentation to generate very precise coverage data.
15+
16+
This document describes how to enable and use the LLVM instrumentation-based coverage, via the `-Zinstrument-coverage` compiler flag.
17+
18+
## How it works
19+
20+
When `-Zinstrument-coverage` is enabled, the Rust compiler enhances rust-based libraries and binaries by:
21+
22+
* Automatically injecting calls to an LLVM intrinsic ([`llvm.instrprof.increment`]), at functions and branches in compiled code, to increment counters when conditional sections of code are executed.
23+
* Embedding additional information in the data section of each library and binary (using the [LLVM Code Coverage Mapping Format]), to define the code regions (start and end positions in the source code) being counted.
24+
25+
When running a coverage-instrumented program, the counter values are written to a `profraw` file at program termination. LLVM bundles tools that read the counter results, combine those results with the coverage map (embedded in the program binary), and generate coverage reports in multiple formats.
26+
27+
## Enable coverage profiling in the Rust compiler
28+
29+
Rust's source-based code coverage requires the Rust "profiler runtime". Without it, compiling with `-Zinstrument-coverage` generates an error that the profiler runtime is missing.
30+
31+
The Rust `nightly` distribution channel should include the profiler runtime, by default.
32+
33+
*IMPORTANT:* If you are building the Rust compiler from the source distribution, the profiler runtime is *not* enabled in the default `config.toml.example`, and may not be enabled in your `config.toml`. Edit the `config.toml` file, and find the `profiler` feature entry. Uncomment it and set it to `true`:
34+
35+
```toml
36+
# Build the profiler runtime (required when compiling with options that depend
37+
# on this runtime, such as `-C profile-generate` or `-Z instrument-coverage`).
38+
profiler = true
39+
```
40+
41+
Then rebuild the Rust compiler (see [rustc-dev-guide-how-to-build-and-run]).
42+
43+
### Building the demangler
44+
45+
LLVM coverage reporting tools generate results that can include function names and other symbol references, and the raw coverage results report symbols using the compiler's "mangled" version of the symbol names, which can be difficult to interpret. To work around this issue, LLVM coverage tools also support a user-specified symbol name demangler.
46+
47+
One option for a Rust demangler is [`rustfilt`](https://crates.io/crates/rustfilt), which can be installed with:
48+
49+
```shell
50+
cargo install rustfilt
51+
```
52+
53+
Another option, if you are building from the Rust compiler source distribution, is to use the `rust-demangler` tool included in the Rust source distribution, which can be built with:
54+
55+
```shell
56+
$ ./x.py build rust-demangler
57+
```
58+
59+
## Compiling with coverage enabled
60+
61+
Set the `-Zinstrument-coverage` compiler flag in order to enable LLVM source-based code coverage profiling.
62+
63+
With `cargo`, you can instrument your program binary *and* dependencies at the same time.
64+
65+
For example (if your project's Cargo.toml builds a binary by default):
66+
67+
```shell
68+
$ cd your-project
69+
$ cargo clean
70+
$ RUSTFLAGS="-Zinstrument-coverage" cargo build
71+
```
72+
73+
If `cargo` is not configured to use your `profiler`-enabled version of `rustc`, set the path explicitly via the `RUSTC` environment variable. Here is another example, using a `stage1` build of `rustc` to compile an `example` binary (from the [`json5format`](https://crates.io/crates/json5format) crate):
74+
75+
```shell
76+
$ RUSTC=$HOME/rust/build/x86_64-unknown-linux-gnu/stage1/bin/rustc \
77+
RUSTFLAGS="-Zinstrument-coverage" \
78+
cargo build --example formatjson5
79+
```
80+
81+
## Running the instrumented binary to generate raw coverage profiling data
82+
83+
In the previous example, `cargo` generated the coverage-instrumented binary `formatjson5`:
84+
85+
```shell
86+
$ echo "{some: 'thing'}" | target/debug/examples/formatjson5 -
87+
```
88+
```json5
89+
{
90+
some: 'thing',
91+
}
92+
```
93+
94+
After running this program, a new file, `default.profraw`, should be in the current working directory. It's often preferable to set a specific file name or path. You can change the output file using the environment variable `LLVM_PROFILE_FILE`:
95+
96+
97+
```shell
98+
$ echo "{some: 'thing'}" \
99+
| LLVM_PROFILE_FILE="formatjson5.profraw" target/debug/examples/formatjson5 -
100+
...
101+
$ ls formatjson5.profraw
102+
formatjson5.profraw
103+
```
104+
105+
If `LLVM_PROFILE_FILE` contains a path to a non-existent directory, the missing directory structure will be created. Additionally, the following special pattern strings are rewritten:
106+
107+
* `%p` - The process ID.
108+
* `%h` - The hostname of the machine running the program.
109+
* `%t` - The value of the TMPDIR environment variable.
110+
* `%Nm` - the instrumented binary’s signature: The runtime creates a pool of N raw profiles, used for on-line profile merging. The runtime takes care of selecting a raw profile from the pool, locking it, and updating it before the program exits. `N` must be between `1` and `9`, and defaults to `1` if omitted (with simply `%m`).
111+
* `%c` - Does not add anything to the filename, but enables a mode (on some platforms, including Darwin) in which profile counter updates are continuously synced to a file. This means that if the instrumented program crashes, or is killed by a signal, perfect coverage information can still be recovered.
112+
113+
## Creating coverage reports
114+
115+
LLVM's tools to process coverage data and coverage maps have some version dependencies. If you encounter a version mismatch, try updating your LLVM tools.
116+
117+
If you are building the Rust compiler from source, you can optionally use the bundled LLVM tools, built from source. Those tool binaries can typically be found in your build platform directory at something like: `rust/build/x86_64-unknown-linux-gnu/llvm/bin/llvm-*`. (Look for `llvm-profdata` and `llvm-cov`.)
118+
119+
Raw profiles have to be indexed before they can be used to generate coverage reports. This is done using [`llvm-profdata merge`] (which can combine multiple raw profiles and index them at the same time):
120+
121+
```shell
122+
$ llvm-profdata merge -sparse formatjson5.profraw -o formatjson5.profdata
123+
```
124+
125+
Finally, the `.profdata` file is used, in combination with the coverage map (from the program binary) to generate coverage reports using [`llvm-cov report`]--for a coverage summaries--and [`llvm-cov show`]--to see detailed coverage of lines and regions (character ranges), overlaid on the original source code.
126+
127+
These commands have several display and filtering options. For example:
128+
129+
```shell
130+
$ llvm-cov show -Xdemangler=rustfilt target/debug/examples/formatjson5 \
131+
-instr-profile=formatjson5.profdata \
132+
-show-line-counts-or-regions \
133+
-show-instantiations \
134+
-name=add_quoted_string
135+
```
136+
137+
<img alt="Screenshot of sample `llvm-cov show` result, for function add_quoted_string" src="img/llvm-cov-show-01.png" class="center"/>
138+
<br/>
139+
<br/>
140+
141+
Some of the more notable options in this example include:
142+
143+
* `--Xdemangler=rustfilt` - the command name or path used to demangle Rust symbols (`rustfilt` in the example, but this could also be a path to the `rust-demangler` tool)
144+
* `target/debug/examples/formatjson5` - the instrumented binary (from which to extract the coverage map)
145+
* `--instr-profile=<path-to-file>.profdata` - the location of the `.profdata` file created by `llvm-profdata merge` (from the `.profraw` file generated by the instrumented binary)
146+
* `--name=<exact-function-name>` - to show coverage for a specific function (or, consider using another filter option, such as `--name-regex=<pattern>`)
147+
148+
## Interpreting reports
149+
150+
There are four statistics tracked in a coverage summary:
151+
152+
* Function coverage is the percentage of functions that have been executed at least once. A function is considered to be executed if any of its instantiations are executed.
153+
* Instantiation coverage is the percentage of function instantiations that have been executed at least once. Generic functions and functions generated from macros are two kinds of functions that may have multiple instantiations.
154+
* Line coverage is the percentage of code lines that have been executed at least once. Only executable lines within function bodies are considered to be code lines.
155+
* Region coverage is the percentage of code regions that have been executed at least once. A code region may span multiple lines: for example, in a large function body with no control flow. In other cases, a single line can contain multiple code regions: `return x || (y && z)` has countable code regions for `x` (which may resolve the expression, if `x` is `true`), `|| (y && z)` (executed only if `x` was `false`), and `return` (executed in either situation).
156+
157+
Of these four statistics, function coverage is usually the least granular while region coverage is the most granular. The project-wide totals for each statistic are listed in the summary.
158+
159+
## Other references
160+
161+
Rust's implementation and workflow for source-based code coverage is based on the same library and tools used to implement [source-based code coverage in Clang](https://clang.llvm.org/docs/SourceBasedCodeCoverage.html). (This document is partially based on the Clang guide.)
162+
163+
[#34701]: https://github.com/rust-lang/rust/issues/34701
164+
[`llvm.instrprof.increment`]: https://llvm.org/docs/LangRef.html#llvm-instrprof-increment-intrinsic
165+
[LLVM Code Coverage Mapping Format]: https://llvm.org/docs/CoverageMappingFormat.html
166+
[rustc-dev-guide-how-to-build-and-run]: https://rustc-dev-guide.rust-lang.org/building/how-to-build-and-run.html
167+
[`llvm-profdata merge`]: https://llvm.org/docs/CommandGuide/llvm-profdata.html#profdata-merge
168+
[`llvm-cov report`]: https://llvm.org/docs/CommandGuide/llvm-cov.html#llvm-cov-report
169+
[`llvm-cov show`]: https://llvm.org/docs/CommandGuide/llvm-cov.html#llvm-cov-show
Lines changed: 70 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
use std::cmp;
2-
use std::string::String;
32

4-
use crate::clean::{self, DocFragment, Item};
3+
use crate::clean::{self, DocFragment, DocFragmentKind, Item};
54
use crate::core::DocContext;
65
use crate::fold::{self, DocFolder};
76
use crate::passes::Pass;
@@ -35,65 +34,81 @@ impl clean::Attributes {
3534
}
3635

3736
fn unindent_fragments(docs: &mut Vec<DocFragment>) {
38-
for fragment in docs {
39-
fragment.doc = unindent(&fragment.doc);
40-
}
41-
}
42-
43-
fn unindent(s: &str) -> String {
44-
let lines = s.lines().collect::<Vec<&str>>();
45-
let mut saw_first_line = false;
46-
let mut saw_second_line = false;
47-
let min_indent = lines.iter().fold(usize::MAX, |min_indent, line| {
48-
// After we see the first non-whitespace line, look at
49-
// the line we have. If it is not whitespace, and therefore
50-
// part of the first paragraph, then ignore the indentation
51-
// level of the first line
52-
let ignore_previous_indents =
53-
saw_first_line && !saw_second_line && !line.chars().all(|c| c.is_whitespace());
37+
// `add` is used in case the most common sugared doc syntax is used ("/// "). The other
38+
// fragments kind's lines are never starting with a whitespace unless they are using some
39+
// markdown formatting requiring it. Therefore, if the doc block have a mix between the two,
40+
// we need to take into account the fact that the minimum indent minus one (to take this
41+
// whitespace into account).
42+
//
43+
// For example:
44+
//
45+
// /// hello!
46+
// #[doc = "another"]
47+
//
48+
// In this case, you want "hello! another" and not "hello! another".
49+
let add = if docs.windows(2).any(|arr| arr[0].kind != arr[1].kind)
50+
&& docs.iter().any(|d| d.kind == DocFragmentKind::SugaredDoc)
51+
{
52+
// In case we have a mix of sugared doc comments and "raw" ones, we want the sugared one to
53+
// "decide" how much the minimum indent will be.
54+
1
55+
} else {
56+
0
57+
};
5458

55-
let min_indent = if ignore_previous_indents { usize::MAX } else { min_indent };
59+
// `min_indent` is used to know how much whitespaces from the start of each lines must be
60+
// removed. Example:
61+
//
62+
// /// hello!
63+
// #[doc = "another"]
64+
//
65+
// In here, the `min_indent` is 1 (because non-sugared fragment are always counted with minimum
66+
// 1 whitespace), meaning that "hello!" will be considered a codeblock because it starts with 4
67+
// (5 - 1) whitespaces.
68+
let min_indent = match docs
69+
.iter()
70+
.map(|fragment| {
71+
fragment.doc.lines().fold(usize::MAX, |min_indent, line| {
72+
if line.chars().all(|c| c.is_whitespace()) {
73+
min_indent
74+
} else {
75+
// Compare against either space or tab, ignoring whether they are
76+
// mixed or not.
77+
let whitespace = line.chars().take_while(|c| *c == ' ' || *c == '\t').count();
78+
cmp::min(min_indent, whitespace)
79+
+ if fragment.kind == DocFragmentKind::SugaredDoc { 0 } else { add }
80+
}
81+
})
82+
})
83+
.min()
84+
{
85+
Some(x) => x,
86+
None => return,
87+
};
5688

57-
if saw_first_line {
58-
saw_second_line = true;
89+
for fragment in docs {
90+
if fragment.doc.lines().count() == 0 {
91+
continue;
5992
}
6093

61-
if line.chars().all(|c| c.is_whitespace()) {
62-
min_indent
94+
let min_indent = if fragment.kind != DocFragmentKind::SugaredDoc && min_indent > 0 {
95+
min_indent - add
6396
} else {
64-
saw_first_line = true;
65-
let mut whitespace = 0;
66-
line.chars().all(|char| {
67-
// Compare against either space or tab, ignoring whether they
68-
// are mixed or not
69-
if char == ' ' || char == '\t' {
70-
whitespace += 1;
71-
true
97+
min_indent
98+
};
99+
100+
fragment.doc = fragment
101+
.doc
102+
.lines()
103+
.map(|line| {
104+
if line.chars().all(|c| c.is_whitespace()) {
105+
line.to_string()
72106
} else {
73-
false
107+
assert!(line.len() >= min_indent);
108+
line[min_indent..].to_string()
74109
}
75-
});
76-
cmp::min(min_indent, whitespace)
77-
}
78-
});
79-
80-
if !lines.is_empty() {
81-
let mut unindented = vec![lines[0].trim_start().to_string()];
82-
unindented.extend_from_slice(
83-
&lines[1..]
84-
.iter()
85-
.map(|&line| {
86-
if line.chars().all(|c| c.is_whitespace()) {
87-
line.to_string()
88-
} else {
89-
assert!(line.len() >= min_indent);
90-
line[min_indent..].to_string()
91-
}
92-
})
93-
.collect::<Vec<_>>(),
94-
);
95-
unindented.join("\n")
96-
} else {
97-
s.to_string()
110+
})
111+
.collect::<Vec<_>>()
112+
.join("\n");
98113
}
99114
}
Lines changed: 28 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,72 +1,63 @@
11
use super::*;
2+
use rustc_span::source_map::DUMMY_SP;
3+
4+
fn create_doc_fragment(s: &str) -> Vec<DocFragment> {
5+
vec![DocFragment {
6+
line: 0,
7+
span: DUMMY_SP,
8+
parent_module: None,
9+
doc: s.to_string(),
10+
kind: DocFragmentKind::SugaredDoc,
11+
}]
12+
}
13+
14+
#[track_caller]
15+
fn run_test(input: &str, expected: &str) {
16+
let mut s = create_doc_fragment(input);
17+
unindent_fragments(&mut s);
18+
assert_eq!(s[0].doc, expected);
19+
}
220

321
#[test]
422
fn should_unindent() {
5-
let s = " line1\n line2".to_string();
6-
let r = unindent(&s);
7-
assert_eq!(r, "line1\nline2");
23+
run_test(" line1\n line2", "line1\nline2");
824
}
925

1026
#[test]
1127
fn should_unindent_multiple_paragraphs() {
12-
let s = " line1\n\n line2".to_string();
13-
let r = unindent(&s);
14-
assert_eq!(r, "line1\n\nline2");
28+
run_test(" line1\n\n line2", "line1\n\nline2");
1529
}
1630

1731
#[test]
1832
fn should_leave_multiple_indent_levels() {
1933
// Line 2 is indented another level beyond the
2034
// base indentation and should be preserved
21-
let s = " line1\n\n line2".to_string();
22-
let r = unindent(&s);
23-
assert_eq!(r, "line1\n\n line2");
35+
run_test(" line1\n\n line2", "line1\n\n line2");
2436
}
2537

2638
#[test]
2739
fn should_ignore_first_line_indent() {
28-
// The first line of the first paragraph may not be indented as
29-
// far due to the way the doc string was written:
30-
//
31-
// #[doc = "Start way over here
32-
// and continue here"]
33-
let s = "line1\n line2".to_string();
34-
let r = unindent(&s);
35-
assert_eq!(r, "line1\nline2");
40+
run_test("line1\n line2", "line1\n line2");
3641
}
3742

3843
#[test]
3944
fn should_not_ignore_first_line_indent_in_a_single_line_para() {
40-
let s = "line1\n\n line2".to_string();
41-
let r = unindent(&s);
42-
assert_eq!(r, "line1\n\n line2");
45+
run_test("line1\n\n line2", "line1\n\n line2");
4346
}
4447

4548
#[test]
4649
fn should_unindent_tabs() {
47-
let s = "\tline1\n\tline2".to_string();
48-
let r = unindent(&s);
49-
assert_eq!(r, "line1\nline2");
50+
run_test("\tline1\n\tline2", "line1\nline2");
5051
}
5152

5253
#[test]
5354
fn should_trim_mixed_indentation() {
54-
let s = "\t line1\n\t line2".to_string();
55-
let r = unindent(&s);
56-
assert_eq!(r, "line1\nline2");
57-
58-
let s = " \tline1\n \tline2".to_string();
59-
let r = unindent(&s);
60-
assert_eq!(r, "line1\nline2");
55+
run_test("\t line1\n\t line2", "line1\nline2");
56+
run_test(" \tline1\n \tline2", "line1\nline2");
6157
}
6258

6359
#[test]
6460
fn should_not_trim() {
65-
let s = "\t line1 \n\t line2".to_string();
66-
let r = unindent(&s);
67-
assert_eq!(r, "line1 \nline2");
68-
69-
let s = " \tline1 \n \tline2".to_string();
70-
let r = unindent(&s);
71-
assert_eq!(r, "line1 \nline2");
61+
run_test("\t line1 \n\t line2", "line1 \nline2");
62+
run_test(" \tline1 \n \tline2", "line1 \nline2");
7263
}

‎src/test/rustdoc/unindent.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Just some text.

‎src/test/rustdoc/unindent.rs

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
#![feature(external_doc)]
2+
3+
#![crate_name = "foo"]
4+
5+
// @has foo/struct.Example.html
6+
// @matches - '//pre[@class="rust rust-example-rendered"]' \
7+
// '(?m)let example = Example::new\(\)\n \.first\(\)\n \.second\(\)\n \.build\(\);\Z'
8+
/// ```rust
9+
/// let example = Example::new()
10+
/// .first()
11+
#[cfg_attr(not(feature = "one"), doc = " .second()")]
12+
/// .build();
13+
/// ```
14+
pub struct Example;
15+
16+
// @has foo/struct.F.html
17+
// @matches - '//pre[@class="rust rust-example-rendered"]' \
18+
// '(?m)let example = Example::new\(\)\n \.first\(\)\n \.another\(\)\n \.build\(\);\Z'
19+
///```rust
20+
///let example = Example::new()
21+
/// .first()
22+
#[cfg_attr(not(feature = "one"), doc = " .another()")]
23+
/// .build();
24+
/// ```
25+
pub struct F;
26+
27+
// @has foo/struct.G.html
28+
// @matches - '//pre[@class="rust rust-example-rendered"]' \
29+
// '(?m)let example = Example::new\(\)\n\.first\(\)\n \.another\(\)\n\.build\(\);\Z'
30+
///```rust
31+
///let example = Example::new()
32+
///.first()
33+
#[cfg_attr(not(feature = "one"), doc = " .another()")]
34+
///.build();
35+
///```
36+
pub struct G;
37+
38+
// @has foo/struct.H.html
39+
// @has - '//div[@class="docblock"]/p' 'no whitespace lol'
40+
///no whitespace
41+
#[doc = " lol"]
42+
pub struct H;
43+
44+
// @has foo/struct.I.html
45+
// @matches - '//pre[@class="rust rust-example-rendered"]' '(?m)4 whitespaces!\Z'
46+
/// 4 whitespaces!
47+
#[doc = "something"]
48+
pub struct I;
49+
50+
// @has foo/struct.J.html
51+
// @matches - '//div[@class="docblock"]/p' '(?m)a\nno whitespace\nJust some text.\Z'
52+
///a
53+
///no whitespace
54+
#[doc(include = "unindent.md")]
55+
pub struct J;
56+
57+
// @has foo/struct.K.html
58+
// @matches - '//pre[@class="rust rust-example-rendered"]' '(?m)4 whitespaces!\Z'
59+
///a
60+
///
61+
/// 4 whitespaces!
62+
///
63+
#[doc(include = "unindent.md")]
64+
pub struct K;
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// compile-flags: -D warnings --cap-lints allow
2+
// check-pass
3+
4+
// Regression test for issue #78660
5+
// Tests that we don't ICE when a future-incompat-report lint has
6+
// has a command-line source, but is capped to allow
7+
8+
fn main() {
9+
["hi"].into_iter();
10+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
Future incompatibility report: Future breakage date: None, diagnostic:
2+
warning: this method call currently resolves to `<&[T; N] as IntoIterator>::into_iter` (due to autoref coercions), but that might change in the future when `IntoIterator` impls for arrays are added.
3+
--> $DIR/issue-78660-cap-lints-future-compat.rs:9:12
4+
|
5+
LL | ["hi"].into_iter();
6+
| ^^^^^^^^^ help: use `.iter()` instead of `.into_iter()` to avoid ambiguity: `iter`
7+
|
8+
= note: `-D array-into-iter` implied by `-D warnings`
9+
= warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
10+
= note: for more information, see issue #66145 <https://github.com/rust-lang/rust/issues/66145>
11+
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
macro_rules! empty {
2+
() => { }
3+
}
4+
5+
fn foo() -> bool { //~ ERROR mismatched
6+
{ true } //~ ERROR mismatched
7+
empty!();
8+
}
9+
10+
fn main() {}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
error[E0308]: mismatched types
2+
--> $DIR/empty-trailing-stmt.rs:6:7
3+
|
4+
LL | { true }
5+
| ^^^^ expected `()`, found `bool`
6+
7+
error[E0308]: mismatched types
8+
--> $DIR/empty-trailing-stmt.rs:5:13
9+
|
10+
LL | fn foo() -> bool {
11+
| --- ^^^^ expected `bool`, found `()`
12+
| |
13+
| implicitly returns `()` as its body has no tail or `return` expression
14+
15+
error: aborting due to 2 previous errors
16+
17+
For more information about this error, try `rustc --explain E0308`.

‎src/test/ui/meta/auxiliary/env.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
// Check that aux builds can also use rustc-env, but environment is configured
2+
// separately from the main test case.
3+
//
4+
// rustc-env:COMPILETEST_BAR=bar
5+
6+
pub fn test() {
7+
assert_eq!(option_env!("COMPILETEST_FOO"), None);
8+
assert_eq!(env!("COMPILETEST_BAR"), "bar");
9+
}

‎src/test/ui/meta-expected-error-correct-rev.a.stderr renamed to ‎src/test/ui/meta/expected-error-correct-rev.a.stderr

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
error[E0308]: mismatched types
2-
--> $DIR/meta-expected-error-correct-rev.rs:7:18
2+
--> $DIR/expected-error-correct-rev.rs:7:18
33
|
44
LL | let x: u32 = 22_usize;
55
| --- ^^^^^^^^ expected `u32`, found `usize`

‎src/test/ui/meta-expected-error-correct-rev.rs renamed to ‎src/test/ui/meta/expected-error-correct-rev.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// revisions: a
22

3-
// Counterpart to `meta-expected-error-wrong-rev.rs`
3+
// Counterpart to `expected-error-wrong-rev.rs`
44

55
#[cfg(a)]
66
fn foo() {
File renamed without changes.

‎src/test/ui/meta-revision-ok.rs renamed to ‎src/test/ui/meta/revision-ok.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// Meta test for compiletest: check that when we give the right error
2-
// patterns, the test passes. See all `meta-revision-bad.rs`.
2+
// patterns, the test passes. See all `revision-bad.rs`.
33

44
// run-fail
55
// revisions: foo bar

‎src/test/ui/meta/rustc-env.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// Compiletest meta test checking that rustc-env and unset-rustc-env directives
2+
// can be used to configure environment for rustc.
3+
//
4+
// run-pass
5+
// aux-build:env.rs
6+
// rustc-env:COMPILETEST_FOO=foo
7+
//
8+
// An environment variable that is likely to be set, but should be safe to unset.
9+
// unset-rustc-env:PWD
10+
11+
extern crate env;
12+
13+
fn main() {
14+
assert_eq!(env!("COMPILETEST_FOO"), "foo");
15+
assert_eq!(option_env!("COMPILETEST_BAR"), None);
16+
assert_eq!(option_env!("PWD"), None);
17+
env::test();
18+
}

‎src/test/ui/proc-macro/meta-macro-hygiene.stdout

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ macro_rules! produce_it
4040
}
4141
}
4242

43-
fn main /* 0#0 */() { }
43+
fn main /* 0#0 */() { ; }
4444

4545
/*
4646
Expansions:

0 commit comments

Comments
 (0)
Please sign in to comment.