Skip to content

Commit 92f3753

Browse files
committed
Auto merge of #84039 - jyn514:uplift-atomic-ordering, r=wesleywiser
Uplift the invalid_atomic_ordering lint from clippy to rustc This is mostly just a rebase of #79654; I've copy/pasted the text from that PR below. r? `@lcnr` since you reviewed the last one, but feel free to reassign. --- This is an implementation of rust-lang/compiler-team#390. As mentioned, in general this turns an unconditional runtime panic into a (compile time) lint failure. It has no false positives, and the only false negatives I'm aware of are if `Ordering` isn't specified directly and is comes from an argument/constant/whatever. As a result of it having no false positives, and the alternative always being strictly wrong, it's on as deny by default. This seems right. In the [zulip stream](https://rust-lang.zulipchat.com/#narrow/stream/233931-t-compiler.2Fmajor-changes/topic/Uplift.20the.20.60invalid_atomic_ordering.60.20lint.20from.20clippy/near/218483957) `@joshtriplett` suggested that lang team should FCP this before landing it. Perhaps libs team cares too? --- Some notes on the code for reviewers / others below ## Changes from clippy The code is changed from [the implementation in clippy](https://github.com/rust-lang/rust-clippy/blob/68cf94f6a66e47234e3adefc6dfbe806cd6ad164/clippy_lints/src/atomic_ordering.rs) in the following ways: 1. Uses `Symbols` and `rustc_diagnostic_item`s instead of string literals. - It's possible I should have just invoked Symbol::intern for some of these instead? Seems better to use symbol, but it did require adding several. 2. The functions are moved to static methods inside the lint struct, as a way to namespace them. - There's a lot of other code in that file — which I picked as the location for this lint because `@jyn514` told me that seemed reasonable. 3. Supports unstable AtomicU128/AtomicI128. - I did this because it was almost easier to support them than not — not supporting them would have (ideally) required finding a way not to give them a `rustc_diagnostic_item`, which would have complicated an already big macro. - These don't have tests since I wasn't sure if/how I should make tests conditional on whether or not the target has the atomic... This is to a certain extent an issue of 64bit atomics too, but 128-bit atomics are much less common. Regardless, the existing tests should be *more* than thorough enough here. 4. Minor changes like: - grammar tweaks ("loads cannot have `Release` **and** `AcqRel` ordering" => "loads cannot have `Release` **or** `AcqRel` ordering") - function renames (`match_ordering_def_path` => `matches_ordering_def_path`), - avoiding clippy-specific helper methods that don't exist in rustc_lint and didn't seem worth adding for this case (for example `cx.struct_span_lint` vs clippy's `span_lint_and_help` helper). ## Potential issues (This is just about the code in this PR, not conceptual issues with the lint or anything) 1. I'm not sure if I should have used a diagnostic item for `Ordering` and its variants (I couldn't figure out how really, so if I should do this some pointers would be appreciated). - It seems possible that failing to do this might possibly mean there are more cases this lint would miss, but I don't really know how `match_def_path` works and if it has any pitfalls like that, so maybe not. 2. I *think* I deprecated the lint in clippy (CC `@flip1995` who asked to be notified about clippy changes in the future in [this comment](#75671 (comment))) but I'm not sure if I need to do anything else there. - I'm kind of hoping CI will catch if I missed anything, since `x.py test src/tools/clippy` fails with a lot of errors with and without my changes (and is probably a nonsense command regardless). Running `cargo test` from src/tools/clippy also fails with unrelated errors that seem like refactorings that didnt update clippy? So, honestly no clue. 3. I wasn't sure if the description/example I gave good. Hopefully it is. The example is less thorough than the one from clippy here: https://rust-lang.github.io/rust-clippy/master/index.html#invalid_atomic_ordering. Let me know if/how I should change it if it needs changing. 4. It pulls in the `if_chain` crate. This crate was already used in clippy, and seems like it's used elsewhere in rustc, but I'm willing to rewrite it to not use this if needed (I'd prefer not to, all things being equal).
2 parents 23461b2 + 5522177 commit 92f3753

28 files changed

+639
-437
lines changed

Cargo.lock

+1
Original file line numberDiff line numberDiff line change
@@ -3924,6 +3924,7 @@ dependencies = [
39243924
name = "rustc_lint"
39253925
version = "0.0.0"
39263926
dependencies = [
3927+
"if_chain",
39273928
"rustc_ast",
39283929
"rustc_ast_pretty",
39293930
"rustc_attr",

compiler/rustc_lint/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ version = "0.0.0"
44
edition = "2018"
55

66
[dependencies]
7+
if_chain = "1.0"
78
tracing = "0.1"
89
unicode-security = "0.0.5"
910
rustc_middle = { path = "../rustc_middle" }

compiler/rustc_lint/src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,7 @@ macro_rules! late_lint_passes {
170170
TemporaryCStringAsPtr: TemporaryCStringAsPtr,
171171
NonPanicFmt: NonPanicFmt,
172172
NoopMethodCall: NoopMethodCall,
173+
InvalidAtomicOrdering: InvalidAtomicOrdering,
173174
]
174175
);
175176
};

compiler/rustc_lint/src/types.rs

+238-3
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,19 @@ use rustc_attr as attr;
44
use rustc_data_structures::fx::FxHashSet;
55
use rustc_errors::Applicability;
66
use rustc_hir as hir;
7-
use rustc_hir::{is_range_literal, ExprKind, Node};
7+
use rustc_hir::def_id::DefId;
8+
use rustc_hir::{is_range_literal, Expr, ExprKind, Node};
89
use rustc_middle::ty::layout::{IntegerExt, SizeSkeleton};
910
use rustc_middle::ty::subst::SubstsRef;
10-
use rustc_middle::ty::{self, AdtKind, Ty, TyCtxt, TypeFoldable};
11+
use rustc_middle::ty::{self, AdtKind, DefIdTree, Ty, TyCtxt, TypeFoldable};
1112
use rustc_span::source_map;
1213
use rustc_span::symbol::sym;
13-
use rustc_span::{Span, DUMMY_SP};
14+
use rustc_span::{Span, Symbol, DUMMY_SP};
1415
use rustc_target::abi::Abi;
1516
use rustc_target::abi::{Integer, LayoutOf, TagEncoding, Variants};
1617
use rustc_target::spec::abi::Abi as SpecAbi;
1718

19+
use if_chain::if_chain;
1820
use std::cmp;
1921
use std::iter;
2022
use std::ops::ControlFlow;
@@ -1379,3 +1381,236 @@ impl<'tcx> LateLintPass<'tcx> for VariantSizeDifferences {
13791381
}
13801382
}
13811383
}
1384+
1385+
declare_lint! {
1386+
/// The `invalid_atomic_ordering` lint detects passing an `Ordering`
1387+
/// to an atomic operation that does not support that ordering.
1388+
///
1389+
/// ### Example
1390+
///
1391+
/// ```rust,compile_fail
1392+
/// # use core::sync::atomic::{AtomicU8, Ordering};
1393+
/// let atom = AtomicU8::new(0);
1394+
/// let value = atom.load(Ordering::Release);
1395+
/// # let _ = value;
1396+
/// ```
1397+
///
1398+
/// {{produces}}
1399+
///
1400+
/// ### Explanation
1401+
///
1402+
/// Some atomic operations are only supported for a subset of the
1403+
/// `atomic::Ordering` variants. Passing an unsupported variant will cause
1404+
/// an unconditional panic at runtime, which is detected by this lint.
1405+
///
1406+
/// This lint will trigger in the following cases: (where `AtomicType` is an
1407+
/// atomic type from `core::sync::atomic`, such as `AtomicBool`,
1408+
/// `AtomicPtr`, `AtomicUsize`, or any of the other integer atomics).
1409+
///
1410+
/// - Passing `Ordering::Acquire` or `Ordering::AcqRel` to
1411+
/// `AtomicType::store`.
1412+
///
1413+
/// - Passing `Ordering::Release` or `Ordering::AcqRel` to
1414+
/// `AtomicType::load`.
1415+
///
1416+
/// - Passing `Ordering::Relaxed` to `core::sync::atomic::fence` or
1417+
/// `core::sync::atomic::compiler_fence`.
1418+
///
1419+
/// - Passing `Ordering::Release` or `Ordering::AcqRel` as the failure
1420+
/// ordering for any of `AtomicType::compare_exchange`,
1421+
/// `AtomicType::compare_exchange_weak`, or `AtomicType::fetch_update`.
1422+
///
1423+
/// - Passing in a pair of orderings to `AtomicType::compare_exchange`,
1424+
/// `AtomicType::compare_exchange_weak`, or `AtomicType::fetch_update`
1425+
/// where the failure ordering is stronger than the success ordering.
1426+
INVALID_ATOMIC_ORDERING,
1427+
Deny,
1428+
"usage of invalid atomic ordering in atomic operations and memory fences"
1429+
}
1430+
1431+
declare_lint_pass!(InvalidAtomicOrdering => [INVALID_ATOMIC_ORDERING]);
1432+
1433+
impl InvalidAtomicOrdering {
1434+
fn inherent_atomic_method_call<'hir>(
1435+
cx: &LateContext<'_>,
1436+
expr: &Expr<'hir>,
1437+
recognized_names: &[Symbol], // used for fast path calculation
1438+
) -> Option<(Symbol, &'hir [Expr<'hir>])> {
1439+
const ATOMIC_TYPES: &[Symbol] = &[
1440+
sym::AtomicBool,
1441+
sym::AtomicPtr,
1442+
sym::AtomicUsize,
1443+
sym::AtomicU8,
1444+
sym::AtomicU16,
1445+
sym::AtomicU32,
1446+
sym::AtomicU64,
1447+
sym::AtomicU128,
1448+
sym::AtomicIsize,
1449+
sym::AtomicI8,
1450+
sym::AtomicI16,
1451+
sym::AtomicI32,
1452+
sym::AtomicI64,
1453+
sym::AtomicI128,
1454+
];
1455+
if_chain! {
1456+
if let ExprKind::MethodCall(ref method_path, _, args, _) = &expr.kind;
1457+
if recognized_names.contains(&method_path.ident.name);
1458+
if let Some(m_def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id);
1459+
if let Some(impl_did) = cx.tcx.impl_of_method(m_def_id);
1460+
if let Some(adt) = cx.tcx.type_of(impl_did).ty_adt_def();
1461+
// skip extension traits, only lint functions from the standard library
1462+
if cx.tcx.trait_id_of_impl(impl_did).is_none();
1463+
1464+
if let Some(parent) = cx.tcx.parent(adt.did);
1465+
if cx.tcx.is_diagnostic_item(sym::atomic_mod, parent);
1466+
if ATOMIC_TYPES.contains(&cx.tcx.item_name(adt.did));
1467+
then {
1468+
return Some((method_path.ident.name, args));
1469+
}
1470+
}
1471+
None
1472+
}
1473+
1474+
fn matches_ordering(cx: &LateContext<'_>, did: DefId, orderings: &[Symbol]) -> bool {
1475+
let tcx = cx.tcx;
1476+
let atomic_ordering = tcx.get_diagnostic_item(sym::Ordering);
1477+
orderings.iter().any(|ordering| {
1478+
tcx.item_name(did) == *ordering && {
1479+
let parent = tcx.parent(did);
1480+
parent == atomic_ordering
1481+
// needed in case this is a ctor, not a variant
1482+
|| parent.map_or(false, |parent| tcx.parent(parent) == atomic_ordering)
1483+
}
1484+
})
1485+
}
1486+
1487+
fn opt_ordering_defid(cx: &LateContext<'_>, ord_arg: &Expr<'_>) -> Option<DefId> {
1488+
if let ExprKind::Path(ref ord_qpath) = ord_arg.kind {
1489+
cx.qpath_res(ord_qpath, ord_arg.hir_id).opt_def_id()
1490+
} else {
1491+
None
1492+
}
1493+
}
1494+
1495+
fn check_atomic_load_store(cx: &LateContext<'_>, expr: &Expr<'_>) {
1496+
use rustc_hir::def::{DefKind, Res};
1497+
use rustc_hir::QPath;
1498+
if_chain! {
1499+
if let Some((method, args)) = Self::inherent_atomic_method_call(cx, expr, &[sym::load, sym::store]);
1500+
if let Some((ordering_arg, invalid_ordering)) = match method {
1501+
sym::load => Some((&args[1], sym::Release)),
1502+
sym::store => Some((&args[2], sym::Acquire)),
1503+
_ => None,
1504+
};
1505+
1506+
if let ExprKind::Path(QPath::Resolved(_, path)) = ordering_arg.kind;
1507+
if let Res::Def(DefKind::Ctor(..), ctor_id) = path.res;
1508+
if Self::matches_ordering(cx, ctor_id, &[invalid_ordering, sym::AcqRel]);
1509+
then {
1510+
cx.struct_span_lint(INVALID_ATOMIC_ORDERING, ordering_arg.span, |diag| {
1511+
if method == sym::load {
1512+
diag.build("atomic loads cannot have `Release` or `AcqRel` ordering")
1513+
.help("consider using ordering modes `Acquire`, `SeqCst` or `Relaxed`")
1514+
.emit()
1515+
} else {
1516+
debug_assert_eq!(method, sym::store);
1517+
diag.build("atomic stores cannot have `Acquire` or `AcqRel` ordering")
1518+
.help("consider using ordering modes `Release`, `SeqCst` or `Relaxed`")
1519+
.emit();
1520+
}
1521+
});
1522+
}
1523+
}
1524+
}
1525+
1526+
fn check_memory_fence(cx: &LateContext<'_>, expr: &Expr<'_>) {
1527+
if_chain! {
1528+
if let ExprKind::Call(ref func, ref args) = expr.kind;
1529+
if let ExprKind::Path(ref func_qpath) = func.kind;
1530+
if let Some(def_id) = cx.qpath_res(func_qpath, func.hir_id).opt_def_id();
1531+
if cx.tcx.is_diagnostic_item(sym::fence, def_id) ||
1532+
cx.tcx.is_diagnostic_item(sym::compiler_fence, def_id);
1533+
if let ExprKind::Path(ref ordering_qpath) = &args[0].kind;
1534+
if let Some(ordering_def_id) = cx.qpath_res(ordering_qpath, args[0].hir_id).opt_def_id();
1535+
if Self::matches_ordering(cx, ordering_def_id, &[sym::Relaxed]);
1536+
then {
1537+
cx.struct_span_lint(INVALID_ATOMIC_ORDERING, args[0].span, |diag| {
1538+
diag.build("memory fences cannot have `Relaxed` ordering")
1539+
.help("consider using ordering modes `Acquire`, `Release`, `AcqRel` or `SeqCst`")
1540+
.emit();
1541+
});
1542+
}
1543+
}
1544+
}
1545+
1546+
fn check_atomic_compare_exchange(cx: &LateContext<'_>, expr: &Expr<'_>) {
1547+
if_chain! {
1548+
if let Some((method, args)) = Self::inherent_atomic_method_call(cx, expr, &[sym::fetch_update, sym::compare_exchange, sym::compare_exchange_weak]);
1549+
if let Some((success_order_arg, failure_order_arg)) = match method {
1550+
sym::fetch_update => Some((&args[1], &args[2])),
1551+
sym::compare_exchange | sym::compare_exchange_weak => Some((&args[3], &args[4])),
1552+
_ => None,
1553+
};
1554+
1555+
if let Some(fail_ordering_def_id) = Self::opt_ordering_defid(cx, failure_order_arg);
1556+
then {
1557+
// Helper type holding on to some checking and error reporting data. Has
1558+
// - (success ordering,
1559+
// - list of failure orderings forbidden by the success order,
1560+
// - suggestion message)
1561+
type OrdLintInfo = (Symbol, &'static [Symbol], &'static str);
1562+
const RELAXED: OrdLintInfo = (sym::Relaxed, &[sym::SeqCst, sym::Acquire], "ordering mode `Relaxed`");
1563+
const ACQUIRE: OrdLintInfo = (sym::Acquire, &[sym::SeqCst], "ordering modes `Acquire` or `Relaxed`");
1564+
const SEQ_CST: OrdLintInfo = (sym::SeqCst, &[], "ordering modes `Acquire`, `SeqCst` or `Relaxed`");
1565+
const RELEASE: OrdLintInfo = (sym::Release, RELAXED.1, RELAXED.2);
1566+
const ACQREL: OrdLintInfo = (sym::AcqRel, ACQUIRE.1, ACQUIRE.2);
1567+
const SEARCH: [OrdLintInfo; 5] = [RELAXED, ACQUIRE, SEQ_CST, RELEASE, ACQREL];
1568+
1569+
let success_lint_info = Self::opt_ordering_defid(cx, success_order_arg)
1570+
.and_then(|success_ord_def_id| -> Option<OrdLintInfo> {
1571+
SEARCH
1572+
.iter()
1573+
.copied()
1574+
.find(|(ordering, ..)| {
1575+
Self::matches_ordering(cx, success_ord_def_id, &[*ordering])
1576+
})
1577+
});
1578+
if Self::matches_ordering(cx, fail_ordering_def_id, &[sym::Release, sym::AcqRel]) {
1579+
// If we don't know the success order is, use what we'd suggest
1580+
// if it were maximally permissive.
1581+
let suggested = success_lint_info.unwrap_or(SEQ_CST).2;
1582+
cx.struct_span_lint(INVALID_ATOMIC_ORDERING, failure_order_arg.span, |diag| {
1583+
let msg = format!(
1584+
"{}'s failure ordering may not be `Release` or `AcqRel`",
1585+
method,
1586+
);
1587+
diag.build(&msg)
1588+
.help(&format!("consider using {} instead", suggested))
1589+
.emit();
1590+
});
1591+
} else if let Some((success_ord, bad_ords_given_success, suggested)) = success_lint_info {
1592+
if Self::matches_ordering(cx, fail_ordering_def_id, bad_ords_given_success) {
1593+
cx.struct_span_lint(INVALID_ATOMIC_ORDERING, failure_order_arg.span, |diag| {
1594+
let msg = format!(
1595+
"{}'s failure ordering may not be stronger than the success ordering of `{}`",
1596+
method,
1597+
success_ord,
1598+
);
1599+
diag.build(&msg)
1600+
.help(&format!("consider using {} instead", suggested))
1601+
.emit();
1602+
});
1603+
}
1604+
}
1605+
}
1606+
}
1607+
}
1608+
}
1609+
1610+
impl<'tcx> LateLintPass<'tcx> for InvalidAtomicOrdering {
1611+
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
1612+
Self::check_atomic_load_store(cx, expr);
1613+
Self::check_memory_fence(cx, expr);
1614+
Self::check_atomic_compare_exchange(cx, expr);
1615+
}
1616+
}

compiler/rustc_span/src/symbol.rs

+29
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,8 @@ symbols! {
121121
// There is currently no checking that all symbols are used; that would be
122122
// nice to have.
123123
Symbols {
124+
AcqRel,
125+
Acquire,
124126
Alignment,
125127
Any,
126128
Arc,
@@ -129,6 +131,20 @@ symbols! {
129131
Arguments,
130132
AsMut,
131133
AsRef,
134+
AtomicBool,
135+
AtomicI128,
136+
AtomicI16,
137+
AtomicI32,
138+
AtomicI64,
139+
AtomicI8,
140+
AtomicIsize,
141+
AtomicPtr,
142+
AtomicU128,
143+
AtomicU16,
144+
AtomicU32,
145+
AtomicU64,
146+
AtomicU8,
147+
AtomicUsize,
132148
BTreeEntry,
133149
BTreeMap,
134150
BTreeSet,
@@ -215,12 +231,15 @@ symbols! {
215231
Rc,
216232
Ready,
217233
Receiver,
234+
Relaxed,
235+
Release,
218236
Result,
219237
Return,
220238
Right,
221239
RustcDecodable,
222240
RustcEncodable,
223241
Send,
242+
SeqCst,
224243
Some,
225244
StructuralEq,
226245
StructuralPartialEq,
@@ -311,6 +330,8 @@ symbols! {
311330
assume_init,
312331
async_await,
313332
async_closure,
333+
atomic,
334+
atomic_mod,
314335
atomics,
315336
att_syntax,
316337
attr,
@@ -392,8 +413,12 @@ symbols! {
392413
coerce_unsized,
393414
cold,
394415
column,
416+
compare_and_swap,
417+
compare_exchange,
418+
compare_exchange_weak,
395419
compile_error,
396420
compiler_builtins,
421+
compiler_fence,
397422
concat,
398423
concat_idents,
399424
conservative_impl_trait,
@@ -578,6 +603,8 @@ symbols! {
578603
fadd_fast,
579604
fdiv_fast,
580605
feature,
606+
fence,
607+
fetch_update,
581608
ffi,
582609
ffi_const,
583610
ffi_pure,
@@ -731,6 +758,7 @@ symbols! {
731758
lint_reasons,
732759
literal,
733760
llvm_asm,
761+
load,
734762
local,
735763
local_inner_macros,
736764
log10f32,
@@ -1220,6 +1248,7 @@ symbols! {
12201248
stmt,
12211249
stmt_expr_attributes,
12221250
stop_after_dataflow,
1251+
store,
12231252
str,
12241253
str_alloc,
12251254
string_type,

0 commit comments

Comments
 (0)