Skip to content

Commit 530a73f

Browse files
committed
Prevent invalid LevelSpec values
`LevelSpec` has two related fields, `level` and `lint_id`. This commit makes the fields private (and introduces getters) to ensure they are set together using only valid combinations. The commit also introduces `is_allow` and `is_expect` methods because those are useful.
1 parent f439907 commit 530a73f

23 files changed

Lines changed: 136 additions & 107 deletions

File tree

compiler/rustc_const_eval/src/const_eval/machine.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -739,7 +739,7 @@ impl<'tcx> interpret::Machine<'tcx> for CompileTimeMachine<'tcx> {
739739
rustc_session::lint::builtin::LONG_RUNNING_CONST_EVAL,
740740
hir_id,
741741
)
742-
.level
742+
.level()
743743
.is_error();
744744
let span = ecx.cur_span();
745745
ecx.tcx.emit_node_span_lint(

compiler/rustc_driver_impl/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -729,7 +729,7 @@ fn print_crate_info(
729729
// lint is unstable and feature gate isn't active, don't print
730730
continue;
731731
}
732-
let level = builder.lint_level_spec(lint).level;
732+
let level = builder.lint_level_spec(lint).level();
733733
println_info!("{}={}", lint.name_lower(), level.as_str());
734734
}
735735
}

compiler/rustc_hir_typeck/src/upvar.rs

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2415,11 +2415,8 @@ fn should_do_rust_2021_incompatible_closure_captures_analysis(
24152415
return false;
24162416
}
24172417

2418-
let level = tcx
2419-
.lint_level_spec_at_node(lint::builtin::RUST_2021_INCOMPATIBLE_CLOSURE_CAPTURES, closure_id)
2420-
.level;
2421-
2422-
!matches!(level, lint::Level::Allow)
2418+
!tcx.lint_level_spec_at_node(lint::builtin::RUST_2021_INCOMPATIBLE_CLOSURE_CAPTURES, closure_id)
2419+
.is_allow()
24232420
}
24242421

24252422
/// Return a two string tuple (s1, s2)

compiler/rustc_lint/src/builtin.rs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,8 @@ use crate::lints::{
6060
BuiltinUnreachablePub, BuiltinUnsafe, BuiltinUnstableFeatures, BuiltinUnusedDocComment,
6161
BuiltinUnusedDocCommentSub, BuiltinWhileTrue, EqInternalMethodImplemented, InvalidAsmLabel,
6262
};
63-
use crate::{EarlyContext, EarlyLintPass, LateContext, LateLintPass, Level, LintContext};
63+
use crate::{EarlyContext, EarlyLintPass, LateContext, LateLintPass, LintContext};
64+
6465
declare_lint! {
6566
/// The `while_true` lint detects `while true { }`.
6667
///
@@ -694,9 +695,7 @@ impl<'tcx> LateLintPass<'tcx> for MissingDebugImplementations {
694695
}
695696

696697
// Avoid listing trait impls if the trait is allowed.
697-
let level =
698-
cx.tcx.lint_level_spec_at_node(MISSING_DEBUG_IMPLEMENTATIONS, item.hir_id()).level;
699-
if level == Level::Allow {
698+
if cx.tcx.lint_level_spec_at_node(MISSING_DEBUG_IMPLEMENTATIONS, item.hir_id()).is_allow() {
700699
return;
701700
}
702701

compiler/rustc_lint/src/levels.rs

Lines changed: 26 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ fn lints_that_dont_need_to_run(tcx: TyCtxt<'_>, (): ()) -> UnordSet<LintId> {
129129
let level_spec =
130130
root_map.lint_level_spec_at_node(tcx, LintId::of(lint), hir::CRATE_HIR_ID);
131131
// Only include lints that are allowed at crate root or by default.
132-
matches!(level_spec.level, Level::Allow)
132+
level_spec.is_allow()
133133
|| (matches!(level_spec.src, LintLevelSource::Default)
134134
&& lint.default_level(tcx.sess.edition()) == Level::Allow)
135135
})
@@ -142,7 +142,7 @@ fn lints_that_dont_need_to_run(tcx: TyCtxt<'_>, (): ()) -> UnordSet<LintId> {
142142
// All lints that appear with a non-allow level must be run.
143143
for (_, specs) in map.specs.iter() {
144144
for (lint, level_spec) in specs.iter() {
145-
if !matches!(level_spec.level, Level::Allow) {
145+
if !level_spec.is_allow() {
146146
dont_need_to_run.remove(lint);
147147
}
148148
}
@@ -520,15 +520,15 @@ impl<'s, P: LintLevelsProvider> LintLevelsBuilder<'s, P> {
520520
};
521521
for &id in ids {
522522
// ForceWarn and Forbid cannot be overridden
523-
if let Some(LevelSpec { level: Level::ForceWarn | Level::Forbid, .. }) =
524-
self.current_specs().get(&id)
523+
if let Some(level_spec) = self.current_specs().get(&id)
524+
&& matches!(level_spec.level(), Level::ForceWarn | Level::Forbid)
525525
{
526526
continue;
527527
}
528528

529529
if self.check_gated_lint(id, DUMMY_SP, true) {
530530
let src = LintLevelSource::CommandLine(lint_flag_val, level);
531-
self.insert(id, LevelSpec { level, lint_id: None, src });
531+
self.insert(id, LevelSpec::new(level, None, src));
532532
}
533533
}
534534
}
@@ -537,9 +537,14 @@ impl<'s, P: LintLevelsProvider> LintLevelsBuilder<'s, P> {
537537
/// Attempts to insert the `id` to `LevelSpec` map entry. If unsuccessful
538538
/// (e.g. if a forbid was already inserted on the same scope), then emits a
539539
/// diagnostic with no change to `specs`.
540-
fn insert_spec(&mut self, id: LintId, LevelSpec { level, lint_id, src }: LevelSpec) {
541-
let LevelSpec { level: old_level, src: old_src, .. } =
542-
self.provider.get_lint_level_spec(id.lint, self.sess);
540+
fn insert_spec(&mut self, id: LintId, level_spec: LevelSpec) {
541+
let level = level_spec.level();
542+
let lint_id = level_spec.lint_id();
543+
let src = level_spec.src;
544+
545+
let old_level_spec = self.provider.get_lint_level_spec(id.lint, self.sess);
546+
let old_level = old_level_spec.level();
547+
let old_src = old_level_spec.src;
543548

544549
// Setting to a non-forbid level is an error if the lint previously had
545550
// a forbid level. Note that this is not necessarily true even with a
@@ -622,14 +627,14 @@ impl<'s, P: LintLevelsProvider> LintLevelsBuilder<'s, P> {
622627
match (old_level, level) {
623628
// If the new level is an expectation store it in `ForceWarn`
624629
(Level::ForceWarn, Level::Expect) => {
625-
self.insert(id, LevelSpec { level: Level::ForceWarn, lint_id, src: old_src })
630+
self.insert(id, LevelSpec::new(Level::ForceWarn, lint_id, old_src))
626631
}
627632
// Keep `ForceWarn` level but drop the expectation
628633
(Level::ForceWarn, _) => {
629-
self.insert(id, LevelSpec { level: Level::ForceWarn, lint_id: None, src: old_src })
634+
self.insert(id, LevelSpec::new(Level::ForceWarn, None, old_src))
630635
}
631636
// Set the lint level as normal
632-
_ => self.insert(id, LevelSpec { level, lint_id, src }),
637+
_ => self.insert(id, LevelSpec::new(level, lint_id, src)),
633638
};
634639
}
635640

@@ -644,7 +649,7 @@ impl<'s, P: LintLevelsProvider> LintLevelsBuilder<'s, P> {
644649
if attr.is_automatically_derived_attr() {
645650
self.insert(
646651
LintId::of(SINGLE_USE_LIFETIMES),
647-
LevelSpec { level: Level::Allow, lint_id: None, src: LintLevelSource::Default },
652+
LevelSpec::new(Level::Allow, None, LintLevelSource::Default),
648653
);
649654
continue;
650655
}
@@ -653,7 +658,7 @@ impl<'s, P: LintLevelsProvider> LintLevelsBuilder<'s, P> {
653658
if attr.is_doc_hidden() {
654659
self.insert(
655660
LintId::of(MISSING_DOCS),
656-
LevelSpec { level: Level::Allow, lint_id: None, src: LintLevelSource::Default },
661+
LevelSpec::new(Level::Allow, None, LintLevelSource::Default),
657662
);
658663
continue;
659664
}
@@ -866,7 +871,7 @@ impl<'s, P: LintLevelsProvider> LintLevelsBuilder<'s, P> {
866871
let src = LintLevelSource::Node { name, span: sp, reason };
867872
for &id in ids {
868873
if self.check_gated_lint(id, sp, false) {
869-
self.insert_spec(id, LevelSpec { level, lint_id, src });
874+
self.insert_spec(id, LevelSpec::new(level, lint_id, src));
870875
}
871876
}
872877

@@ -897,20 +902,24 @@ impl<'s, P: LintLevelsProvider> LintLevelsBuilder<'s, P> {
897902
}
898903

899904
if self.lint_added_lints && !is_crate_node {
900-
for (id, &LevelSpec { level, ref src, .. }) in self.current_specs().iter() {
905+
for (id, level_spec) in self.current_specs().iter() {
901906
if !id.lint.crate_level_only {
902907
continue;
903908
}
904909

905-
let LintLevelSource::Node { name: lint_attr_name, span: lint_attr_span, .. } = *src
910+
let LintLevelSource::Node { name: lint_attr_name, span: lint_attr_span, .. } =
911+
level_spec.src
906912
else {
907913
continue;
908914
};
909915

910916
self.emit_span_lint(
911917
UNUSED_ATTRIBUTES,
912918
lint_attr_span.into(),
913-
IgnoredUnlessCrateSpecified { level: level.as_str(), name: lint_attr_name },
919+
IgnoredUnlessCrateSpecified {
920+
level: level_spec.level().as_str(),
921+
name: lint_attr_name,
922+
},
914923
);
915924
// don't set a separate error for every lint in the group
916925
break;

compiler/rustc_lint/src/non_ascii_idents.rs

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -155,18 +155,14 @@ impl EarlyLintPass for NonAsciiIdents {
155155
fn check_crate(&mut self, cx: &EarlyContext<'_>, _: &ast::Crate) {
156156
use std::collections::BTreeMap;
157157

158-
use rustc_session::lint::Level;
159158
use rustc_span::Span;
160159
use unicode_security::GeneralSecurityProfile;
161160

162-
let check_non_ascii_idents =
163-
cx.builder.lint_level_spec(NON_ASCII_IDENTS).level != Level::Allow;
164-
let check_uncommon_codepoints =
165-
cx.builder.lint_level_spec(UNCOMMON_CODEPOINTS).level != Level::Allow;
166-
let check_confusable_idents =
167-
cx.builder.lint_level_spec(CONFUSABLE_IDENTS).level != Level::Allow;
161+
let check_non_ascii_idents = !cx.builder.lint_level_spec(NON_ASCII_IDENTS).is_allow();
162+
let check_uncommon_codepoints = !cx.builder.lint_level_spec(UNCOMMON_CODEPOINTS).is_allow();
163+
let check_confusable_idents = !cx.builder.lint_level_spec(CONFUSABLE_IDENTS).is_allow();
168164
let check_mixed_script_confusables =
169-
cx.builder.lint_level_spec(MIXED_SCRIPT_CONFUSABLES).level != Level::Allow;
165+
!cx.builder.lint_level_spec(MIXED_SCRIPT_CONFUSABLES).is_allow();
170166

171167
if !check_non_ascii_idents
172168
&& !check_uncommon_codepoints

compiler/rustc_metadata/src/creader.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -332,7 +332,7 @@ impl CStore {
332332
lint::builtin::UNUSED_CRATE_DEPENDENCIES,
333333
rustc_hir::CRATE_HIR_ID,
334334
)
335-
.level;
335+
.level();
336336
if level != lint::Level::Allow {
337337
let unused_externs =
338338
self.unused_externs.iter().map(|ident| ident.to_ident_string()).collect::<Vec<_>>();

compiler/rustc_middle/src/lint.rs

Lines changed: 49 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -56,11 +56,56 @@ impl LintLevelSource {
5656
/// Convenience helper for things that are frequently used together.
5757
#[derive(Copy, Clone, Debug, StableHash, Encodable, Decodable)]
5858
pub struct LevelSpec {
59-
pub level: Level,
60-
pub lint_id: Option<LintExpectationId>,
59+
// This field *must* be private. It must be set in tandem with `lint_id`, only in
60+
// `LevelSpec::new`, because only certain `level`/`lint_id` combinations are valid. See
61+
// `LevelSpec::new` for those combinations.
62+
//
63+
// If you are thinking right now that `level` and `lint_id` should be combined into a single
64+
// type that excludes the invalid combinations, that's a reasonable thought, but in practice
65+
// it's painful because `level` needs to be used by itself, without `lint_id`, in many places.
66+
// Making the fields private prevents invalid combinations while retaining the flexibility of
67+
// two separate fields.
68+
level: Level,
69+
70+
// This field *must* be private. See the comment on `level`.
71+
lint_id: Option<LintExpectationId>,
72+
6173
pub src: LintLevelSource,
6274
}
6375

76+
impl LevelSpec {
77+
// Panics if an invalid `level`/`lint_id` combination is given.
78+
pub fn new(
79+
level: Level,
80+
lint_id: Option<LintExpectationId>,
81+
src: LintLevelSource,
82+
) -> LevelSpec {
83+
match (level, lint_id) {
84+
(Level::Allow | Level::Warn | Level::Deny | Level::Forbid, None) => {}
85+
(Level::Expect, Some(_)) => {}
86+
(Level::ForceWarn, _) => {}
87+
_ => panic!("invalid level/lint_id combination"),
88+
}
89+
LevelSpec { level, lint_id, src }
90+
}
91+
92+
pub fn level(self) -> Level {
93+
self.level
94+
}
95+
96+
pub fn is_allow(self) -> bool {
97+
self.level == Level::Allow
98+
}
99+
100+
pub fn is_expect(self) -> bool {
101+
self.level == Level::Expect
102+
}
103+
104+
pub fn lint_id(self) -> Option<LintExpectationId> {
105+
self.lint_id
106+
}
107+
}
108+
64109
/// Return type for the `shallow_lint_levels_on` query.
65110
///
66111
/// This map represents the set of allowed lints and allowance levels given
@@ -82,10 +127,8 @@ pub fn reveal_actual_level_spec(
82127
let level_spec = probe_for_lint_level_spec(lint);
83128

84129
// If `level` is none then we actually assume the default level for this lint.
85-
let mut level_spec = level_spec.unwrap_or_else(|| LevelSpec {
86-
level: lint.lint.default_level(sess.edition()),
87-
lint_id: None,
88-
src: LintLevelSource::Default,
130+
let mut level_spec = level_spec.unwrap_or_else(|| {
131+
LevelSpec::new(lint.lint.default_level(sess.edition()), None, LintLevelSource::Default)
89132
});
90133

91134
// If we're about to issue a warning, check at the last minute for any

compiler/rustc_middle/src/middle/stability.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ use rustc_macros::{Decodable, Encodable, StableHash, Subdiagnostic};
1313
use rustc_session::Session;
1414
use rustc_session::errors::feature_err_issue;
1515
use rustc_session::lint::builtin::{DEPRECATED, DEPRECATED_IN_FUTURE};
16-
use rustc_session::lint::{DeprecatedSinceKind, Level, Lint};
16+
use rustc_session::lint::{DeprecatedSinceKind, Lint};
1717
use rustc_span::{Span, Symbol, sym};
1818
use tracing::debug;
1919

@@ -234,7 +234,7 @@ fn late_report_deprecation(
234234
// Calculating message for lint involves calling `self.def_path_str`,
235235
// which will by default invoke the expensive `visible_parent_map` query.
236236
// Skip all that work if the lint is allowed anyway.
237-
if tcx.lint_level_spec_at_node(lint, hir_id).level == Level::Allow {
237+
if tcx.lint_level_spec_at_node(lint, hir_id).is_allow() {
238238
return;
239239
}
240240

compiler/rustc_mir_build/src/check_unsafety.rs

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ use rustc_middle::thir::visit::Visitor;
1212
use rustc_middle::thir::*;
1313
use rustc_middle::ty::print::with_no_trimmed_paths;
1414
use rustc_middle::ty::{self, Ty, TyCtxt};
15-
use rustc_session::lint::Level;
1615
use rustc_session::lint::builtin::{DEPRECATED_SAFE_2024, UNSAFE_OP_IN_UNSAFE_FN, UNUSED_UNSAFE};
1716
use rustc_span::def_id::{DefId, LocalDefId};
1817
use rustc_span::{Span, Symbol};
@@ -175,8 +174,7 @@ impl<'tcx> UnsafetyVisitor<'_, 'tcx> {
175174

176175
/// Whether the `unsafe_op_in_unsafe_fn` lint is `allow`ed at the current HIR node.
177176
fn unsafe_op_in_unsafe_fn_allowed(&self) -> bool {
178-
self.tcx.lint_level_spec_at_node(UNSAFE_OP_IN_UNSAFE_FN, self.hir_context).level
179-
== Level::Allow
177+
self.tcx.lint_level_spec_at_node(UNSAFE_OP_IN_UNSAFE_FN, self.hir_context).is_allow()
180178
}
181179

182180
/// Handle closures/coroutines/inline-consts, which is unsafecked with their parent body.
@@ -234,10 +232,7 @@ impl<'a, 'tcx> Visitor<'a, 'tcx> for UnsafetyVisitor<'a, 'tcx> {
234232
});
235233
}
236234
BlockSafety::ExplicitUnsafe(hir_id) => {
237-
let used = matches!(
238-
self.tcx.lint_level_spec_at_node(UNUSED_UNSAFE, hir_id).level,
239-
Level::Allow
240-
);
235+
let used = self.tcx.lint_level_spec_at_node(UNUSED_UNSAFE, hir_id).is_allow();
241236
self.in_safety_context(
242237
SafetyContext::UnsafeBlock {
243238
span: block.span,

0 commit comments

Comments
 (0)