Skip to content

Commit c327080

Browse files
committed
rustc: add a lint for large enum variants
It can be easy to accidentally bloat the size of an enum by making one variant larger than the others. When this happens, it usually goes unnoticed. This commit adds a lint that can warn when the largest variant in an enum is more than 3 times larger than the second-largest variant. This requires a little bit of rejiggering, because size information is only available in trans, but lint levels are only available in the lint context. It is allow by default because it's pretty noisy, and isn't really *that* undesirable. Closes #10362
1 parent f122ad0 commit c327080

File tree

4 files changed

+181
-41
lines changed

4 files changed

+181
-41
lines changed

src/librustc/middle/lint.rs

+80-38
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ use syntax::parse::token;
7272
use syntax::visit::Visitor;
7373
use syntax::{ast, ast_util, visit};
7474

75-
#[deriving(Clone, Eq, Ord, TotalEq, TotalOrd)]
75+
#[deriving(Clone, Show, Eq, Ord, TotalEq, TotalOrd)]
7676
pub enum Lint {
7777
CTypes,
7878
UnusedImports,
@@ -93,6 +93,7 @@ pub enum Lint {
9393
UnknownFeatures,
9494
UnknownCrateType,
9595
UnsignedNegate,
96+
VariantSizeDifference,
9697

9798
ManagedHeapMemory,
9899
OwnedHeapMemory,
@@ -146,8 +147,9 @@ pub struct LintSpec {
146147

147148
pub type LintDict = HashMap<&'static str, LintSpec>;
148149

150+
// this is public for the lints that run in trans
149151
#[deriving(Eq)]
150-
enum LintSource {
152+
pub enum LintSource {
151153
Node(Span),
152154
Default,
153155
CommandLine
@@ -399,6 +401,13 @@ static lint_table: &'static [(&'static str, LintSpec)] = &[
399401
default: Warn
400402
}),
401403

404+
("variant_size_difference",
405+
LintSpec {
406+
lint: VariantSizeDifference,
407+
desc: "detects enums with widely varying variant sizes",
408+
default: Allow,
409+
}),
410+
402411
("unused_must_use",
403412
LintSpec {
404413
lint: UnusedMustUse,
@@ -461,6 +470,54 @@ struct Context<'a> {
461470

462471
/// Ids of structs/enums which have been checked for raw_pointer_deriving
463472
checked_raw_pointers: NodeSet,
473+
474+
/// Level of EnumSizeVariance lint for each enum, stored here because the
475+
/// body of the lint needs to run in trans.
476+
enum_levels: HashMap<ast::NodeId, (Level, LintSource)>,
477+
}
478+
479+
pub fn emit_lint(level: Level, src: LintSource, msg: &str, span: Span,
480+
lint_str: &str, tcx: &ty::ctxt) {
481+
if level == Allow { return }
482+
483+
let mut note = None;
484+
let msg = match src {
485+
Default => {
486+
format!("{}, \\#[{}({})] on by default", msg,
487+
level_to_str(level), lint_str)
488+
},
489+
CommandLine => {
490+
format!("{} [-{} {}]", msg,
491+
match level {
492+
Warn => 'W', Deny => 'D', Forbid => 'F',
493+
Allow => fail!()
494+
}, lint_str.replace("_", "-"))
495+
},
496+
Node(src) => {
497+
note = Some(src);
498+
msg.to_str()
499+
}
500+
};
501+
502+
match level {
503+
Warn => { tcx.sess.span_warn(span, msg.as_slice()); }
504+
Deny | Forbid => { tcx.sess.span_err(span, msg.as_slice()); }
505+
Allow => fail!(),
506+
}
507+
508+
for &span in note.iter() {
509+
tcx.sess.span_note(span, "lint level defined here");
510+
}
511+
}
512+
513+
pub fn lint_to_str(lint: Lint) -> &'static str {
514+
for &(name, lspec) in lint_table.iter() {
515+
if lspec.lint == lint {
516+
return name;
517+
}
518+
}
519+
520+
fail!("unrecognized lint: {}", lint);
464521
}
465522

466523
impl<'a> Context<'a> {
@@ -492,7 +549,7 @@ impl<'a> Context<'a> {
492549
return *k;
493550
}
494551
}
495-
fail!("unregistered lint {:?}", lint);
552+
fail!("unregistered lint {}", lint);
496553
}
497554

498555
fn span_lint(&self, lint: Lint, span: Span, msg: &str) {
@@ -501,37 +558,8 @@ impl<'a> Context<'a> {
501558
Some(&(Warn, src)) => (self.get_level(Warnings), src),
502559
Some(&pair) => pair,
503560
};
504-
if level == Allow { return }
505-
506-
let mut note = None;
507-
let msg = match src {
508-
Default => {
509-
format_strbuf!("{}, \\#[{}({})] on by default",
510-
msg,
511-
level_to_str(level),
512-
self.lint_to_str(lint))
513-
},
514-
CommandLine => {
515-
format!("{} [-{} {}]", msg,
516-
match level {
517-
Warn => 'W', Deny => 'D', Forbid => 'F',
518-
Allow => fail!()
519-
}, self.lint_to_str(lint).replace("_", "-"))
520-
},
521-
Node(src) => {
522-
note = Some(src);
523-
msg.to_str()
524-
}
525-
};
526-
match level {
527-
Warn => self.tcx.sess.span_warn(span, msg.as_slice()),
528-
Deny | Forbid => self.tcx.sess.span_err(span, msg.as_slice()),
529-
Allow => fail!(),
530-
}
531561

532-
for &span in note.iter() {
533-
self.tcx.sess.span_note(span, "lint level defined here");
534-
}
562+
emit_lint(level, src, msg, span, self.lint_to_str(lint), self.tcx);
535563
}
536564

537565
/**
@@ -1685,9 +1713,24 @@ fn check_stability(cx: &Context, e: &ast::Expr) {
16851713
cx.span_lint(lint, e.span, msg.as_slice());
16861714
}
16871715

1716+
fn check_enum_variant_sizes(cx: &mut Context, it: &ast::Item) {
1717+
match it.node {
1718+
ast::ItemEnum(..) => {
1719+
match cx.cur.find(&(VariantSizeDifference as uint)) {
1720+
Some(&(lvl, src)) if lvl != Allow => {
1721+
cx.node_levels.insert((it.id, VariantSizeDifference), (lvl, src));
1722+
},
1723+
_ => { }
1724+
}
1725+
},
1726+
_ => { }
1727+
}
1728+
}
1729+
16881730
impl<'a> Visitor<()> for Context<'a> {
16891731
fn visit_item(&mut self, it: &ast::Item, _: ()) {
16901732
self.with_lint_attrs(it.attrs.as_slice(), |cx| {
1733+
check_enum_variant_sizes(cx, it);
16911734
check_item_ctypes(cx, it);
16921735
check_item_non_camel_case_types(cx, it);
16931736
check_item_non_uppercase_statics(cx, it);
@@ -1878,6 +1921,7 @@ pub fn check_crate(tcx: &ty::ctxt,
18781921
lint_stack: Vec::new(),
18791922
negated_expr_id: -1,
18801923
checked_raw_pointers: NodeSet::new(),
1924+
enum_levels: HashMap::new(),
18811925
};
18821926

18831927
// Install default lint levels, followed by the command line levels, and
@@ -1913,13 +1957,11 @@ pub fn check_crate(tcx: &ty::ctxt,
19131957
// in the iteration code.
19141958
for (id, v) in tcx.sess.lints.borrow().iter() {
19151959
for &(lint, span, ref msg) in v.iter() {
1916-
tcx.sess.span_bug(span,
1917-
format!("unprocessed lint {:?} at {}: {}",
1918-
lint,
1919-
tcx.map.node_to_str(*id),
1920-
*msg).as_slice())
1960+
tcx.sess.span_bug(span, format!("unprocessed lint {} at {}: {}",
1961+
lint, tcx.map.node_to_str(*id), *msg).as_slice())
19211962
}
19221963
}
19231964

19241965
tcx.sess.abort_if_errors();
1966+
*tcx.enum_lint_levels.borrow_mut() = cx.enum_levels;
19251967
}

src/librustc/middle/trans/base.rs

+55-3
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ use lib::llvm::{ModuleRef, ValueRef, BasicBlockRef};
3636
use lib::llvm::{llvm, Vector};
3737
use lib;
3838
use metadata::{csearch, encoder};
39+
use middle::lint;
3940
use middle::astencode;
4041
use middle::lang_items::{LangItem, ExchangeMallocFnLangItem, StartFnLangItem};
4142
use middle::weak_lang_items;
@@ -57,7 +58,7 @@ use middle::trans::foreign;
5758
use middle::trans::glue;
5859
use middle::trans::inline;
5960
use middle::trans::machine;
60-
use middle::trans::machine::{llalign_of_min, llsize_of};
61+
use middle::trans::machine::{llalign_of_min, llsize_of, llsize_of_real};
6162
use middle::trans::meth;
6263
use middle::trans::monomorphize;
6364
use middle::trans::tvec;
@@ -1539,7 +1540,7 @@ fn trans_enum_variant_or_tuple_like_struct(ccx: &CrateContext,
15391540
}
15401541

15411542
fn trans_enum_def(ccx: &CrateContext, enum_definition: &ast::EnumDef,
1542-
id: ast::NodeId, vi: &[Rc<ty::VariantInfo>],
1543+
sp: Span, id: ast::NodeId, vi: &[Rc<ty::VariantInfo>],
15431544
i: &mut uint) {
15441545
for &variant in enum_definition.variants.iter() {
15451546
let disr_val = vi[*i].disr_val;
@@ -1559,6 +1560,57 @@ fn trans_enum_def(ccx: &CrateContext, enum_definition: &ast::EnumDef,
15591560
}
15601561
}
15611562
}
1563+
1564+
enum_variant_size_lint(ccx, enum_definition, sp, id);
1565+
}
1566+
1567+
fn enum_variant_size_lint(ccx: &CrateContext, enum_def: &ast::EnumDef, sp: Span, id: ast::NodeId) {
1568+
let mut sizes = Vec::new(); // does no allocation if no pushes, thankfully
1569+
1570+
let (lvl, src) = ccx.tcx.node_lint_levels.borrow()
1571+
.find(&(id, lint::VariantSizeDifference))
1572+
.map_or((lint::Allow, lint::Default), |&(lvl,src)| (lvl, src));
1573+
1574+
if lvl != lint::Allow {
1575+
let avar = adt::represent_type(ccx, ty::node_id_to_type(ccx.tcx(), id));
1576+
match *avar {
1577+
adt::General(_, ref variants) => {
1578+
for var in variants.iter() {
1579+
let mut size = 0;
1580+
for field in var.fields.iter().skip(1) {
1581+
// skip the dicriminant
1582+
size += llsize_of_real(ccx, sizing_type_of(ccx, *field));
1583+
}
1584+
sizes.push(size);
1585+
}
1586+
},
1587+
_ => { /* its size is either constant or unimportant */ }
1588+
}
1589+
1590+
let (largest, slargest, largest_index) = sizes.iter().enumerate().fold((0, 0, 0),
1591+
|(l, s, li), (idx, &size)|
1592+
if size > l {
1593+
(size, l, idx)
1594+
} else if size > s {
1595+
(l, size, li)
1596+
} else {
1597+
(l, s, li)
1598+
}
1599+
);
1600+
1601+
// we only warn if the largest variant is at least thrice as large as
1602+
// the second-largest.
1603+
if largest > slargest * 3 && slargest > 0 {
1604+
lint::emit_lint(lvl, src,
1605+
format!("enum variant is more than three times larger \
1606+
({} bytes) than the next largest (ignoring padding)",
1607+
largest).as_slice(),
1608+
sp, lint::lint_to_str(lint::VariantSizeDifference), ccx.tcx());
1609+
1610+
ccx.sess().span_note(enum_def.variants.get(largest_index).span,
1611+
"this variant is the largest");
1612+
}
1613+
}
15621614
}
15631615

15641616
pub struct TransItemVisitor<'a> {
@@ -1605,7 +1657,7 @@ pub fn trans_item(ccx: &CrateContext, item: &ast::Item) {
16051657
if !generics.is_type_parameterized() {
16061658
let vi = ty::enum_variants(ccx.tcx(), local_def(item.id));
16071659
let mut i = 0;
1608-
trans_enum_def(ccx, enum_definition, item.id, vi.as_slice(), &mut i);
1660+
trans_enum_def(ccx, enum_definition, item.span, item.id, vi.as_slice(), &mut i);
16091661
}
16101662
}
16111663
ast::ItemStatic(_, m, expr) => {

src/librustc/middle/ty.rs

+4
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ use back::svh::Svh;
1414
use driver::session::Session;
1515
use metadata::csearch;
1616
use mc = middle::mem_categorization;
17+
use middle::lint;
1718
use middle::const_eval;
1819
use middle::dependency_format;
1920
use middle::lang_items::{ExchangeHeapLangItem, OpaqueStructLangItem};
@@ -352,6 +353,8 @@ pub struct ctxt {
352353
pub vtable_map: typeck::vtable_map,
353354

354355
pub dependency_formats: RefCell<dependency_format::Dependencies>,
356+
357+
pub enum_lint_levels: RefCell<HashMap<ast::NodeId, (lint::Level, lint::LintSource)>>,
355358
}
356359

357360
pub enum tbox_flag {
@@ -1134,6 +1137,7 @@ pub fn mk_ctxt(s: Session,
11341137
method_map: RefCell::new(FnvHashMap::new()),
11351138
vtable_map: RefCell::new(FnvHashMap::new()),
11361139
dependency_formats: RefCell::new(HashMap::new()),
1140+
enum_lint_levels: RefCell::new(HashMap::new()),
11371141
}
11381142
}
11391143

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
// Copyright 2014 The Rust Project Developers. See the COPYRIGHT
2+
// file at the top-level directory of this distribution and at
3+
// http://rust-lang.org/COPYRIGHT.
4+
//
5+
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6+
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7+
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8+
// option. This file may not be copied, modified, or distributed
9+
// except according to those terms.
10+
//
11+
// ignore-pretty
12+
13+
#![deny(enum_size_variance)]
14+
#![allow(dead_code)]
15+
16+
enum Enum1 { }
17+
18+
enum Enum2 { A, B, C }
19+
20+
enum Enum3 { D(int), E, F }
21+
22+
enum Enum4 { H(int), I(int), J }
23+
24+
enum Enum5 { //~ ERROR three times larger
25+
L(int, int, int, int), //~ NOTE this variant is the largest
26+
M(int),
27+
N
28+
}
29+
30+
enum Enum6<T, U> {
31+
O(T),
32+
P(U),
33+
Q(int)
34+
}
35+
36+
#[allow(enum_size_variance)]
37+
enum Enum7 {
38+
R(int, int, int, int),
39+
S(int),
40+
T
41+
}
42+
pub fn main() { }

0 commit comments

Comments
 (0)