Differentiate between asm!(), global_asm!() and naked_asm!(), and make only asm!() unsafe

This commit is contained in:
Chayim Refael Friedman 2025-07-09 17:37:27 +03:00
parent edb804a100
commit bd8087e86e
16 changed files with 198 additions and 16 deletions

View file

@ -10,7 +10,7 @@ use tt::TextRange;
use crate::{
expr_store::lower::{ExprCollector, FxIndexSet},
hir::{AsmOperand, AsmOptions, Expr, ExprId, InlineAsm, InlineAsmRegOrRegClass},
hir::{AsmOperand, AsmOptions, Expr, ExprId, InlineAsm, InlineAsmKind, InlineAsmRegOrRegClass},
};
impl ExprCollector<'_> {
@ -269,8 +269,17 @@ impl ExprCollector<'_> {
}
})
};
let kind = if asm.global_asm_token().is_some() {
InlineAsmKind::GlobalAsm
} else if asm.naked_asm_token().is_some() {
InlineAsmKind::NakedAsm
} else {
InlineAsmKind::Asm
};
let idx = self.alloc_expr(
Expr::InlineAsm(InlineAsm { operands: operands.into_boxed_slice(), options }),
Expr::InlineAsm(InlineAsm { operands: operands.into_boxed_slice(), options, kind }),
syntax_ptr,
);
self.source_map

View file

@ -332,6 +332,17 @@ pub struct OffsetOf {
pub struct InlineAsm {
pub operands: Box<[(Option<Name>, AsmOperand)]>,
pub options: AsmOptions,
pub kind: InlineAsmKind,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum InlineAsmKind {
/// `asm!()`.
Asm,
/// `global_asm!()`.
GlobalAsm,
/// `naked_asm!()`.
NakedAsm,
}
#[derive(Clone, Copy, PartialEq, Eq, Hash)]

View file

@ -28,6 +28,20 @@ fn test_asm_expand() {
r#"
#[rustc_builtin_macro]
macro_rules! asm {() => {}}
#[rustc_builtin_macro]
macro_rules! global_asm {() => {}}
#[rustc_builtin_macro]
macro_rules! naked_asm {() => {}}
// FIXME: This creates an error
// global_asm! {
// ""
// }
#[unsafe(naked)]
extern "C" fn foo() {
naked_asm!("");
}
fn main() {
let i: u64 = 3;
@ -45,6 +59,20 @@ fn main() {
expect![[r##"
#[rustc_builtin_macro]
macro_rules! asm {() => {}}
#[rustc_builtin_macro]
macro_rules! global_asm {() => {}}
#[rustc_builtin_macro]
macro_rules! naked_asm {() => {}}
// FIXME: This creates an error
// global_asm! {
// ""
// }
#[unsafe(naked)]
extern "C" fn foo() {
builtin #naked_asm ("");
}
fn main() {
let i: u64 = 3;

View file

@ -125,8 +125,8 @@ register_builtin! {
(assert, Assert) => assert_expand,
(stringify, Stringify) => stringify_expand,
(asm, Asm) => asm_expand,
(global_asm, GlobalAsm) => asm_expand,
(naked_asm, NakedAsm) => asm_expand,
(global_asm, GlobalAsm) => global_asm_expand,
(naked_asm, NakedAsm) => naked_asm_expand,
(cfg, Cfg) => cfg_expand,
(core_panic, CorePanic) => panic_expand,
(std_panic, StdPanic) => panic_expand,
@ -325,6 +325,36 @@ fn asm_expand(
ExpandResult::ok(expanded)
}
fn global_asm_expand(
_db: &dyn ExpandDatabase,
_id: MacroCallId,
tt: &tt::TopSubtree,
span: Span,
) -> ExpandResult<tt::TopSubtree> {
let mut tt = tt.clone();
tt.top_subtree_delimiter_mut().kind = tt::DelimiterKind::Parenthesis;
let pound = mk_pound(span);
let expanded = quote! {span =>
builtin #pound global_asm #tt
};
ExpandResult::ok(expanded)
}
fn naked_asm_expand(
_db: &dyn ExpandDatabase,
_id: MacroCallId,
tt: &tt::TopSubtree,
span: Span,
) -> ExpandResult<tt::TopSubtree> {
let mut tt = tt.clone();
tt.top_subtree_delimiter_mut().kind = tt::DelimiterKind::Parenthesis;
let pound = mk_pound(span);
let expanded = quote! {span =>
builtin #pound naked_asm #tt
};
ExpandResult::ok(expanded)
}
fn cfg_expand(
db: &dyn ExpandDatabase,
id: MacroCallId,

View file

@ -7,7 +7,7 @@ use either::Either;
use hir_def::{
AdtId, DefWithBodyId, FieldId, FunctionId, VariantId,
expr_store::{Body, path::Path},
hir::{AsmOperand, Expr, ExprId, ExprOrPatId, Pat, PatId, Statement, UnaryOp},
hir::{AsmOperand, Expr, ExprId, ExprOrPatId, InlineAsmKind, Pat, PatId, Statement, UnaryOp},
resolver::{HasResolver, ResolveValueResult, Resolver, ValueNs},
signatures::StaticFlags,
type_ref::Rawness,
@ -315,7 +315,12 @@ impl<'db> UnsafeVisitor<'db> {
self.inside_assignment = old_inside_assignment;
}
Expr::InlineAsm(asm) => {
self.on_unsafe_op(current.into(), UnsafetyReason::InlineAsm);
if asm.kind == InlineAsmKind::Asm {
// `naked_asm!()` requires `unsafe` on the attribute (`#[unsafe(naked)]`),
// and `global_asm!()` doesn't require it at all.
self.on_unsafe_op(current.into(), UnsafetyReason::InlineAsm);
}
asm.operands.iter().for_each(|(_, op)| match op {
AsmOperand::In { expr, .. }
| AsmOperand::Out { expr: Some(expr), .. }

View file

@ -3220,7 +3220,8 @@ impl Macro {
}
}
pub fn is_asm_or_global_asm(&self, db: &dyn HirDatabase) -> bool {
/// Is this `asm!()`, or a variant of it (e.g. `global_asm!()`)?
pub fn is_asm_like(&self, db: &dyn HirDatabase) -> bool {
match self.id {
MacroId::Macro2Id(it) => {
matches!(it.lookup(db).expander, MacroExpander::BuiltIn(m) if m.is_asm())

View file

@ -1776,7 +1776,7 @@ impl<'db> SemanticsImpl<'db> {
pub fn is_unsafe_macro_call(&self, macro_call: &ast::MacroCall) -> bool {
let Some(mac) = self.resolve_macro_call(macro_call) else { return false };
if mac.is_asm_or_global_asm(self.db) {
if mac.is_asm_like(self.db) {
return true;
}

View file

@ -983,4 +983,19 @@ fn test() {
"#,
);
}
#[test]
fn naked_asm_is_safe() {
check_diagnostics(
r#"
#[rustc_builtin_macro]
macro_rules! naked_asm { () => {} }
#[unsafe(naked)]
extern "C" fn naked() {
naked_asm!("");
}
"#,
);
}
}

View file

@ -253,8 +253,7 @@ fn builtin_expr(p: &mut Parser<'_>) -> Option<CompletedMarker> {
let m = p.start();
p.bump_remap(T![builtin]);
p.bump(T![#]);
if p.at_contextual_kw(T![offset_of]) {
p.bump_remap(T![offset_of]);
if p.eat_contextual_kw(T![offset_of]) {
p.expect(T!['(']);
type_(p);
p.expect(T![,]);
@ -278,8 +277,7 @@ fn builtin_expr(p: &mut Parser<'_>) -> Option<CompletedMarker> {
p.expect(T![')']);
}
Some(m.complete(p, OFFSET_OF_EXPR))
} else if p.at_contextual_kw(T![format_args]) {
p.bump_remap(T![format_args]);
} else if p.eat_contextual_kw(T![format_args]) {
p.expect(T!['(']);
expr(p);
if p.eat(T![,]) {
@ -302,7 +300,16 @@ fn builtin_expr(p: &mut Parser<'_>) -> Option<CompletedMarker> {
}
p.expect(T![')']);
Some(m.complete(p, FORMAT_ARGS_EXPR))
} else if p.at_contextual_kw(T![asm]) {
} else if p.eat_contextual_kw(T![asm])
|| p.eat_contextual_kw(T![global_asm])
|| p.eat_contextual_kw(T![naked_asm])
{
// test asm_kinds
// fn foo() {
// builtin#asm("");
// builtin#global_asm("");
// builtin#naked_asm("");
// }
parse_asm_expr(p, m)
} else {
m.abandon(p);
@ -322,7 +329,6 @@ fn builtin_expr(p: &mut Parser<'_>) -> Option<CompletedMarker> {
// );
// }
fn parse_asm_expr(p: &mut Parser<'_>, m: Marker) -> Option<CompletedMarker> {
p.bump_remap(T![asm]);
p.expect(T!['(']);
if expr(p).is_none() {
p.err_and_bump("expected asm template");

File diff suppressed because one or more lines are too long

View file

@ -21,6 +21,8 @@ mod ok {
#[test]
fn asm_expr() { run_and_expect_no_errors("test_data/parser/inline/ok/asm_expr.rs"); }
#[test]
fn asm_kinds() { run_and_expect_no_errors("test_data/parser/inline/ok/asm_kinds.rs"); }
#[test]
fn asm_label() { run_and_expect_no_errors("test_data/parser/inline/ok/asm_label.rs"); }
#[test]
fn assoc_const_eq() {

View file

@ -0,0 +1,49 @@
SOURCE_FILE
FN
FN_KW "fn"
WHITESPACE " "
NAME
IDENT "foo"
PARAM_LIST
L_PAREN "("
R_PAREN ")"
WHITESPACE " "
BLOCK_EXPR
STMT_LIST
L_CURLY "{"
WHITESPACE "\n "
EXPR_STMT
ASM_EXPR
BUILTIN_KW "builtin"
POUND "#"
ASM_KW "asm"
L_PAREN "("
LITERAL
STRING "\"\""
R_PAREN ")"
SEMICOLON ";"
WHITESPACE "\n "
EXPR_STMT
ASM_EXPR
BUILTIN_KW "builtin"
POUND "#"
GLOBAL_ASM_KW "global_asm"
L_PAREN "("
LITERAL
STRING "\"\""
R_PAREN ")"
SEMICOLON ";"
WHITESPACE "\n "
EXPR_STMT
ASM_EXPR
BUILTIN_KW "builtin"
POUND "#"
NAKED_ASM_KW "naked_asm"
L_PAREN "("
LITERAL
STRING "\"\""
R_PAREN ")"
SEMICOLON ";"
WHITESPACE "\n"
R_CURLY "}"
WHITESPACE "\n"

View file

@ -0,0 +1,5 @@
fn foo() {
builtin#asm("");
builtin#global_asm("");
builtin#naked_asm("");
}

View file

@ -409,7 +409,8 @@ OffsetOfExpr =
// global_asm := "global_asm!(" format_string *("," format_string) *("," operand) [","] ")"
// format_string := STRING_LITERAL / RAW_STRING_LITERAL
AsmExpr =
Attr* 'builtin' '#' 'asm' '(' template:(Expr (',' Expr)*) (AsmPiece (',' AsmPiece)*)? ','? ')'
Attr* 'builtin' '#' ( 'asm' | 'global_asm' | 'naked_asm' )
'(' template:(Expr (',' Expr)*) (AsmPiece (',' AsmPiece)*)? ','? ')'
// operand_expr := expr / "_" / expr "=>" expr / expr "=>" "_"
AsmOperandExpr = in_expr:Expr ('=>' out_expr:Expr)?

View file

@ -118,6 +118,14 @@ impl AsmExpr {
pub fn asm_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![asm]) }
#[inline]
pub fn builtin_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![builtin]) }
#[inline]
pub fn global_asm_token(&self) -> Option<SyntaxToken> {
support::token(&self.syntax, T![global_asm])
}
#[inline]
pub fn naked_asm_token(&self) -> Option<SyntaxToken> {
support::token(&self.syntax, T![naked_asm])
}
}
pub struct AsmLabel {
pub(crate) syntax: SyntaxNode,

View file

@ -116,6 +116,8 @@ const CONTEXTUAL_KEYWORDS: &[&str] =
// keywords we use for special macro expansions
const CONTEXTUAL_BUILTIN_KEYWORDS: &[&str] = &[
"asm",
"naked_asm",
"global_asm",
"att_syntax",
"builtin",
"clobber_abi",