Skip to content

Commit ba77c60

Browse files
committed
auto merge of #14300 : cmr/rust/enum-size-lint, r=kballard
See commits for details.
2 parents a7ab733 + d8467e2 commit ba77c60

File tree

4 files changed

+239
-98
lines changed

4 files changed

+239
-98
lines changed

src/librustc/middle/lint.rs

+96-54
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, Hash)]
7676
pub enum Lint {
7777
CTypes,
7878
UnusedImports,
@@ -94,6 +94,7 @@ pub enum Lint {
9494
UnknownFeatures,
9595
UnknownCrateType,
9696
UnsignedNegate,
97+
VariantSizeDifference,
9798

9899
ManagedHeapMemory,
99100
OwnedHeapMemory,
@@ -147,8 +148,9 @@ pub struct LintSpec {
147148

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

151+
// this is public for the lints that run in trans
150152
#[deriving(Eq)]
151-
enum LintSource {
153+
pub enum LintSource {
152154
Node(Span),
153155
Default,
154156
CommandLine
@@ -407,6 +409,13 @@ static lint_table: &'static [(&'static str, LintSpec)] = &[
407409
default: Warn
408410
}),
409411

412+
("variant_size_difference",
413+
LintSpec {
414+
lint: VariantSizeDifference,
415+
desc: "detects enums with widely varying variant sizes",
416+
default: Allow,
417+
}),
418+
410419
("unused_must_use",
411420
LintSpec {
412421
lint: UnusedMustUse,
@@ -445,30 +454,78 @@ pub fn get_lint_dict() -> LintDict {
445454
}
446455

447456
struct Context<'a> {
448-
// All known lint modes (string versions)
457+
/// All known lint modes (string versions)
449458
dict: LintDict,
450-
// Current levels of each lint warning
459+
/// Current levels of each lint warning
451460
cur: SmallIntMap<(Level, LintSource)>,
452-
// context we're checking in (used to access fields like sess)
461+
/// Context we're checking in (used to access fields like sess)
453462
tcx: &'a ty::ctxt,
454-
// Items exported by the crate; used by the missing_doc lint.
463+
/// Items exported by the crate; used by the missing_doc lint.
455464
exported_items: &'a privacy::ExportedItems,
456-
// The id of the current `ast::StructDef` being walked.
465+
/// The id of the current `ast::StructDef` being walked.
457466
cur_struct_def_id: ast::NodeId,
458-
// Whether some ancestor of the current node was marked
459-
// #[doc(hidden)].
467+
/// Whether some ancestor of the current node was marked
468+
/// #[doc(hidden)].
460469
is_doc_hidden: bool,
461470

462-
// When recursing into an attributed node of the ast which modifies lint
463-
// levels, this stack keeps track of the previous lint levels of whatever
464-
// was modified.
471+
/// When recursing into an attributed node of the ast which modifies lint
472+
/// levels, this stack keeps track of the previous lint levels of whatever
473+
/// was modified.
465474
lint_stack: Vec<(Lint, Level, LintSource)>,
466475

467-
// id of the last visited negated expression
476+
/// Id of the last visited negated expression
468477
negated_expr_id: ast::NodeId,
469478

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

474531
impl<'a> Context<'a> {
@@ -500,7 +557,7 @@ impl<'a> Context<'a> {
500557
return *k;
501558
}
502559
}
503-
fail!("unregistered lint {:?}", lint);
560+
fail!("unregistered lint {}", lint);
504561
}
505562

506563
fn span_lint(&self, lint: Lint, span: Span, msg: &str) {
@@ -509,37 +566,8 @@ impl<'a> Context<'a> {
509566
Some(&(Warn, src)) => (self.get_level(Warnings), src),
510567
Some(&pair) => pair,
511568
};
512-
if level == Allow { return }
513-
514-
let mut note = None;
515-
let msg = match src {
516-
Default => {
517-
format_strbuf!("{}, \\#[{}({})] on by default",
518-
msg,
519-
level_to_str(level),
520-
self.lint_to_str(lint))
521-
},
522-
CommandLine => {
523-
format!("{} [-{} {}]", msg,
524-
match level {
525-
Warn => 'W', Deny => 'D', Forbid => 'F',
526-
Allow => fail!()
527-
}, self.lint_to_str(lint).replace("_", "-"))
528-
},
529-
Node(src) => {
530-
note = Some(src);
531-
msg.to_str()
532-
}
533-
};
534-
match level {
535-
Warn => self.tcx.sess.span_warn(span, msg.as_slice()),
536-
Deny | Forbid => self.tcx.sess.span_err(span, msg.as_slice()),
537-
Allow => fail!(),
538-
}
539569

540-
for &span in note.iter() {
541-
self.tcx.sess.span_note(span, "lint level defined here");
542-
}
570+
emit_lint(level, src, msg, span, self.lint_to_str(lint), self.tcx);
543571
}
544572

545573
/**
@@ -618,8 +646,8 @@ impl<'a> Context<'a> {
618646
}
619647
}
620648

621-
// Check that every lint from the list of attributes satisfies `f`.
622-
// Return true if that's the case. Otherwise return false.
649+
/// Check that every lint from the list of attributes satisfies `f`.
650+
/// Return true if that's the case. Otherwise return false.
623651
pub fn each_lint(sess: &session::Session,
624652
attrs: &[ast::Attribute],
625653
f: |@ast::MetaItem, Level, InternedString| -> bool)
@@ -653,8 +681,8 @@ pub fn each_lint(sess: &session::Session,
653681
true
654682
}
655683

656-
// Check from a list of attributes if it contains the appropriate
657-
// `#[level(lintname)]` attribute (e.g. `#[allow(dead_code)]).
684+
/// Check from a list of attributes if it contains the appropriate
685+
/// `#[level(lintname)]` attribute (e.g. `#[allow(dead_code)]).
658686
pub fn contains_lint(attrs: &[ast::Attribute],
659687
level: Level,
660688
lintname: &'static str)
@@ -1739,9 +1767,24 @@ fn check_stability(cx: &Context, e: &ast::Expr) {
17391767
cx.span_lint(lint, e.span, msg.as_slice());
17401768
}
17411769

1770+
fn check_enum_variant_sizes(cx: &mut Context, it: &ast::Item) {
1771+
match it.node {
1772+
ast::ItemEnum(..) => {
1773+
match cx.cur.find(&(VariantSizeDifference as uint)) {
1774+
Some(&(lvl, src)) if lvl != Allow => {
1775+
cx.node_levels.insert((it.id, VariantSizeDifference), (lvl, src));
1776+
},
1777+
_ => { }
1778+
}
1779+
},
1780+
_ => { }
1781+
}
1782+
}
1783+
17421784
impl<'a> Visitor<()> for Context<'a> {
17431785
fn visit_item(&mut self, it: &ast::Item, _: ()) {
17441786
self.with_lint_attrs(it.attrs.as_slice(), |cx| {
1787+
check_enum_variant_sizes(cx, it);
17451788
check_item_ctypes(cx, it);
17461789
check_item_non_camel_case_types(cx, it);
17471790
check_item_non_uppercase_statics(cx, it);
@@ -1933,6 +1976,7 @@ pub fn check_crate(tcx: &ty::ctxt,
19331976
lint_stack: Vec::new(),
19341977
negated_expr_id: -1,
19351978
checked_raw_pointers: NodeSet::new(),
1979+
node_levels: HashMap::new(),
19361980
};
19371981

19381982
// Install default lint levels, followed by the command line levels, and
@@ -1969,13 +2013,11 @@ pub fn check_crate(tcx: &ty::ctxt,
19692013
// in the iteration code.
19702014
for (id, v) in tcx.sess.lints.borrow().iter() {
19712015
for &(lint, span, ref msg) in v.iter() {
1972-
tcx.sess.span_bug(span,
1973-
format!("unprocessed lint {:?} at {}: {}",
1974-
lint,
1975-
tcx.map.node_to_str(*id),
1976-
*msg).as_slice())
2016+
tcx.sess.span_bug(span, format!("unprocessed lint {} at {}: {}",
2017+
lint, tcx.map.node_to_str(*id), *msg).as_slice())
19772018
}
19782019
}
19792020

19802021
tcx.sess.abort_if_errors();
2022+
*tcx.node_lint_levels.borrow_mut() = cx.node_levels;
19812023
}

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;
@@ -1489,7 +1490,7 @@ fn trans_enum_variant_or_tuple_like_struct(ccx: &CrateContext,
14891490
}
14901491

14911492
fn trans_enum_def(ccx: &CrateContext, enum_definition: &ast::EnumDef,
1492-
id: ast::NodeId, vi: &[Rc<ty::VariantInfo>],
1493+
sp: Span, id: ast::NodeId, vi: &[Rc<ty::VariantInfo>],
14931494
i: &mut uint) {
14941495
for &variant in enum_definition.variants.iter() {
14951496
let disr_val = vi[*i].disr_val;
@@ -1509,6 +1510,57 @@ fn trans_enum_def(ccx: &CrateContext, enum_definition: &ast::EnumDef,
15091510
}
15101511
}
15111512
}
1513+
1514+
enum_variant_size_lint(ccx, enum_definition, sp, id);
1515+
}
1516+
1517+
fn enum_variant_size_lint(ccx: &CrateContext, enum_def: &ast::EnumDef, sp: Span, id: ast::NodeId) {
1518+
let mut sizes = Vec::new(); // does no allocation if no pushes, thankfully
1519+
1520+
let (lvl, src) = ccx.tcx.node_lint_levels.borrow()
1521+
.find(&(id, lint::VariantSizeDifference))
1522+
.map_or((lint::Allow, lint::Default), |&(lvl,src)| (lvl, src));
1523+
1524+
if lvl != lint::Allow {
1525+
let avar = adt::represent_type(ccx, ty::node_id_to_type(ccx.tcx(), id));
1526+
match *avar {
1527+
adt::General(_, ref variants) => {
1528+
for var in variants.iter() {
1529+
let mut size = 0;
1530+
for field in var.fields.iter().skip(1) {
1531+
// skip the dicriminant
1532+
size += llsize_of_real(ccx, sizing_type_of(ccx, *field));
1533+
}
1534+
sizes.push(size);
1535+
}
1536+
},
1537+
_ => { /* its size is either constant or unimportant */ }
1538+
}
1539+
1540+
let (largest, slargest, largest_index) = sizes.iter().enumerate().fold((0, 0, 0),
1541+
|(l, s, li), (idx, &size)|
1542+
if size > l {
1543+
(size, l, idx)
1544+
} else if size > s {
1545+
(l, size, li)
1546+
} else {
1547+
(l, s, li)
1548+
}
1549+
);
1550+
1551+
// we only warn if the largest variant is at least thrice as large as
1552+
// the second-largest.
1553+
if largest > slargest * 3 && slargest > 0 {
1554+
lint::emit_lint(lvl, src,
1555+
format!("enum variant is more than three times larger \
1556+
({} bytes) than the next largest (ignoring padding)",
1557+
largest).as_slice(),
1558+
sp, lint::lint_to_str(lint::VariantSizeDifference), ccx.tcx());
1559+
1560+
ccx.sess().span_note(enum_def.variants.get(largest_index).span,
1561+
"this variant is the largest");
1562+
}
1563+
}
15121564
}
15131565

15141566
pub struct TransItemVisitor<'a> {
@@ -1555,7 +1607,7 @@ pub fn trans_item(ccx: &CrateContext, item: &ast::Item) {
15551607
if !generics.is_type_parameterized() {
15561608
let vi = ty::enum_variants(ccx.tcx(), local_def(item.id));
15571609
let mut i = 0;
1558-
trans_enum_def(ccx, enum_definition, item.id, vi.as_slice(), &mut i);
1610+
trans_enum_def(ccx, enum_definition, item.span, item.id, vi.as_slice(), &mut i);
15591611
}
15601612
}
15611613
ast::ItemStatic(_, m, expr) => {

0 commit comments

Comments
 (0)