Skip to content

Uplift the invalid_atomic_ordering lint from clippy to rustc #84039

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

Merged
merged 2 commits into from
Aug 16, 2021
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Uplift the invalid_atomic_ordering lint from clippy to rustc
- Deprecate clippy::invalid_atomic_ordering
- Use rustc_diagnostic_item for the orderings in the invalid_atomic_ordering lint
- Reduce code duplication
- Give up on making enum variants diagnostic items and just look for
`Ordering` instead

  I ran into tons of trouble with this because apparently the change to
  store HIR attrs in a side table also gave the DefIds of the
  constructor instead of the variant itself. So I had to change
  `matches_ordering` to also check the grandparent of the defid as well.

- Rename `atomic_ordering_x` symbols to just the name of the variant
- Fix typos in checks - there were a few places that said "may not be
  Release" in the diagnostic but actually checked for SeqCst in the lint.
- Make constant items const
- Use fewer diagnostic items
- Only look at arguments after making sure the method matches

  This prevents an ICE when there aren't enough arguments.

- Ignore trait methods
- Only check Ctors instead of going through `qpath_res`

  The functions take values, so this couldn't ever be anything else.

- Add if_chain to allowed dependencies
- Fix grammar
- Remove unnecessary allow
  • Loading branch information
thomcc authored and jyn514 committed Aug 16, 2021
commit 402a9c9f5e143baaf37ef8b68d84edaadda253dd
1 change: 1 addition & 0 deletions Cargo.lock
Original file line number Diff line number Diff line change
Expand Up @@ -3923,6 +3923,7 @@ dependencies = [
name = "rustc_lint"
version = "0.0.0"
dependencies = [
"if_chain",
"rustc_ast",
"rustc_ast_pretty",
"rustc_attr",
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_lint/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ version = "0.0.0"
edition = "2018"

[dependencies]
if_chain = "1.0"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ooh bold move!

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would prefer that we try to keep our external dependencies as small as possible and this doesn't seem like a necessary addition to me but we have no official policy saying that, so I don't think it should hold up this PR.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@wesleywiser if_chain is already in the source tree through clippy, this just adds it to rustc itself. I could rewrite it without if_chain, but it would be annoying and IMO a lot uglier.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that's true but clippy isn't a dependency of rustc so any dependencies they pull in don't actually affect rustc. That's why you had to add it to the tidy allowed set of dependencies.

It's my personal preference and we already have quite a lot of external dependencies as that list showed so don't worry about it 🙂

tracing = "0.1"
unicode-security = "0.0.5"
rustc_middle = { path = "../rustc_middle" }
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_lint/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,7 @@ macro_rules! late_lint_passes {
TemporaryCStringAsPtr: TemporaryCStringAsPtr,
NonPanicFmt: NonPanicFmt,
NoopMethodCall: NoopMethodCall,
InvalidAtomicOrdering: InvalidAtomicOrdering,
]
);
};
Expand Down
239 changes: 236 additions & 3 deletions compiler/rustc_lint/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,19 @@ use rustc_attr as attr;
use rustc_data_structures::fx::FxHashSet;
use rustc_errors::Applicability;
use rustc_hir as hir;
use rustc_hir::{is_range_literal, ExprKind, Node};
use rustc_hir::def_id::DefId;
use rustc_hir::{is_range_literal, Expr, ExprKind, Node};
use rustc_middle::ty::layout::{IntegerExt, SizeSkeleton};
use rustc_middle::ty::subst::SubstsRef;
use rustc_middle::ty::{self, AdtKind, Ty, TyCtxt, TypeFoldable};
use rustc_middle::ty::{self, AdtKind, DefIdTree, Ty, TyCtxt, TypeFoldable};
use rustc_span::source_map;
use rustc_span::symbol::sym;
use rustc_span::{Span, DUMMY_SP};
use rustc_span::{Span, Symbol, DUMMY_SP};
use rustc_target::abi::Abi;
use rustc_target::abi::{Integer, LayoutOf, TagEncoding, Variants};
use rustc_target::spec::abi::Abi as SpecAbi;

use if_chain::if_chain;
use std::cmp;
use std::iter;
use std::ops::ControlFlow;
Expand Down Expand Up @@ -1379,3 +1381,234 @@ impl<'tcx> LateLintPass<'tcx> for VariantSizeDifferences {
}
}
}

declare_lint! {
/// The `invalid_atomic_ordering` lint detects passing an `Ordering`
/// to an atomic operation that does not support that ordering.
///
/// ### Example
///
/// ```rust,compile_fail
/// # use core::sync::atomic::{AtomicU8, Ordering};
/// let atom = AtomicU8::new(0);
/// let value = atom.load(Ordering::Release);
/// # let _ = value;
/// ```
///
/// {{produces}}
///
/// ### Explanation
///
/// Some atomic operations are only supported for a subset of the
/// `atomic::Ordering` variants. Passing an unsupported variant will cause
/// an unconditional panic at runtime, which is detected by this lint.
///
/// This lint will trigger in the following cases: (where `AtomicType` is an
/// atomic type from `core::sync::atomic`, such as `AtomicBool`,
/// `AtomicPtr`, `AtomicUsize`, or any of the other integer atomics).
///
/// - Passing `Ordering::Acquire` or `Ordering::AcqRel` to
/// `AtomicType::store`.
///
/// - Passing `Ordering::Release` or `Ordering::AcqRel` to
/// `AtomicType::load`.
///
/// - Passing `Ordering::Relaxed` to `core::sync::atomic::fence` or
/// `core::sync::atomic::compiler_fence`.
///
/// - Passing `Ordering::Release` or `Ordering::AcqRel` as the failure
/// ordering for any of `AtomicType::compare_exchange`,
/// `AtomicType::compare_exchange_weak`, or `AtomicType::fetch_update`.
///
/// - Passing in a pair of orderings to `AtomicType::compare_exchange`,
/// `AtomicType::compare_exchange_weak`, or `AtomicType::fetch_update`
/// where the failure ordering is stronger than the success ordering.
INVALID_ATOMIC_ORDERING,
Deny,
"usage of invalid atomic ordering in atomic operations and memory fences"
}

declare_lint_pass!(InvalidAtomicOrdering => [INVALID_ATOMIC_ORDERING]);

impl InvalidAtomicOrdering {
fn inherent_atomic_method_call<'hir>(
cx: &LateContext<'_>,
expr: &Expr<'hir>,
) -> Option<(Symbol, &'hir [Expr<'hir>])> {
const ATOMIC_TYPES: &[Symbol] = &[
sym::AtomicBool,
sym::AtomicPtr,
sym::AtomicUsize,
sym::AtomicU8,
sym::AtomicU16,
sym::AtomicU32,
sym::AtomicU64,
sym::AtomicU128,
sym::AtomicIsize,
sym::AtomicI8,
sym::AtomicI16,
sym::AtomicI32,
sym::AtomicI64,
sym::AtomicI128,
];
if_chain! {
if let ExprKind::MethodCall(ref method_path, _, args, _) = &expr.kind;
if let Some(m_def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id);
if let Some(impl_did) = cx.tcx.impl_of_method(m_def_id);
if let Some(adt) = cx.tcx.type_of(impl_did).ty_adt_def();
// skip extension traits, only lint functions from the standard library
if cx.tcx.trait_id_of_impl(impl_did).is_none();

if let Some(parent) = cx.tcx.parent(adt.did);
if cx.tcx.is_diagnostic_item(sym::atomic_mod, parent);
if ATOMIC_TYPES.contains(&cx.tcx.item_name(adt.did));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if we should compute the set of DefIds for functions we care about once, store that in InvalidAtomicOrdering and then here we can just see if m_def_id is in that set instead of having to look up the impl, the AdtDef, the parent, etc for every method call.

I guess we can do a perf run before merging. If there's no impact, it's probably not worth the work unless you think that would make the code clearer.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, the perf run showed type_of, metadata_decode_entry_type_of, diagnostic_items, impl_trait_ref, metadata_decode_entry_associated_item, and metadata_decode_entry_opt_def_kind being a lot slower. Your caching idea would help with diagnostic_items, but I don't think it would help much with any of the others. Instead I'm going to check if the name of the method is one of the four hard-coded function names (load/store/fetch_update/compare_exchange), and only if so do the expensive type_of check.

I also have a feeling that the other functions that don't go through inherent_atomic_method are slow - only check_atomic_compare_exchange uses it.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, I'm not sure how to get a list of the items in a local module :/ item_children panics.

error: internal compiler error: compiler/rustc_middle/src/ty/query/mod.rs:285:1: `tcx.item_children(DefId(0:8474 ~ core[c54b]::sync::atomic))` unsupported by its crate; perhaps the `item_children` query was never assigned a provider function

then {
return Some((method_path.ident.name, args));
}
}
None
}

fn matches_ordering(cx: &LateContext<'_>, did: DefId, orderings: &[Symbol]) -> bool {
let tcx = cx.tcx;
let atomic_ordering = tcx.get_diagnostic_item(sym::Ordering);
orderings.iter().any(|ordering| {
tcx.item_name(did) == *ordering && {
let parent = tcx.parent(did);
parent == atomic_ordering
// needed in case this is a ctor, not a variant
|| parent.map_or(false, |parent| tcx.parent(parent) == atomic_ordering)
}
})
}

fn opt_ordering_defid(cx: &LateContext<'_>, ord_arg: &Expr<'_>) -> Option<DefId> {
if let ExprKind::Path(ref ord_qpath) = ord_arg.kind {
cx.qpath_res(ord_qpath, ord_arg.hir_id).opt_def_id()
} else {
None
}
}

fn check_atomic_load_store(cx: &LateContext<'_>, expr: &Expr<'_>) {
use rustc_hir::def::{DefKind, Res};
use rustc_hir::QPath;
if_chain! {
if let Some((method, args)) = Self::inherent_atomic_method_call(cx, expr);
if let Some((ordering_arg, invalid_ordering)) = match method {
sym::load => Some((&args[1], sym::Release)),
sym::store => Some((&args[2], sym::Acquire)),
_ => None,
};

if let ExprKind::Path(QPath::Resolved(_, path)) = ordering_arg.kind;
if let Res::Def(DefKind::Ctor(..), ctor_id) = path.res;
if Self::matches_ordering(cx, ctor_id, &[invalid_ordering, sym::AcqRel]);
then {
cx.struct_span_lint(INVALID_ATOMIC_ORDERING, ordering_arg.span, |diag| {
if method == sym::load {
diag.build("atomic loads cannot have `Release` or `AcqRel` ordering")
.help("consider using ordering modes `Acquire`, `SeqCst` or `Relaxed`")
.emit()
} else {
debug_assert_eq!(method, sym::store);
diag.build("atomic stores cannot have `Acquire` or `AcqRel` ordering")
.help("consider using ordering modes `Release`, `SeqCst` or `Relaxed`")
.emit();
}
});
}
}
}

fn check_memory_fence(cx: &LateContext<'_>, expr: &Expr<'_>) {
if_chain! {
if let ExprKind::Call(ref func, ref args) = expr.kind;
if let ExprKind::Path(ref func_qpath) = func.kind;
if let Some(def_id) = cx.qpath_res(func_qpath, func.hir_id).opt_def_id();
if cx.tcx.is_diagnostic_item(sym::fence, def_id) ||
cx.tcx.is_diagnostic_item(sym::compiler_fence, def_id);
if let ExprKind::Path(ref ordering_qpath) = &args[0].kind;
if let Some(ordering_def_id) = cx.qpath_res(ordering_qpath, args[0].hir_id).opt_def_id();
if Self::matches_ordering(cx, ordering_def_id, &[sym::Relaxed]);
then {
cx.struct_span_lint(INVALID_ATOMIC_ORDERING, args[0].span, |diag| {
diag.build("memory fences cannot have `Relaxed` ordering")
.help("consider using ordering modes `Acquire`, `Release`, `AcqRel` or `SeqCst`")
.emit();
});
}
}
}

fn check_atomic_compare_exchange(cx: &LateContext<'_>, expr: &Expr<'_>) {
if_chain! {
if let Some((method, args)) = Self::inherent_atomic_method_call(cx, expr);
if let Some((success_order_arg, failure_order_arg)) = match method {
sym::fetch_update => Some((&args[1], &args[2])),
sym::compare_exchange | sym::compare_exchange_weak => Some((&args[3], &args[4])),
_ => None,
};

if let Some(fail_ordering_def_id) = Self::opt_ordering_defid(cx, failure_order_arg);
then {
// Helper type holding on to some checking and error reporting data. Has
// - (success ordering,
// - list of failure orderings forbidden by the success order,
// - suggestion message)
type OrdLintInfo = (Symbol, &'static [Symbol], &'static str);
const RELAXED: OrdLintInfo = (sym::Relaxed, &[sym::SeqCst, sym::Acquire], "ordering mode `Relaxed`");
const ACQUIRE: OrdLintInfo = (sym::Acquire, &[sym::SeqCst], "ordering modes `Acquire` or `Relaxed`");
const SEQ_CST: OrdLintInfo = (sym::SeqCst, &[], "ordering modes `Acquire`, `SeqCst` or `Relaxed`");
const RELEASE: OrdLintInfo = (sym::Release, RELAXED.1, RELAXED.2);
const ACQREL: OrdLintInfo = (sym::AcqRel, ACQUIRE.1, ACQUIRE.2);
const SEARCH: [OrdLintInfo; 5] = [RELAXED, ACQUIRE, SEQ_CST, RELEASE, ACQREL];

let success_lint_info = Self::opt_ordering_defid(cx, success_order_arg)
.and_then(|success_ord_def_id| -> Option<OrdLintInfo> {
SEARCH
.iter()
.copied()
.find(|(ordering, ..)| {
Self::matches_ordering(cx, success_ord_def_id, &[*ordering])
})
});
if Self::matches_ordering(cx, fail_ordering_def_id, &[sym::Release, sym::AcqRel]) {
// If we don't know the success order is, use what we'd suggest
// if it were maximally permissive.
let suggested = success_lint_info.unwrap_or(SEQ_CST).2;
cx.struct_span_lint(INVALID_ATOMIC_ORDERING, failure_order_arg.span, |diag| {
let msg = format!(
"{}'s failure ordering may not be `Release` or `AcqRel`",
method,
);
diag.build(&msg)
.help(&format!("consider using {} instead", suggested))
.emit();
});
} else if let Some((success_ord, bad_ords_given_success, suggested)) = success_lint_info {
if Self::matches_ordering(cx, fail_ordering_def_id, bad_ords_given_success) {
cx.struct_span_lint(INVALID_ATOMIC_ORDERING, failure_order_arg.span, |diag| {
let msg = format!(
"{}'s failure ordering may not be stronger than the success ordering of `{}`",
method,
success_ord,
);
diag.build(&msg)
.help(&format!("consider using {} instead", suggested))
.emit();
});
}
}
}
}
}
}

impl<'tcx> LateLintPass<'tcx> for InvalidAtomicOrdering {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
Self::check_atomic_load_store(cx, expr);
Self::check_memory_fence(cx, expr);
Self::check_atomic_compare_exchange(cx, expr);
}
}
29 changes: 29 additions & 0 deletions compiler/rustc_span/src/symbol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,8 @@ symbols! {
// There is currently no checking that all symbols are used; that would be
// nice to have.
Symbols {
AcqRel,
Acquire,
Alignment,
Any,
Arc,
Expand All @@ -129,6 +131,20 @@ symbols! {
Arguments,
AsMut,
AsRef,
AtomicBool,
AtomicI128,
AtomicI16,
AtomicI32,
AtomicI64,
AtomicI8,
AtomicIsize,
AtomicPtr,
AtomicU128,
AtomicU16,
AtomicU32,
AtomicU64,
AtomicU8,
AtomicUsize,
BTreeEntry,
BTreeMap,
BTreeSet,
Expand Down Expand Up @@ -215,12 +231,15 @@ symbols! {
Rc,
Ready,
Receiver,
Relaxed,
Release,
Result,
Return,
Right,
RustcDecodable,
RustcEncodable,
Send,
SeqCst,
Some,
StructuralEq,
StructuralPartialEq,
Expand Down Expand Up @@ -311,6 +330,8 @@ symbols! {
assume_init,
async_await,
async_closure,
atomic,
atomic_mod,
atomics,
att_syntax,
attr,
Expand Down Expand Up @@ -390,8 +411,12 @@ symbols! {
coerce_unsized,
cold,
column,
compare_and_swap,
compare_exchange,
compare_exchange_weak,
compile_error,
compiler_builtins,
compiler_fence,
concat,
concat_idents,
conservative_impl_trait,
Expand Down Expand Up @@ -575,6 +600,8 @@ symbols! {
fadd_fast,
fdiv_fast,
feature,
fence,
fetch_update,
ffi,
ffi_const,
ffi_pure,
Expand Down Expand Up @@ -728,6 +755,7 @@ symbols! {
lint_reasons,
literal,
llvm_asm,
load,
local,
local_inner_macros,
log10f32,
Expand Down Expand Up @@ -1217,6 +1245,7 @@ symbols! {
stmt,
stmt_expr_attributes,
stop_after_dataflow,
store,
str,
str_alloc,
string_type,
Expand Down
Loading