aboutsummaryrefslogtreecommitdiffstats
path: root/support/crown/src/trace_in_no_trace.rs
blob: 917a2c7459ba3e137f90e589c4dec8f83d78f9e4 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */

use rustc_ast::ast::{AttrKind, Attribute};
use rustc_ast::token::TokenKind;
use rustc_ast::tokenstream::TokenTree;
use rustc_ast::AttrArgs;
use rustc_error_messages::MultiSpan;
use rustc_hir::{self as hir};
use rustc_lint::{LateContext, LateLintPass, LintContext, LintPass, LintStore};
use rustc_middle::ty;
use rustc_session::declare_tool_lint;
use rustc_span::symbol::Symbol;

use crate::common::{get_local_trait_def_id, get_trait_def_id, implements_trait};
use crate::symbols;

declare_tool_lint! {
    pub crown::TRACE_IN_NO_TRACE,
    Deny,
    "Warn and report incorrect usage of Traceable (jsmanaged) objects in must_not_have_traceable marked wrappers"
}

declare_tool_lint! {
    pub crown::EMPTY_TRACE_IN_NO_TRACE,
    Warn,
    "Warn about usage of empty Traceable objects in must_not_have_traceable marked wrappers"
}

const EMPTY_TRACE_IN_NO_TRACE_MSG: &str =
    "must_not_have_traceable marked wrapper is not needed for types that implements \
empty Traceable (like primitive types). Consider removing the wrapper.";

pub fn register(lint_store: &mut LintStore) {
    let symbols = Symbols::new();
    lint_store.register_lints(&[&TRACE_IN_NO_TRACE, &EMPTY_TRACE_IN_NO_TRACE]);
    lint_store.register_late_pass(move |_| Box::new(NotracePass::new(symbols.clone())));
}

/// Lint for ensuring safe usage of NoTrace wrappers
///
/// This lint (disable with `-A trace-in-no-trace`/`#[allow(trace_in_no_trace)]`) ensures that
/// wrappers marked with must_not_have_traceable(i: usize) only stores
/// non-jsmanaged (DOES NOT implement JSTraceble) type in i-th generic
///
/// For example usage look at the tests
pub(crate) struct NotracePass {
    symbols: Symbols,
}

impl NotracePass {
    pub(crate) fn new(symbols: Symbols) -> Self {
        Self { symbols }
    }
}

impl LintPass for NotracePass {
    fn name(&self) -> &'static str {
        "ServoNotracePass"
    }
}

fn get_must_not_have_traceable(sym: &Symbols, attrs: &[Attribute]) -> Option<usize> {
    attrs
        .iter()
        .find(|attr| {
            matches!(
                &attr.kind,
                AttrKind::Normal(normal)
                if normal.item.path.segments.len() == 3 &&
                normal.item.path.segments[0].ident.name == sym.crown &&
                normal.item.path.segments[1].ident.name == sym.trace_in_no_trace_lint &&
                normal.item.path.segments[2].ident.name == sym.must_not_have_traceable
            )
        })
        .map(|x| match &x.get_normal_item().args {
            AttrArgs::Empty => 0,
            AttrArgs::Delimited(a) => match a
                .tokens
                .trees()
                .next()
                .expect("Arguments not found for must_not_have_traceable")
            {
                TokenTree::Token(tok, _) => match tok.kind {
                    TokenKind::Literal(lit) => lit.symbol.as_str().parse().unwrap(),
                    _ => panic!("must_not_have_traceable expected integer literal here"),
                },
                TokenTree::Delimited(_, _, _) => {
                    todo!("must_not_have_traceable does not support multiple notraceable positions")
                },
            },
            _ => {
                panic!("must_not_have_traceable does not support key-value arguments")
            },
        })
}

fn is_jstraceable<'tcx>(cx: &LateContext<'tcx>, ty: ty::Ty<'tcx>) -> bool {
    // TODO(sagudev): get_trait_def_id is expensive, use lazy and cache it for whole pass
    if let Some(trait_id) = get_trait_def_id(cx, &["mozjs", "gc", "Traceable"]) {
        return implements_trait(cx, ty, trait_id, &[]);
    }
    // when running tests
    if let Some(trait_id) = get_local_trait_def_id(cx, "JSTraceable") {
        return implements_trait(cx, ty, trait_id, &[]);
    }
    panic!("JSTraceable not found");
}

/// Gives warrning or errors for incorect usage of NoTrace like `NoTrace<impl Traceable>`.
fn incorrect_no_trace<'tcx, I: Into<MultiSpan> + Copy>(
    sym: &'_ Symbols,
    cx: &LateContext<'tcx>,
    ty: ty::Ty<'tcx>,
    span: I,
) {
    let mut walker = ty.walk();
    while let Some(generic_arg) = walker.next() {
        let t = match generic_arg.unpack() {
            rustc_middle::ty::GenericArgKind::Type(t) => t,
            _ => {
                walker.skip_current_subtree();
                continue;
            },
        };
        let recur_into_subtree = match t.kind() {
            ty::Adt(did, substs) => {
                if let Some(pos) =
                    get_must_not_have_traceable(sym, &cx.tcx.get_attrs_unchecked(did.did()))
                {
                    let inner = substs.type_at(pos);
                    if inner.is_primitive_ty() {
                        cx.lint(
                            EMPTY_TRACE_IN_NO_TRACE,
                            EMPTY_TRACE_IN_NO_TRACE_MSG,
                            |lint| lint.set_span(span),
                        )
                    } else if is_jstraceable(cx, inner) {
                        cx.lint(
                            TRACE_IN_NO_TRACE,
                            format!(
                                "must_not_have_traceable marked wrapper must not have \
jsmanaged inside on {pos}-th position. Consider removing the wrapper."
                            ),
                            |lint| lint.set_span(span),
                        )
                    }
                    false
                } else {
                    true
                }
            },
            _ => !t.is_primitive_ty(),
        };
        if !recur_into_subtree {
            walker.skip_current_subtree();
        }
    }
}

// NoTrace correct usage of NoTrace must only be checked on Struct (item) and Enums (variants)
// as these are the only ones that are actually traced
impl<'tcx> LateLintPass<'tcx> for NotracePass {
    fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::Item) {
        // TODO: better performance if we limit with lint attr???
        /*let attrs = cx.tcx.hir().attrs(item.hir_id());
        if has_lint_attr(&self.symbols, &attrs, self.symbols.must_root) {
            return;
        }*/
        if let hir::ItemKind::Struct(def, ..) = &item.kind {
            for ref field in def.fields() {
                let field_type = cx.tcx.type_of(field.def_id);
                incorrect_no_trace(&self.symbols, cx, field_type.skip_binder(), field.span);
            }
        }
    }

    fn check_variant(&mut self, cx: &LateContext, var: &hir::Variant) {
        match var.data {
            hir::VariantData::Tuple(fields, ..) => {
                for field in fields {
                    let field_type = cx.tcx.type_of(field.def_id);
                    incorrect_no_trace(&self.symbols, cx, field_type.skip_binder(), field.ty.span);
                }
            },
            _ => (), // Struct variants already caught by check_struct_def
        }
    }
}

symbols! {
    crown
    trace_in_no_trace_lint
    must_not_have_traceable
}