rustc_attr_parsing/attributes/
codegen_attrs.rs

1use rustc_hir::attrs::{CoverageAttrKind, OptimizeAttr, SanitizerSet, UsedBy};
2use rustc_session::parse::feature_err;
3
4use super::prelude::*;
5use crate::session_diagnostics::{
6    NakedFunctionIncompatibleAttribute, NullOnExport, NullOnObjcClass, NullOnObjcSelector,
7    ObjcClassExpectedStringLiteral, ObjcSelectorExpectedStringLiteral,
8};
9
10pub(crate) struct OptimizeParser;
11
12impl<S: Stage> SingleAttributeParser<S> for OptimizeParser {
13    const PATH: &[Symbol] = &[sym::optimize];
14    const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepOutermost;
15    const ON_DUPLICATE: OnDuplicate<S> = OnDuplicate::WarnButFutureError;
16    const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[
17        Allow(Target::Fn),
18        Allow(Target::Closure),
19        Allow(Target::Method(MethodKind::Trait { body: true })),
20        Allow(Target::Method(MethodKind::TraitImpl)),
21        Allow(Target::Method(MethodKind::Inherent)),
22    ]);
23    const TEMPLATE: AttributeTemplate = template!(List: &["size", "speed", "none"]);
24
25    fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser<'_>) -> Option<AttributeKind> {
26        let Some(list) = args.list() else {
27            cx.expected_list(cx.attr_span);
28            return None;
29        };
30
31        let Some(single) = list.single() else {
32            cx.expected_single_argument(list.span);
33            return None;
34        };
35
36        let res = match single.meta_item().and_then(|i| i.path().word().map(|i| i.name)) {
37            Some(sym::size) => OptimizeAttr::Size,
38            Some(sym::speed) => OptimizeAttr::Speed,
39            Some(sym::none) => OptimizeAttr::DoNotOptimize,
40            _ => {
41                cx.expected_specific_argument(single.span(), &[sym::size, sym::speed, sym::none]);
42                OptimizeAttr::Default
43            }
44        };
45
46        Some(AttributeKind::Optimize(res, cx.attr_span))
47    }
48}
49
50pub(crate) struct ColdParser;
51
52impl<S: Stage> NoArgsAttributeParser<S> for ColdParser {
53    const PATH: &[Symbol] = &[sym::cold];
54    const ON_DUPLICATE: OnDuplicate<S> = OnDuplicate::Warn;
55    const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowListWarnRest(&[
56        Allow(Target::Fn),
57        Allow(Target::Method(MethodKind::Trait { body: true })),
58        Allow(Target::Method(MethodKind::TraitImpl)),
59        Allow(Target::Method(MethodKind::Trait { body: false })),
60        Allow(Target::Method(MethodKind::Inherent)),
61        Allow(Target::ForeignFn),
62        Allow(Target::Closure),
63    ]);
64    const CREATE: fn(Span) -> AttributeKind = AttributeKind::Cold;
65}
66
67pub(crate) struct CoverageParser;
68
69impl<S: Stage> SingleAttributeParser<S> for CoverageParser {
70    const PATH: &[Symbol] = &[sym::coverage];
71    const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepOutermost;
72    const ON_DUPLICATE: OnDuplicate<S> = OnDuplicate::Error;
73    const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[
74        Allow(Target::Fn),
75        Allow(Target::Closure),
76        Allow(Target::Method(MethodKind::Trait { body: true })),
77        Allow(Target::Method(MethodKind::TraitImpl)),
78        Allow(Target::Method(MethodKind::Inherent)),
79        Allow(Target::Impl { of_trait: true }),
80        Allow(Target::Impl { of_trait: false }),
81        Allow(Target::Mod),
82        Allow(Target::Crate),
83    ]);
84    const TEMPLATE: AttributeTemplate = template!(OneOf: &[sym::off, sym::on]);
85
86    fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser<'_>) -> Option<AttributeKind> {
87        let Some(args) = args.list() else {
88            cx.expected_specific_argument_and_list(cx.attr_span, &[sym::on, sym::off]);
89            return None;
90        };
91
92        let Some(arg) = args.single() else {
93            cx.expected_single_argument(args.span);
94            return None;
95        };
96
97        let fail_incorrect_argument =
98            |span| cx.expected_specific_argument(span, &[sym::on, sym::off]);
99
100        let Some(arg) = arg.meta_item() else {
101            fail_incorrect_argument(args.span);
102            return None;
103        };
104
105        let kind = match arg.path().word_sym() {
106            Some(sym::off) => CoverageAttrKind::Off,
107            Some(sym::on) => CoverageAttrKind::On,
108            None | Some(_) => {
109                fail_incorrect_argument(arg.span());
110                return None;
111            }
112        };
113
114        Some(AttributeKind::Coverage(cx.attr_span, kind))
115    }
116}
117
118pub(crate) struct ExportNameParser;
119
120impl<S: Stage> SingleAttributeParser<S> for ExportNameParser {
121    const PATH: &[rustc_span::Symbol] = &[sym::export_name];
122    const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepInnermost;
123    const ON_DUPLICATE: OnDuplicate<S> = OnDuplicate::WarnButFutureError;
124    const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[
125        Allow(Target::Static),
126        Allow(Target::Fn),
127        Allow(Target::Method(MethodKind::Inherent)),
128        Allow(Target::Method(MethodKind::Trait { body: true })),
129        Allow(Target::Method(MethodKind::TraitImpl)),
130        Warn(Target::Field),
131        Warn(Target::Arm),
132        Warn(Target::MacroDef),
133        Warn(Target::MacroCall),
134    ]);
135    const TEMPLATE: AttributeTemplate = template!(NameValueStr: "name");
136
137    fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser<'_>) -> Option<AttributeKind> {
138        let Some(nv) = args.name_value() else {
139            cx.expected_name_value(cx.attr_span, None);
140            return None;
141        };
142        let Some(name) = nv.value_as_str() else {
143            cx.expected_string_literal(nv.value_span, Some(nv.value_as_lit()));
144            return None;
145        };
146        if name.as_str().contains('\0') {
147            // `#[export_name = ...]` will be converted to a null-terminated string,
148            // so it may not contain any null characters.
149            cx.emit_err(NullOnExport { span: cx.attr_span });
150            return None;
151        }
152        Some(AttributeKind::ExportName { name, span: cx.attr_span })
153    }
154}
155
156pub(crate) struct ObjcClassParser;
157
158impl<S: Stage> SingleAttributeParser<S> for ObjcClassParser {
159    const PATH: &[rustc_span::Symbol] = &[sym::rustc_objc_class];
160    const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepInnermost;
161    const ON_DUPLICATE: OnDuplicate<S> = OnDuplicate::Error;
162    const ALLOWED_TARGETS: AllowedTargets =
163        AllowedTargets::AllowList(&[Allow(Target::ForeignStatic)]);
164    const TEMPLATE: AttributeTemplate = template!(NameValueStr: "ClassName");
165
166    fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser<'_>) -> Option<AttributeKind> {
167        let Some(nv) = args.name_value() else {
168            cx.expected_name_value(cx.attr_span, None);
169            return None;
170        };
171        let Some(classname) = nv.value_as_str() else {
172            // `#[rustc_objc_class = ...]` is expected to be used as an implementatioin detail
173            // inside a standard library macro, but `cx.expected_string_literal` exposes too much.
174            // Use a custom error message instead.
175            cx.emit_err(ObjcClassExpectedStringLiteral { span: nv.value_span });
176            return None;
177        };
178        if classname.as_str().contains('\0') {
179            // `#[rustc_objc_class = ...]` will be converted to a null-terminated string,
180            // so it may not contain any null characters.
181            cx.emit_err(NullOnObjcClass { span: nv.value_span });
182            return None;
183        }
184        Some(AttributeKind::ObjcClass { classname, span: cx.attr_span })
185    }
186}
187
188pub(crate) struct ObjcSelectorParser;
189
190impl<S: Stage> SingleAttributeParser<S> for ObjcSelectorParser {
191    const PATH: &[rustc_span::Symbol] = &[sym::rustc_objc_selector];
192    const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepInnermost;
193    const ON_DUPLICATE: OnDuplicate<S> = OnDuplicate::Error;
194    const ALLOWED_TARGETS: AllowedTargets =
195        AllowedTargets::AllowList(&[Allow(Target::ForeignStatic)]);
196    const TEMPLATE: AttributeTemplate = template!(NameValueStr: "methodName");
197
198    fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser<'_>) -> Option<AttributeKind> {
199        let Some(nv) = args.name_value() else {
200            cx.expected_name_value(cx.attr_span, None);
201            return None;
202        };
203        let Some(methname) = nv.value_as_str() else {
204            // `#[rustc_objc_selector = ...]` is expected to be used as an implementatioin detail
205            // inside a standard library macro, but `cx.expected_string_literal` exposes too much.
206            // Use a custom error message instead.
207            cx.emit_err(ObjcSelectorExpectedStringLiteral { span: nv.value_span });
208            return None;
209        };
210        if methname.as_str().contains('\0') {
211            // `#[rustc_objc_selector = ...]` will be converted to a null-terminated string,
212            // so it may not contain any null characters.
213            cx.emit_err(NullOnObjcSelector { span: nv.value_span });
214            return None;
215        }
216        Some(AttributeKind::ObjcSelector { methname, span: cx.attr_span })
217    }
218}
219
220#[derive(Default)]
221pub(crate) struct NakedParser {
222    span: Option<Span>,
223}
224
225impl<S: Stage> AttributeParser<S> for NakedParser {
226    const ATTRIBUTES: AcceptMapping<Self, S> =
227        &[(&[sym::naked], template!(Word), |this, cx, args| {
228            if let Err(span) = args.no_args() {
229                cx.expected_no_args(span);
230                return;
231            }
232
233            if let Some(earlier) = this.span {
234                let span = cx.attr_span;
235                cx.warn_unused_duplicate(earlier, span);
236            } else {
237                this.span = Some(cx.attr_span);
238            }
239        })];
240    const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[
241        Allow(Target::Fn),
242        Allow(Target::Method(MethodKind::Inherent)),
243        Allow(Target::Method(MethodKind::Trait { body: true })),
244        Allow(Target::Method(MethodKind::TraitImpl)),
245        Warn(Target::MacroCall),
246    ]);
247
248    fn finalize(self, cx: &FinalizeContext<'_, '_, S>) -> Option<AttributeKind> {
249        // FIXME(jdonszelmann): upgrade this list to *parsed* attributes
250        // once all of these have parsed forms. That'd make the check much nicer...
251        //
252        // many attributes don't make sense in combination with #[naked].
253        // Notable attributes that are incompatible with `#[naked]` are:
254        //
255        // * `#[inline]`
256        // * `#[track_caller]`
257        // * `#[test]`, `#[ignore]`, `#[should_panic]`
258        //
259        // NOTE: when making changes to this list, check that `error_codes/E0736.md` remains
260        // accurate.
261        const ALLOW_LIST: &[rustc_span::Symbol] = &[
262            // conditional compilation
263            sym::cfg_trace,
264            sym::cfg_attr_trace,
265            // testing (allowed here so better errors can be generated in `rustc_builtin_macros::test`)
266            sym::test,
267            sym::ignore,
268            sym::should_panic,
269            sym::bench,
270            // diagnostics
271            sym::allow,
272            sym::warn,
273            sym::deny,
274            sym::forbid,
275            sym::deprecated,
276            sym::must_use,
277            // abi, linking and FFI
278            sym::cold,
279            sym::export_name,
280            sym::link_section,
281            sym::linkage,
282            sym::no_mangle,
283            sym::instruction_set,
284            sym::repr,
285            sym::rustc_std_internal_symbol,
286            // FIXME(#82232, #143834): temporarily renamed to mitigate `#[align]` nameres ambiguity
287            sym::rustc_align,
288            sym::rustc_align_static,
289            // obviously compatible with self
290            sym::naked,
291            // documentation
292            sym::doc,
293        ];
294
295        let span = self.span?;
296
297        // only if we found a naked attribute do we do the somewhat expensive check
298        'outer: for other_attr in cx.all_attrs {
299            for allowed_attr in ALLOW_LIST {
300                if other_attr.segments().next().is_some_and(|i| cx.tools.contains(&i.name)) {
301                    // effectively skips the error message  being emitted below
302                    // if it's a tool attribute
303                    continue 'outer;
304                }
305                if other_attr.word_is(*allowed_attr) {
306                    // effectively skips the error message  being emitted below
307                    // if its an allowed attribute
308                    continue 'outer;
309                }
310
311                if other_attr.word_is(sym::target_feature) {
312                    if !cx.features().naked_functions_target_feature() {
313                        feature_err(
314                            &cx.sess(),
315                            sym::naked_functions_target_feature,
316                            other_attr.span(),
317                            "`#[target_feature(/* ... */)]` is currently unstable on `#[naked]` functions",
318                        ).emit();
319                    }
320
321                    continue 'outer;
322                }
323            }
324
325            cx.emit_err(NakedFunctionIncompatibleAttribute {
326                span: other_attr.span(),
327                naked_span: span,
328                attr: other_attr.get_attribute_path().to_string(),
329            });
330        }
331
332        Some(AttributeKind::Naked(span))
333    }
334}
335
336pub(crate) struct TrackCallerParser;
337impl<S: Stage> NoArgsAttributeParser<S> for TrackCallerParser {
338    const PATH: &[Symbol] = &[sym::track_caller];
339    const ON_DUPLICATE: OnDuplicate<S> = OnDuplicate::Warn;
340    const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[
341        Allow(Target::Fn),
342        Allow(Target::Method(MethodKind::Inherent)),
343        Allow(Target::Method(MethodKind::Trait { body: true })),
344        Allow(Target::Method(MethodKind::TraitImpl)),
345        Allow(Target::Method(MethodKind::Trait { body: false })),
346        Allow(Target::ForeignFn),
347        Allow(Target::Closure),
348        Warn(Target::MacroDef),
349        Warn(Target::Arm),
350        Warn(Target::Field),
351        Warn(Target::MacroCall),
352    ]);
353    const CREATE: fn(Span) -> AttributeKind = AttributeKind::TrackCaller;
354}
355
356pub(crate) struct NoMangleParser;
357impl<S: Stage> NoArgsAttributeParser<S> for NoMangleParser {
358    const PATH: &[Symbol] = &[sym::no_mangle];
359    const ON_DUPLICATE: OnDuplicate<S> = OnDuplicate::Warn;
360    const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowListWarnRest(&[
361        Allow(Target::Fn),
362        Allow(Target::Static),
363        Allow(Target::Method(MethodKind::Inherent)),
364        Allow(Target::Method(MethodKind::TraitImpl)),
365    ]);
366    const CREATE: fn(Span) -> AttributeKind = AttributeKind::NoMangle;
367}
368
369#[derive(Default)]
370pub(crate) struct UsedParser {
371    first_compiler: Option<Span>,
372    first_linker: Option<Span>,
373    first_default: Option<Span>,
374}
375
376// A custom `AttributeParser` is used rather than a Simple attribute parser because
377// - Specifying two `#[used]` attributes is a warning (but will be an error in the future)
378// - But specifying two conflicting attributes: `#[used(compiler)]` and `#[used(linker)]` is already an error today
379// We can change this to a Simple parser once the warning becomes an error
380impl<S: Stage> AttributeParser<S> for UsedParser {
381    const ATTRIBUTES: AcceptMapping<Self, S> = &[(
382        &[sym::used],
383        template!(Word, List: &["compiler", "linker"]),
384        |group: &mut Self, cx, args| {
385            let used_by = match args {
386                ArgParser::NoArgs => UsedBy::Default,
387                ArgParser::List(list) => {
388                    let Some(l) = list.single() else {
389                        cx.expected_single_argument(list.span);
390                        return;
391                    };
392
393                    match l.meta_item().and_then(|i| i.path().word_sym()) {
394                        Some(sym::compiler) => {
395                            if !cx.features().used_with_arg() {
396                                feature_err(
397                                    &cx.sess(),
398                                    sym::used_with_arg,
399                                    cx.attr_span,
400                                    "`#[used(compiler)]` is currently unstable",
401                                )
402                                .emit();
403                            }
404                            UsedBy::Compiler
405                        }
406                        Some(sym::linker) => {
407                            if !cx.features().used_with_arg() {
408                                feature_err(
409                                    &cx.sess(),
410                                    sym::used_with_arg,
411                                    cx.attr_span,
412                                    "`#[used(linker)]` is currently unstable",
413                                )
414                                .emit();
415                            }
416                            UsedBy::Linker
417                        }
418                        _ => {
419                            cx.expected_specific_argument(l.span(), &[sym::compiler, sym::linker]);
420                            return;
421                        }
422                    }
423                }
424                ArgParser::NameValue(_) => return,
425            };
426
427            let attr_span = cx.attr_span;
428
429            // `#[used]` is interpreted as `#[used(linker)]` (though depending on target OS the
430            // circumstances are more complicated). While we're checking `used_by`, also report
431            // these cross-`UsedBy` duplicates to warn.
432            let target = match used_by {
433                UsedBy::Compiler => &mut group.first_compiler,
434                UsedBy::Linker => {
435                    if let Some(prev) = group.first_default {
436                        cx.warn_unused_duplicate(prev, attr_span);
437                        return;
438                    }
439                    &mut group.first_linker
440                }
441                UsedBy::Default => {
442                    if let Some(prev) = group.first_linker {
443                        cx.warn_unused_duplicate(prev, attr_span);
444                        return;
445                    }
446                    &mut group.first_default
447                }
448            };
449
450            if let Some(prev) = *target {
451                cx.warn_unused_duplicate(prev, attr_span);
452            } else {
453                *target = Some(attr_span);
454            }
455        },
456    )];
457    const ALLOWED_TARGETS: AllowedTargets =
458        AllowedTargets::AllowList(&[Allow(Target::Static), Warn(Target::MacroCall)]);
459
460    fn finalize(self, _cx: &FinalizeContext<'_, '_, S>) -> Option<AttributeKind> {
461        // If a specific form of `used` is specified, it takes precedence over generic `#[used]`.
462        // If both `linker` and `compiler` are specified, use `linker`.
463        Some(match (self.first_compiler, self.first_linker, self.first_default) {
464            (_, Some(span), _) => AttributeKind::Used { used_by: UsedBy::Linker, span },
465            (Some(span), _, _) => AttributeKind::Used { used_by: UsedBy::Compiler, span },
466            (_, _, Some(span)) => AttributeKind::Used { used_by: UsedBy::Default, span },
467            (None, None, None) => return None,
468        })
469    }
470}
471
472fn parse_tf_attribute<'c, S: Stage>(
473    cx: &'c mut AcceptContext<'_, '_, S>,
474    args: &'c ArgParser<'_>,
475) -> impl IntoIterator<Item = (Symbol, Span)> + 'c {
476    let mut features = Vec::new();
477    let ArgParser::List(list) = args else {
478        cx.expected_list(cx.attr_span);
479        return features;
480    };
481    if list.is_empty() {
482        cx.warn_empty_attribute(cx.attr_span);
483        return features;
484    }
485    for item in list.mixed() {
486        let Some(name_value) = item.meta_item() else {
487            cx.expected_name_value(item.span(), Some(sym::enable));
488            return features;
489        };
490
491        // Validate name
492        let Some(name) = name_value.path().word_sym() else {
493            cx.expected_name_value(name_value.path().span(), Some(sym::enable));
494            return features;
495        };
496        if name != sym::enable {
497            cx.expected_name_value(name_value.path().span(), Some(sym::enable));
498            return features;
499        }
500
501        // Use value
502        let Some(name_value) = name_value.args().name_value() else {
503            cx.expected_name_value(item.span(), Some(sym::enable));
504            return features;
505        };
506        let Some(value_str) = name_value.value_as_str() else {
507            cx.expected_string_literal(name_value.value_span, Some(name_value.value_as_lit()));
508            return features;
509        };
510        for feature in value_str.as_str().split(",") {
511            features.push((Symbol::intern(feature), item.span()));
512        }
513    }
514    features
515}
516
517pub(crate) struct TargetFeatureParser;
518
519impl<S: Stage> CombineAttributeParser<S> for TargetFeatureParser {
520    type Item = (Symbol, Span);
521    const PATH: &[Symbol] = &[sym::target_feature];
522    const CONVERT: ConvertFn<Self::Item> = |items, span| AttributeKind::TargetFeature {
523        features: items,
524        attr_span: span,
525        was_forced: false,
526    };
527    const TEMPLATE: AttributeTemplate = template!(List: &["enable = \"feat1, feat2\""]);
528
529    fn extend<'c>(
530        cx: &'c mut AcceptContext<'_, '_, S>,
531        args: &'c ArgParser<'_>,
532    ) -> impl IntoIterator<Item = Self::Item> + 'c {
533        parse_tf_attribute(cx, args)
534    }
535
536    const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[
537        Allow(Target::Fn),
538        Allow(Target::Method(MethodKind::Inherent)),
539        Allow(Target::Method(MethodKind::Trait { body: true })),
540        Allow(Target::Method(MethodKind::TraitImpl)),
541        Warn(Target::Statement),
542        Warn(Target::Field),
543        Warn(Target::Arm),
544        Warn(Target::MacroDef),
545        Warn(Target::MacroCall),
546    ]);
547}
548
549pub(crate) struct ForceTargetFeatureParser;
550
551impl<S: Stage> CombineAttributeParser<S> for ForceTargetFeatureParser {
552    type Item = (Symbol, Span);
553    const PATH: &[Symbol] = &[sym::force_target_feature];
554    const CONVERT: ConvertFn<Self::Item> = |items, span| AttributeKind::TargetFeature {
555        features: items,
556        attr_span: span,
557        was_forced: true,
558    };
559    const TEMPLATE: AttributeTemplate = template!(List: &["enable = \"feat1, feat2\""]);
560    const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[
561        Allow(Target::Fn),
562        Allow(Target::Method(MethodKind::Inherent)),
563        Allow(Target::Method(MethodKind::Trait { body: true })),
564        Allow(Target::Method(MethodKind::TraitImpl)),
565    ]);
566
567    fn extend<'c>(
568        cx: &'c mut AcceptContext<'_, '_, S>,
569        args: &'c ArgParser<'_>,
570    ) -> impl IntoIterator<Item = Self::Item> + 'c {
571        parse_tf_attribute(cx, args)
572    }
573}
574
575pub(crate) struct SanitizeParser;
576
577impl<S: Stage> SingleAttributeParser<S> for SanitizeParser {
578    const PATH: &[Symbol] = &[sym::sanitize];
579
580    // FIXME: still checked in check_attrs.rs
581    const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(ALL_TARGETS);
582
583    const TEMPLATE: AttributeTemplate = template!(List: &[
584        r#"address = "on|off""#,
585        r#"kernel_address = "on|off""#,
586        r#"cfi = "on|off""#,
587        r#"hwaddress = "on|off""#,
588        r#"kcfi = "on|off""#,
589        r#"memory = "on|off""#,
590        r#"memtag = "on|off""#,
591        r#"shadow_call_stack = "on|off""#,
592        r#"thread = "on|off""#
593    ]);
594
595    const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepOutermost;
596    const ON_DUPLICATE: OnDuplicate<S> = OnDuplicate::Error;
597
598    fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser<'_>) -> Option<AttributeKind> {
599        let Some(list) = args.list() else {
600            cx.expected_list(cx.attr_span);
601            return None;
602        };
603
604        let mut on_set = SanitizerSet::empty();
605        let mut off_set = SanitizerSet::empty();
606
607        for item in list.mixed() {
608            let Some(item) = item.meta_item() else {
609                cx.expected_name_value(item.span(), None);
610                continue;
611            };
612
613            let path = item.path().word_sym();
614            let Some(value) = item.args().name_value() else {
615                cx.expected_name_value(item.span(), path);
616                continue;
617            };
618
619            let mut apply = |s: SanitizerSet| {
620                let is_on = match value.value_as_str() {
621                    Some(sym::on) => true,
622                    Some(sym::off) => false,
623                    Some(_) => {
624                        cx.expected_specific_argument_strings(
625                            value.value_span,
626                            &[sym::on, sym::off],
627                        );
628                        return;
629                    }
630                    None => {
631                        cx.expected_string_literal(value.value_span, Some(value.value_as_lit()));
632                        return;
633                    }
634                };
635
636                if is_on {
637                    on_set |= s;
638                } else {
639                    off_set |= s;
640                }
641            };
642
643            match path {
644                Some(sym::address) | Some(sym::kernel_address) => {
645                    apply(SanitizerSet::ADDRESS | SanitizerSet::KERNELADDRESS)
646                }
647                Some(sym::cfi) => apply(SanitizerSet::CFI),
648                Some(sym::kcfi) => apply(SanitizerSet::KCFI),
649                Some(sym::memory) => apply(SanitizerSet::MEMORY),
650                Some(sym::memtag) => apply(SanitizerSet::MEMTAG),
651                Some(sym::shadow_call_stack) => apply(SanitizerSet::SHADOWCALLSTACK),
652                Some(sym::thread) => apply(SanitizerSet::THREAD),
653                Some(sym::hwaddress) => apply(SanitizerSet::HWADDRESS),
654                _ => {
655                    cx.expected_specific_argument_strings(
656                        item.path().span(),
657                        &[
658                            sym::address,
659                            sym::cfi,
660                            sym::kcfi,
661                            sym::memory,
662                            sym::memtag,
663                            sym::shadow_call_stack,
664                            sym::thread,
665                            sym::hwaddress,
666                        ],
667                    );
668                    continue;
669                }
670            }
671        }
672
673        Some(AttributeKind::Sanitize { on_set, off_set, span: cx.attr_span })
674    }
675}