Summary

Allow

許可する、可能にする
users to load custom lints into rustc, similar
似ている、同様の
to loadable syntax
文法
extensions.

Motivation

There are many possibilities for user-defined static

静的な
checking:

  • Enforcing correct usage of Servo's JS-managed pointers
  • kballard's use case: checking that rust-lua functions which call
    呼び出し
    longjmp never have destructors on stack variables
  • Enforcing a company or project style guide
  • Detecting common misuses of a library, e.g. expensive or non-idiomatic constructs
    作る、構成体
  • In cryptographic code, annotating which variables contain
    含む
    secrets and then forbidding their use in variable-time operations
    演算、操作
    or memory addressing

Existing project-specific static

静的な
checkers include:

  • A Clang plugin that detects misuse of GLib and GObject
  • A GCC plugin (written in Python!) that detects misuse of the CPython extension API
  • Sparse, which checks Linux kernel code for issues such as mixing up userspace and kernel pointers (often exploitable for privilege escalation)

We should make it easy to build such tools and integrate them with an existing Rust project.

Detailed design
設計(する)

In rustc::lint (which today is rustc::middle::lint):

pub struct Lint { /// An identifier for the lint, written with underscores, /// e.g. "unused_imports". pub name: &'static str, /// Default level for the lint. pub default_level: Level, /// Description of the lint or the issue it detects, /// e.g. "imports that are never used" pub desc: &'static str, } #[macro_export] macro_rules! declare_lint ( ($name:ident, $level:ident, $desc:expr) => ( static $name: &'static ::rustc::lint::Lint = &::rustc::lint::Lint { name: stringify!($name), default_level: ::rustc::lint::$level, desc: $desc, }; )) pub type LintArray = &'static [&'static Lint]; #[macro_export] macro_rules! lint_array ( ($( $lint:expr ),*) => ( { static array: LintArray = &[ $( $lint ),* ]; array } )) pub trait LintPass { fn get_lints(&self) -> LintArray; fn check_item(&mut self, cx: &Context, it: &ast::Item) { } fn check_expr(&mut self, cx: &Context, e: &ast::Expr) { } ... } pub type LintPassObject = Box<LintPass: 'static>;

To define

定義する
a lint:

#![crate_id="lipogram"] #![crate_type="dylib"] #![feature(phase, plugin_registrar)] extern crate syntax; // Load rustc as a plugin to get macros #[phase(plugin, link)] extern crate rustc; use syntax::ast; use syntax::parse::token; use rustc::lint::{Context, LintPass, LintPassObject, LintArray}; use rustc::plugin::Registry; declare_lint!(letter_e, Warn, "forbid use of the letter 'e'") struct Lipogram; impl LintPass for Lipogram { fn get_lints(&self) -> LintArray { lint_array!(letter_e) } fn check_item(&mut self, cx: &Context, it: &ast::Item) { let name = token::get_ident(it.ident); if name.get().contains_char('e') || name.get().contains_char('E') { cx.span_lint(letter_e, it.span, "item name contains the letter 'e'"); } } } #[plugin_registrar] pub fn plugin_registrar(reg: &mut Registry) { reg.register_lint_pass(box Lipogram as LintPassObject); }

A pass which defines

定義する
multiple
複数の
lints will have e.g. lint_array!(deprecated, experimental, unstable).

To use a lint when compiling another crate:

#![feature(phase)] #[phase(plugin)] extern crate lipogram; fn hello() { } fn main() { hello() }

And you will get

test.rs:6:1: 6:15 warning: item name contains the letter 'e', #[warn(letter_e)] on by default test.rs:6 fn hello() { } ^~~~~~~~~~~~~~

Internally, lints are identified by the address of a static

静的な
Lint. This has a number of benefits:

  • The linker takes
    とる
    care of assigning
    代入する
    unique
    一意
    IDs, even with dynamically loaded plugins.
  • A typo writing a lint ID is usually a compiler error, unlike with string IDs.
  • The ability to output a given
    与えられた
    lint is controlled
    制御する
    by the usual visibility mechanism.
    仕組み、機構
    Lints defined
    定義する
    within rustc use the same infrastructure and will simply export their Lints if other parts of the compiler need to output those lints.
  • IDs are small and easy to hash.
  • It's easy to go from an ID to name, description, etc.

User-defined lints are controlled

制御する
through the usual mechanism
仕組み、機構
of attributes and the -A -W -D -F flags to rustc. User-defined lints will show up in -W help if a crate filename is also provided;
与える
otherwise
さもなければ
we append a message suggesting to re-run with a crate filename.

See also the full demo.

Drawbacks

This increases

増加する、昇順の
the amount of code in rustc to implement
実装する
lints, although it makes each individual
個々の、それぞれの
lint much easier to understand in isolation.

Loadable lints produce

産出、産出する
more coupling of user code to rustc internals (with no official stability guarantee,
保証する
of course).

There's no scoping / namespacing of the lint name strings used by attributes and compiler flags. Attempting to register a lint with a duplicate name is an error at registration time.

The use of &'static means that lint plugins can't dynamically generate the set

セットする、集合
of lints based
基となる、基底(の)
on some external resource.

Alternatives

We could provide a more generic mechanism

仕組み、機構
for user-defined AST visitors. This could support other use cases like code transformation. But it would be harder to use, and harder to integrate with the lint infrastructure.

It would be nice to magically find all static

静的な
Lints in a crate, so we don't need get_lints. Is this worth adding
たす
another attribute and another crate metadata type? The plugin::Registry mechanism
仕組み、機構
was meant to avoid
避ける、回避する
such a proliferation of metadata types, but it's not as declarative as I would like.

Unresolved questions

Do we provide guarantees

保証する
about visit order
順序
for a lint, or the order
順序
of multiple
複数の
lints defined
定義する
in the same crate? Some lints may require multiple
複数の
passes.

Should we enforce (while running lints) that each lint printed with span_lint was registered by the corresponding

対応する
LintPass? Users who particularly care can already wrap lints in modules and use visibility to enforce this statically.

Should we separate registering a lint pass from initializing

初期化
/ constructing
作る、構成体
the value implementing
実装する
LintPass? This would support a future where a single
単一の
rustc invocation
呼び出し
can compile multiple
複数の
crates and needs to reset lint state.