-
Notifications
You must be signed in to change notification settings - Fork 13.4k
Warn when passing pointers to asm! with nomem/readonly options #127063
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,4 @@ | ||
use rustc_ast::InlineAsmTemplatePiece; | ||
use rustc_ast::{InlineAsmOptions, InlineAsmTemplatePiece}; | ||
use rustc_data_structures::fx::FxIndexSet; | ||
use rustc_hir::{self as hir, LangItem}; | ||
use rustc_middle::bug; | ||
|
@@ -124,7 +124,7 @@ impl<'a, 'tcx> InlineAsmCtxt<'a, 'tcx> { | |
idx: usize, | ||
reg: InlineAsmRegOrRegClass, | ||
expr: &'tcx hir::Expr<'tcx>, | ||
template: &[InlineAsmTemplatePiece], | ||
asm: &hir::InlineAsm<'tcx>, | ||
is_input: bool, | ||
tied_input: Option<(&'tcx hir::Expr<'tcx>, Option<InlineAsmType>)>, | ||
target_features: &FxIndexSet<Symbol>, | ||
|
@@ -267,7 +267,7 @@ impl<'a, 'tcx> InlineAsmCtxt<'a, 'tcx> { | |
// Search for any use of this operand without a modifier and emit | ||
// the suggestion for them. | ||
let mut spans = vec![]; | ||
for piece in template { | ||
for piece in asm.template { | ||
if let &InlineAsmTemplatePiece::Placeholder { operand_idx, modifier, span } = piece | ||
{ | ||
if operand_idx == idx && modifier.is_none() { | ||
|
@@ -299,6 +299,28 @@ impl<'a, 'tcx> InlineAsmCtxt<'a, 'tcx> { | |
} | ||
} | ||
|
||
match *ty.kind() { | ||
ty::RawPtr(_, hir::Mutability::Mut) if asm.options.contains(InlineAsmOptions::READONLY) => | ||
self | ||
.tcx | ||
.dcx() | ||
.struct_span_warn(expr.span, "passing a mutable pointer to asm! block with 'readonly' option.") | ||
.with_note("`readonly` means that no memory write happens inside the asm! block.") | ||
.with_note("This is not limited to global variables, it also includes passed pointers.") | ||
.with_note("If passing this mutable pointer is intentional, remove the `readonly` attribute.") | ||
.emit(), | ||
ty::RawPtr(_, _) if asm.options.contains(InlineAsmOptions::NOMEM) => | ||
self | ||
.tcx | ||
.dcx() | ||
.struct_span_warn(expr.span, "passing a pointer to asm! block with 'nomem' option.") | ||
.with_note("`nomem` means that no memory write or read happens inside the asm! block.") | ||
.with_note("This is not limited to global variables, it also includes passed pointers.") | ||
.with_note("If passing this pointer is intentional, replace the `nomem` attribute with `readonly` or remove it completely.") | ||
.emit(), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same thing here, if passing the pointer is intentional then our recommendation should not involve changing the semantics of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
At first, I wanted to make it an error, but probably some code would break and Here we could impose impose a simple rule that would help avoiding bugs with unintentional Why would someone pass a pointer and do nothing with the data? If they need just the address, maybe they also need to consider if the provenance should be exposed or not. Idk, I think you know better than me how There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. just noting that there's like two, maybe three versions of "intended/intention" floating around in this discussion. let me see if I can name all of them:
|
||
_ => {} // we're only interested in pointers when asm! has `nomem` or `readonly` | ||
} | ||
|
||
Some(asm_ty) | ||
} | ||
|
||
|
@@ -399,46 +421,30 @@ impl<'a, 'tcx> InlineAsmCtxt<'a, 'tcx> { | |
|
||
match *op { | ||
hir::InlineAsmOperand::In { reg, expr } => { | ||
self.check_asm_operand_type( | ||
idx, | ||
reg, | ||
expr, | ||
asm.template, | ||
true, | ||
None, | ||
target_features, | ||
); | ||
self.check_asm_operand_type(idx, reg, expr, asm, true, None, target_features); | ||
} | ||
hir::InlineAsmOperand::Out { reg, late: _, expr } => { | ||
if let Some(expr) = expr { | ||
self.check_asm_operand_type( | ||
idx, | ||
reg, | ||
expr, | ||
asm.template, | ||
asm, | ||
false, | ||
None, | ||
target_features, | ||
); | ||
} | ||
} | ||
hir::InlineAsmOperand::InOut { reg, late: _, expr } => { | ||
self.check_asm_operand_type( | ||
idx, | ||
reg, | ||
expr, | ||
asm.template, | ||
false, | ||
None, | ||
target_features, | ||
); | ||
self.check_asm_operand_type(idx, reg, expr, asm, false, None, target_features); | ||
} | ||
hir::InlineAsmOperand::SplitInOut { reg, late: _, in_expr, out_expr } => { | ||
let in_ty = self.check_asm_operand_type( | ||
idx, | ||
reg, | ||
in_expr, | ||
asm.template, | ||
asm, | ||
true, | ||
None, | ||
target_features, | ||
|
@@ -448,7 +454,7 @@ impl<'a, 'tcx> InlineAsmCtxt<'a, 'tcx> { | |
idx, | ||
reg, | ||
out_expr, | ||
asm.template, | ||
asm, | ||
false, | ||
Some((in_expr, in_ty)), | ||
target_features, | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
//@ only-x86_64 | ||
//@ needs-asm-support | ||
//@ build-pass | ||
|
||
#![crate_type = "lib"] | ||
#![no_std] | ||
|
||
unsafe fn nomem_bad(p: &i32) { | ||
core::arch::asm!("mov {p}, {p}", p = in(reg) p, options(nomem, nostack, preserves_flags)); | ||
//~^ WARNING passing a pointer to asm! block with 'nomem' option. | ||
} | ||
|
||
unsafe fn readonly_bad(p: &mut i32) { | ||
core::arch::asm!("mov {p}, {p}", p = in(reg) p, options(readonly, nostack, preserves_flags)); | ||
//~^ WARNING passing a mutable pointer to asm! block with 'readonly' option. | ||
} | ||
|
||
unsafe fn nomem_good(p: &i32) { | ||
core::arch::asm!("mov {p}, {p}", p = in(reg) p, options(readonly, nostack, preserves_flags)); | ||
let p = p as *const i32 as usize; | ||
core::arch::asm!("mov {p}, {p}", p = in(reg) p, options(nomem, nostack, preserves_flags)); | ||
} | ||
|
||
unsafe fn readonly_good(p: &mut i32) { | ||
core::arch::asm!("mov {p}, {p}", p = in(reg) p, options(nostack, preserves_flags)); | ||
core::arch::asm!("mov {p}, {p}", p = in(reg) &*p, options(readonly, nostack, preserves_flags)); | ||
let p = p as *const i32; | ||
core::arch::asm!("mov {p}, {p}", p = in(reg) p, options(readonly, nostack, preserves_flags)); | ||
} | ||
|
||
unsafe fn nomem_bad2(p: &mut i32) { | ||
core::arch::asm!("mov {p}, {p}", p = in(reg) p, options(nomem, nostack, preserves_flags)); | ||
//~^ WARNING passing a pointer to asm! block with 'nomem' option. | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
warning: passing a pointer to asm! block with 'nomem' option. | ||
--> $DIR/passing-pointer-nomem-readonly.rs:9:50 | ||
| | ||
LL | core::arch::asm!("mov {p}, {p}", p = in(reg) p, options(nomem, nostack, preserves_flags)); | ||
| ^ | ||
| | ||
= note: `nomem` means that no memory write or read happens inside the asm! block. | ||
= note: This is not limited to global variables, it also includes passed pointers. | ||
= note: If passing this pointer is intentional, replace the `nomem` attribute with `readonly` or remove it completely. | ||
|
||
warning: passing a mutable pointer to asm! block with 'readonly' option. | ||
--> $DIR/passing-pointer-nomem-readonly.rs:14:50 | ||
| | ||
LL | core::arch::asm!("mov {p}, {p}", p = in(reg) p, options(readonly, nostack, preserves_flags)); | ||
| ^ | ||
| | ||
= note: `readonly` means that no memory write happens inside the asm! block. | ||
= note: This is not limited to global variables, it also includes passed pointers. | ||
= note: If passing this mutable pointer is intentional, remove the `readonly` attribute. | ||
|
||
warning: passing a pointer to asm! block with 'nomem' option. | ||
--> $DIR/passing-pointer-nomem-readonly.rs:32:50 | ||
| | ||
LL | core::arch::asm!("mov {p}, {p}", p = in(reg) p, options(nomem, nostack, preserves_flags)); | ||
| ^ | ||
| | ||
= note: `nomem` means that no memory write or read happens inside the asm! block. | ||
= note: This is not limited to global variables, it also includes passed pointers. | ||
= note: If passing this pointer is intentional, replace the `nomem` attribute with `readonly` or remove it completely. | ||
|
||
warning: 3 warnings emitted | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think this message is appropriate: if passing a mutable pointer is intentional, we should only suggest something which suppresses the warning, not something that changes the semantics of the
asm!
.The problem here is that I don't see an obvious way to suppress the warning. Perhaps this lint would be better in clippy instead rather than something that generally applies to all Rust code?
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, now that I'm thinking about it, maybe you're right -
*mut
pointer should anyway implicitly cast to*const
one. What do you think about the other case with*const/mut
andnomem
?