Skip to content
This repository was archived by the owner on May 28, 2025. It is now read-only.

Commit 75915ad

Browse files
committed
Lint against named asm labels
1 parent 7f3dc04 commit 75915ad

File tree

6 files changed

+506
-6
lines changed

6 files changed

+506
-6
lines changed

compiler/rustc_builtin_macros/src/asm.rs

Lines changed: 72 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ 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};
1313
use rustc_span::{InnerSpan, Span};
@@ -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,70 @@ fn expand_preparsed_asm(ecx: &mut ExtCtxt<'_>, args: AsmArgs) -> Option<ast::Inl
469473
}
470474
}
471475

476+
if is_local_asm {
477+
let find_label_span = |needle: &str| -> Option<Span> {
478+
if let Some(snippet) = &template_snippet {
479+
if let Some(pos) = snippet.find(needle) {
480+
let end = pos
481+
+ &snippet[pos..]
482+
.find(|c| c == ':')
483+
.unwrap_or(snippet[pos..].len() - 1);
484+
let inner = InnerSpan::new(pos, end);
485+
return Some(template_sp.from_inner(inner));
486+
}
487+
}
488+
489+
None
490+
};
491+
492+
let mut found_labels = Vec::new();
493+
494+
// A semicolon might not actually be specified as a separator for all targets, but it seems like LLVM accepts it always
495+
let statements = template_str.split(|c| matches!(c, '\n' | ';'));
496+
for statement in statements {
497+
let mut start_idx = 0;
498+
for (idx, _) in statement.match_indices(':') {
499+
let possible_label = statement[start_idx..idx].trim();
500+
let mut chars = possible_label.chars();
501+
if let Some(c) = chars.next() {
502+
// A label starts with an alphabetic character and continues with alphanumeric characters
503+
if c.is_alphabetic() {
504+
if chars.all(char::is_alphanumeric) {
505+
found_labels.push(possible_label);
506+
}
507+
}
508+
}
509+
510+
start_idx = idx + 1;
511+
}
512+
}
513+
514+
if found_labels.len() > 0 {
515+
let spans =
516+
found_labels.into_iter().filter_map(find_label_span).collect::<Vec<Span>>();
517+
if spans.len() > 0 {
518+
for span in spans.into_iter() {
519+
ecx.parse_sess().buffer_lint_with_diagnostic(
520+
lint::builtin::NAMED_ASM_LABELS,
521+
span,
522+
ecx.current_expansion.lint_node_id,
523+
"do not use named labels in inline assembly",
524+
BuiltinLintDiagnostics::NamedAsmLabel("Only GAS local labels of the form `N:` where N is a number may be used in inline asm".to_string()),
525+
);
526+
}
527+
} else {
528+
// If there were labels but we couldn't find a span, combine the warnings and use the template span
529+
ecx.parse_sess().buffer_lint_with_diagnostic(
530+
lint::builtin::NAMED_ASM_LABELS,
531+
template_span,
532+
ecx.current_expansion.lint_node_id,
533+
"do not use named labels in inline assembly",
534+
BuiltinLintDiagnostics::NamedAsmLabel("Only GAS local labels of the form `N:` where N is a number may be used in inline asm".to_string()),
535+
);
536+
}
537+
}
538+
}
539+
472540
// Don't treat raw asm as a format string.
473541
if args.options.contains(ast::InlineAsmOptions::RAW) {
474542
template.push(ast::InlineAsmTemplatePiece::String(template_str.to_string()));
@@ -670,7 +738,7 @@ pub fn expand_asm<'cx>(
670738
) -> Box<dyn base::MacResult + 'cx> {
671739
match parse_args(ecx, sp, tts, false) {
672740
Ok(args) => {
673-
let expr = if let Some(inline_asm) = expand_preparsed_asm(ecx, args) {
741+
let expr = if let Some(inline_asm) = expand_preparsed_asm(ecx, args, true) {
674742
P(ast::Expr {
675743
id: ast::DUMMY_NODE_ID,
676744
kind: ast::ExprKind::InlineAsm(P(inline_asm)),
@@ -697,7 +765,7 @@ pub fn expand_global_asm<'cx>(
697765
) -> Box<dyn base::MacResult + 'cx> {
698766
match parse_args(ecx, sp, tts, true) {
699767
Ok(args) => {
700-
if let Some(inline_asm) = expand_preparsed_asm(ecx, args) {
768+
if let Some(inline_asm) = expand_preparsed_asm(ecx, args, false) {
701769
MacEager::items(smallvec![P(ast::Item {
702770
ident: Ident::invalid(),
703771
attrs: Vec::new(),

compiler/rustc_lint/src/context.rs

Lines changed: 4 additions & 0 deletions
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));

compiler/rustc_lint_defs/src/builtin.rs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2470,6 +2470,38 @@ declare_lint! {
24702470
"incorrect use of inline assembly",
24712471
}
24722472

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

compiler/rustc_lint_defs/src/lib.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -274,15 +274,15 @@ impl<HCX> ToStableHashKey<HCX> for LintId {
274274
}
275275

276276
// Duplicated from rustc_session::config::ExternDepSpec to avoid cyclic dependency
277-
#[derive(PartialEq, Debug)]
277+
#[derive(PartialEq)]
278278
pub enum ExternDepSpec {
279279
Json(Json),
280280
Raw(String),
281281
}
282282

283283
// This could be a closure, but then implementing derive trait
284284
// becomes hacky (and it gets allocated).
285-
#[derive(PartialEq, Debug)]
285+
#[derive(PartialEq)]
286286
pub enum BuiltinLintDiagnostics {
287287
Normal,
288288
BareTraitObject(Span, /* is_global */ bool),
@@ -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

src/test/ui/asm/named_asm_labels.rs

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
#![feature(asm, global_asm)]
2+
3+
fn main() {
4+
unsafe {
5+
// Basic usage
6+
asm!("bar: nop"); //~ ERROR do not use named labels
7+
8+
// No following asm
9+
asm!("abcd:"); //~ ERROR do not use named labels
10+
11+
// Multiple labels on one line
12+
asm!("foo: bar1: nop");
13+
//~^ ERROR do not use named labels
14+
//~| ERROR do not use named labels
15+
16+
// Multiple lines
17+
asm!("foo1: nop", "nop"); //~ ERROR do not use named labels
18+
asm!("foo2: foo3: nop", "nop");
19+
//~^ ERROR do not use named labels
20+
//~| ERROR do not use named labels
21+
asm!("nop", "foo4: nop"); //~ ERROR do not use named labels
22+
asm!("foo5: nop", "foo6: nop");
23+
//~^ ERROR do not use named labels
24+
//~| ERROR do not use named labels
25+
26+
// Statement separator
27+
asm!("foo7: nop; foo8: nop");
28+
//~^ ERROR do not use named labels
29+
//~| ERROR do not use named labels
30+
asm!("foo9: nop; nop"); //~ ERROR do not use named labels
31+
asm!("nop; foo10: nop"); //~ ERROR do not use named labels
32+
33+
// Escaped newline
34+
asm!("bar2: nop\n bar3: nop");
35+
//~^ ERROR do not use named labels
36+
//~| ERROR do not use named labels
37+
asm!("bar4: nop\n nop"); //~ ERROR do not use named labels
38+
asm!("nop\n bar5: nop"); //~ ERROR do not use named labels
39+
asm!("nop\n bar6: bar7: nop");
40+
//~^ ERROR do not use named labels
41+
//~| ERROR do not use named labels
42+
43+
// Raw strings
44+
asm!(
45+
r"
46+
blah2: nop
47+
blah3: nop
48+
"
49+
);
50+
//~^^^^ ERROR do not use named labels
51+
//~^^^^ ERROR do not use named labels
52+
asm!(
53+
r###"
54+
nop
55+
nop ; blah4: nop
56+
"###
57+
);
58+
//~^^^ ERROR do not use named labels
59+
60+
// Non-labels
61+
// should not trigger lint, but may be invalid asm
62+
asm!("ab cd: nop");
63+
64+
// Only `blah:` should trigger
65+
asm!("1bar: blah: nop"); //~ ERROR do not use named labels
66+
67+
// Only `blah1:` should trigger
68+
asm!("blah1: 2bar: nop"); //~ ERROR do not use named labels
69+
70+
// Duplicate labels
71+
asm!("def: def: nop"); //~ ERROR do not use named labels
72+
asm!("def: nop\ndef: nop"); //~ ERROR do not use named labels
73+
asm!("def: nop; def: nop"); //~ ERROR do not use named labels
74+
75+
// Trying to break parsing
76+
asm!(":");
77+
asm!("\n:\n");
78+
asm!("::::");
79+
80+
// 0x3A is a ':'
81+
asm!("fooo\u{003A} nop"); //~ ERROR do not use named labels
82+
asm!("foooo\x3A nop"); //~ ERROR do not use named labels
83+
84+
// 0x0A is a newline
85+
asm!("fooooo:\u{000A} nop"); //~ ERROR do not use named labels
86+
asm!("foooooo:\x0A nop"); //~ ERROR do not use named labels
87+
88+
// Intentionally breaking span finding
89+
// equivalent to "ABC: nop"
90+
asm!("\x41\x42\x43\x3A\x20\x6E\x6F\x70"); //~ ERROR do not use named labels
91+
}
92+
}
93+
94+
// Don't trigger on global asm
95+
global_asm!("aaaaaaaa: nop");

0 commit comments

Comments
 (0)