Skip to content

Commit e55c13e

Browse files
committed
Auto merge of #87324 - asquared31415:named-asm-labels, r=Amanieu
Lint against named asm labels This adds a deny-by-default lint to prevent the use of named labels in inline `asm!`. Without a solution to #81088 about whether the compiler should rewrite named labels or a special syntax for labels, a lint against them should prevent users from writing assembly that could break for internal compiler reasons, such as inlining or anything else that could change the number of actual inline assembly blocks emitted. This does **not** resolve the issue with rewriting labels, that still needs a decision if the compiler should do any more work to try to make them work.
2 parents a59e885 + 51e414f commit e55c13e

File tree

8 files changed

+520
-5
lines changed

8 files changed

+520
-5
lines changed

Diff for: compiler/rustc_builtin_macros/src/asm.rs

+75-5
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,10 @@ use rustc_errors::{Applicability, DiagnosticBuilder};
77
use rustc_expand::base::{self, *};
88
use rustc_parse::parser::Parser;
99
use rustc_parse_format as parse;
10-
use rustc_session::lint;
10+
use rustc_session::lint::{self, BuiltinLintDiagnostics};
1111
use rustc_span::symbol::Ident;
1212
use rustc_span::symbol::{kw, sym, Symbol};
13-
use rustc_span::{InnerSpan, Span};
13+
use rustc_span::{InnerSpan, MultiSpan, Span};
1414
use rustc_target::asm::InlineAsmArch;
1515
use smallvec::smallvec;
1616

@@ -397,7 +397,11 @@ fn parse_reg<'a>(
397397
Ok(result)
398398
}
399399

400-
fn expand_preparsed_asm(ecx: &mut ExtCtxt<'_>, args: AsmArgs) -> Option<ast::InlineAsm> {
400+
fn expand_preparsed_asm(
401+
ecx: &mut ExtCtxt<'_>,
402+
args: AsmArgs,
403+
is_local_asm: bool,
404+
) -> Option<ast::InlineAsm> {
401405
let mut template = vec![];
402406
// Register operands are implicitly used since they are not allowed to be
403407
// referenced in the template string.
@@ -469,6 +473,72 @@ fn expand_preparsed_asm(ecx: &mut ExtCtxt<'_>, args: AsmArgs) -> Option<ast::Inl
469473
}
470474
}
471475

476+
// Lint against the use of named labels in inline `asm!` but not `global_asm!`
477+
if is_local_asm {
478+
let find_label_span = |needle: &str| -> Option<Span> {
479+
if let Some(snippet) = &template_snippet {
480+
if let Some(pos) = snippet.find(needle) {
481+
let end = pos
482+
+ &snippet[pos..]
483+
.find(|c| c == ':')
484+
.unwrap_or(snippet[pos..].len() - 1);
485+
let inner = InnerSpan::new(pos, end);
486+
return Some(template_sp.from_inner(inner));
487+
}
488+
}
489+
490+
None
491+
};
492+
493+
let mut found_labels = Vec::new();
494+
495+
// A semicolon might not actually be specified as a separator for all targets, but it seems like LLVM accepts it always
496+
let statements = template_str.split(|c| matches!(c, '\n' | ';'));
497+
for statement in statements {
498+
// If there's a comment, trim it from the statement
499+
let statement = statement.find("//").map_or(statement, |idx| &statement[..idx]);
500+
let mut start_idx = 0;
501+
for (idx, _) in statement.match_indices(':') {
502+
let possible_label = statement[start_idx..idx].trim();
503+
let mut chars = possible_label.chars();
504+
if let Some(c) = chars.next() {
505+
// A label starts with an alphabetic character or . or _ and continues with alphanumeric characters, _, or $
506+
if (c.is_alphabetic() || matches!(c, '.' | '_'))
507+
&& chars.all(|c| c.is_alphanumeric() || matches!(c, '_' | '$'))
508+
{
509+
found_labels.push(possible_label);
510+
} else {
511+
// If we encounter a non-label, there cannot be any further labels, so stop checking
512+
break;
513+
}
514+
} else {
515+
// Empty string means a leading ':' in this section, which is not a label
516+
break;
517+
}
518+
519+
start_idx = idx + 1;
520+
}
521+
}
522+
523+
if found_labels.len() > 0 {
524+
let spans =
525+
found_labels.into_iter().filter_map(find_label_span).collect::<Vec<Span>>();
526+
// If there were labels but we couldn't find a span, combine the warnings and use the template span
527+
let target_spans: MultiSpan =
528+
if spans.len() > 0 { spans.into() } else { template_sp.into() };
529+
ecx.parse_sess().buffer_lint_with_diagnostic(
530+
lint::builtin::NAMED_ASM_LABELS,
531+
target_spans,
532+
ecx.current_expansion.lint_node_id,
533+
"avoid using named labels in inline assembly",
534+
BuiltinLintDiagnostics::NamedAsmLabel(
535+
"only local labels of the form `<number>:` should be used in inline asm"
536+
.to_string(),
537+
),
538+
);
539+
}
540+
}
541+
472542
// Don't treat raw asm as a format string.
473543
if args.options.contains(ast::InlineAsmOptions::RAW) {
474544
template.push(ast::InlineAsmTemplatePiece::String(template_str.to_string()));
@@ -670,7 +740,7 @@ pub fn expand_asm<'cx>(
670740
) -> Box<dyn base::MacResult + 'cx> {
671741
match parse_args(ecx, sp, tts, false) {
672742
Ok(args) => {
673-
let expr = if let Some(inline_asm) = expand_preparsed_asm(ecx, args) {
743+
let expr = if let Some(inline_asm) = expand_preparsed_asm(ecx, args, true) {
674744
P(ast::Expr {
675745
id: ast::DUMMY_NODE_ID,
676746
kind: ast::ExprKind::InlineAsm(P(inline_asm)),
@@ -697,7 +767,7 @@ pub fn expand_global_asm<'cx>(
697767
) -> Box<dyn base::MacResult + 'cx> {
698768
match parse_args(ecx, sp, tts, true) {
699769
Ok(args) => {
700-
if let Some(inline_asm) = expand_preparsed_asm(ecx, args) {
770+
if let Some(inline_asm) = expand_preparsed_asm(ecx, args, false) {
701771
MacEager::items(smallvec![P(ast::Item {
702772
ident: Ident::invalid(),
703773
attrs: Vec::new(),

Diff for: compiler/rustc_lint/src/context.rs

+4
Original file line numberDiff line numberDiff line change
@@ -758,6 +758,10 @@ pub trait LintContext: Sized {
758758
Applicability::MachineApplicable
759759
);
760760
}
761+
BuiltinLintDiagnostics::NamedAsmLabel(help) => {
762+
db.help(&help);
763+
db.note("see the asm section of the unstable book <https://doc.rust-lang.org/nightly/unstable-book/library-features/asm.html#labels> for more information");
764+
}
761765
}
762766
// Rewrap `db`, and pass control to the user.
763767
decorate(LintDiagnosticBuilder::new(db));

Diff for: compiler/rustc_lint_defs/src/builtin.rs

+33
Original file line numberDiff line numberDiff line change
@@ -2468,6 +2468,38 @@ declare_lint! {
24682468
"incorrect use of inline assembly",
24692469
}
24702470

2471+
declare_lint! {
2472+
/// The `named_asm_labels` lint detects the use of named labels in the
2473+
/// inline `asm!` macro.
2474+
///
2475+
/// ### Example
2476+
///
2477+
/// ```rust,compile_fail
2478+
/// fn main() {
2479+
/// unsafe {
2480+
/// asm!("foo: bar");
2481+
/// }
2482+
/// }
2483+
/// ```
2484+
///
2485+
/// {{produces}}
2486+
///
2487+
/// ### Explanation
2488+
///
2489+
/// LLVM is allowed to duplicate inline assembly blocks for any
2490+
/// reason, for example when it is in a function that gets inlined. Because
2491+
/// of this, GNU assembler [local labels] *must* be used instead of labels
2492+
/// with a name. Using named labels might cause assembler or linker errors.
2493+
///
2494+
/// See the [unstable book] for more details.
2495+
///
2496+
/// [local labels]: https://sourceware.org/binutils/docs/as/Symbol-Names.html#Local-Labels
2497+
/// [unstable book]: https://doc.rust-lang.org/nightly/unstable-book/library-features/asm.html#labels
2498+
pub NAMED_ASM_LABELS,
2499+
Deny,
2500+
"named labels in inline assembly",
2501+
}
2502+
24712503
declare_lint! {
24722504
/// The `unsafe_op_in_unsafe_fn` lint detects unsafe operations in unsafe
24732505
/// functions without an explicit unsafe block.
@@ -2988,6 +3020,7 @@ declare_lint_pass! {
29883020
INLINE_NO_SANITIZE,
29893021
BAD_ASM_STYLE,
29903022
ASM_SUB_REGISTER,
3023+
NAMED_ASM_LABELS,
29913024
UNSAFE_OP_IN_UNSAFE_FN,
29923025
INCOMPLETE_INCLUDE,
29933026
CENUM_IMPL_DROP_CAST,

Diff for: compiler/rustc_lint_defs/src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -305,6 +305,7 @@ pub enum BuiltinLintDiagnostics {
305305
ReservedPrefix(Span),
306306
TrailingMacro(bool, Ident),
307307
BreakWithLabelAndLoop(Span),
308+
NamedAsmLabel(String),
308309
}
309310

310311
/// Lints that are buffered up early on in the `Session` before the

Diff for: src/test/codegen/asm-sanitize-llvm.rs

+1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
#![no_core]
88
#![feature(no_core, lang_items, rustc_attrs)]
99
#![crate_type = "rlib"]
10+
#![allow(named_asm_labels)]
1011

1112
#[rustc_builtin_macro]
1213
macro_rules! asm {

Diff for: src/test/ui/asm/named-asm-labels.rs

+130
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
// only-x86_64
2+
3+
#![feature(asm, global_asm)]
4+
5+
#[no_mangle]
6+
pub static FOO: usize = 42;
7+
8+
fn main() {
9+
unsafe {
10+
// Basic usage
11+
asm!("bar: nop"); //~ ERROR avoid using named labels
12+
13+
// No following asm
14+
asm!("abcd:"); //~ ERROR avoid using named labels
15+
16+
// Multiple labels on one line
17+
asm!("foo: bar1: nop");
18+
//~^ ERROR avoid using named labels
19+
20+
// Multiple lines
21+
asm!("foo1: nop", "nop"); //~ ERROR avoid using named labels
22+
asm!("foo2: foo3: nop", "nop");
23+
//~^ ERROR avoid using named labels
24+
asm!("nop", "foo4: nop"); //~ ERROR avoid using named labels
25+
asm!("foo5: nop", "foo6: nop");
26+
//~^ ERROR avoid using named labels
27+
//~| ERROR avoid using named labels
28+
29+
// Statement separator
30+
asm!("foo7: nop; foo8: nop");
31+
//~^ ERROR avoid using named labels
32+
asm!("foo9: nop; nop"); //~ ERROR avoid using named labels
33+
asm!("nop; foo10: nop"); //~ ERROR avoid using named labels
34+
35+
// Escaped newline
36+
asm!("bar2: nop\n bar3: nop");
37+
//~^ ERROR avoid using named labels
38+
asm!("bar4: nop\n nop"); //~ ERROR avoid using named labels
39+
asm!("nop\n bar5: nop"); //~ ERROR avoid using named labels
40+
asm!("nop\n bar6: bar7: nop");
41+
//~^ ERROR avoid using named labels
42+
43+
// Raw strings
44+
asm!(
45+
r"
46+
blah2: nop
47+
blah3: nop
48+
"
49+
);
50+
//~^^^^ ERROR avoid using named labels
51+
52+
asm!(
53+
r###"
54+
nop
55+
nop ; blah4: nop
56+
"###
57+
);
58+
//~^^^ ERROR avoid using named labels
59+
60+
// Non-labels
61+
// should not trigger lint, but may be invalid asm
62+
asm!("ab cd: nop");
63+
64+
// `blah:` does not trigger because labels need to be at the start
65+
// of the statement, and there was already a non-label
66+
asm!("1bar: blah: nop");
67+
68+
// Only `blah1:` should trigger
69+
asm!("blah1: 2bar: nop"); //~ ERROR avoid using named labels
70+
71+
// Duplicate labels
72+
asm!("def: def: nop"); //~ ERROR avoid using named labels
73+
asm!("def: nop\ndef: nop"); //~ ERROR avoid using named labels
74+
asm!("def: nop; def: nop"); //~ ERROR avoid using named labels
75+
76+
// Trying to break parsing
77+
asm!(":");
78+
asm!("\n:\n");
79+
asm!("::::");
80+
81+
// 0x3A is a ':'
82+
asm!("fooo\u{003A} nop"); //~ ERROR avoid using named labels
83+
asm!("foooo\x3A nop"); //~ ERROR avoid using named labels
84+
85+
// 0x0A is a newline
86+
asm!("fooooo:\u{000A} nop"); //~ ERROR avoid using named labels
87+
asm!("foooooo:\x0A nop"); //~ ERROR avoid using named labels
88+
89+
// Intentionally breaking span finding
90+
// equivalent to "ABC: nop"
91+
asm!("\x41\x42\x43\x3A\x20\x6E\x6F\x70"); //~ ERROR avoid using named labels
92+
93+
// Non-label colons - should pass
94+
// (most of these are stolen from other places)
95+
asm!("{:l}", in(reg) 0i64);
96+
asm!("{:e}", in(reg) 0f32);
97+
asm!("mov rax, qword ptr fs:[0]");
98+
99+
// Comments
100+
asm!(
101+
r"
102+
ab: nop // ab: does foo
103+
// cd: nop
104+
"
105+
);
106+
//~^^^^ ERROR avoid using named labels
107+
108+
// Tests usage of colons in non-label positions
109+
asm!(":lo12:FOO"); // this is apparently valid aarch64
110+
// is there an example that is valid x86 for this test?
111+
asm!(":bbb nop");
112+
113+
// Test include_str in asm
114+
asm!(include_str!("named-asm-labels.s")); //~ ERROR avoid using named labels
115+
116+
// Test allowing or warning on the lint instead
117+
#[allow(named_asm_labels)]
118+
{
119+
asm!("allowed: nop"); // Should not emit anything
120+
}
121+
122+
#[warn(named_asm_labels)]
123+
{
124+
asm!("warned: nop"); //~ WARNING avoid using named labels
125+
}
126+
}
127+
}
128+
129+
// Don't trigger on global asm
130+
global_asm!("aaaaaaaa: nop");

Diff for: src/test/ui/asm/named-asm-labels.s

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
lab1: nop
2+
// do more things
3+
lab2: nop // does bar
4+
// a: b
5+
lab3: nop; lab4: nop

0 commit comments

Comments
 (0)