|
13 | 13 | use rustc::hir::def_id::DefId; |
14 | 14 | use rustc::ty::subst::Substs; |
15 | 15 | use rustc::ty::{self, Ty, TyCtxt}; |
| 16 | +use rustc::ty::layout::{Layout, Primitive}; |
| 17 | +use rustc::traits::ProjectionMode; |
16 | 18 | use middle::const_val::ConstVal; |
17 | 19 | use rustc_const_eval::eval_const_expr_partial; |
18 | 20 | use rustc_const_eval::EvalHint::ExprTypeChecked; |
@@ -77,6 +79,12 @@ declare_lint! { |
77 | 79 | "shift exceeds the type's number of bits" |
78 | 80 | } |
79 | 81 |
|
| 82 | +declare_lint! { |
| 83 | + VARIANT_SIZE_DIFFERENCES, |
| 84 | + Allow, |
| 85 | + "detects enums with widely varying variant sizes" |
| 86 | +} |
| 87 | + |
80 | 88 | #[derive(Copy, Clone)] |
81 | 89 | pub struct TypeLimits { |
82 | 90 | /// Id of the last visited negated expression |
@@ -676,3 +684,63 @@ impl LateLintPass for ImproperCTypes { |
676 | 684 | } |
677 | 685 | } |
678 | 686 | } |
| 687 | + |
| 688 | +pub struct VariantSizeDifferences; |
| 689 | + |
| 690 | +impl LintPass for VariantSizeDifferences { |
| 691 | + fn get_lints(&self) -> LintArray { |
| 692 | + lint_array!(VARIANT_SIZE_DIFFERENCES) |
| 693 | + } |
| 694 | +} |
| 695 | + |
| 696 | +impl LateLintPass for VariantSizeDifferences { |
| 697 | + fn check_item(&mut self, cx: &LateContext, it: &hir::Item) { |
| 698 | + if let hir::ItemEnum(ref enum_definition, ref gens) = it.node { |
| 699 | + if gens.ty_params.is_empty() { // sizes only make sense for non-generic types |
| 700 | + let mut sizes = vec![]; |
| 701 | + let t = cx.tcx.node_id_to_type(it.id); |
| 702 | + let layout = cx.tcx.normalizing_infer_ctxt(ProjectionMode::Any).enter(|infcx| { |
| 703 | + t.layout(&infcx).unwrap_or_else(|e| { |
| 704 | + bug!("failed to get layout for `{}`: {}", t, e) |
| 705 | + }) |
| 706 | + }); |
| 707 | + |
| 708 | + if let Layout::General { ref variants, ref size, discr, .. } = *layout { |
| 709 | + let discr_size = Primitive::Int(discr).size(&cx.tcx.data_layout).bytes(); |
| 710 | + |
| 711 | + debug!("enum `{}` is {} bytes large", t, size.bytes()); |
| 712 | + |
| 713 | + for (variant, variant_layout) in enum_definition.variants.iter().zip(variants) { |
| 714 | + // Subtract the size of the enum discriminant |
| 715 | + let bytes = variant_layout.min_size().bytes().saturating_sub(discr_size); |
| 716 | + sizes.push(bytes); |
| 717 | + |
| 718 | + debug!("- variant `{}` is {} bytes large", variant.node.name, bytes); |
| 719 | + } |
| 720 | + |
| 721 | + let (largest, slargest, largest_index) = sizes.iter() |
| 722 | + .enumerate() |
| 723 | + .fold((0, 0, 0), |
| 724 | + |(l, s, li), (idx, &size)| |
| 725 | + if size > l { |
| 726 | + (size, l, idx) |
| 727 | + } else if size > s { |
| 728 | + (l, size, li) |
| 729 | + } else { |
| 730 | + (l, s, li) |
| 731 | + } |
| 732 | + ); |
| 733 | + |
| 734 | + // we only warn if the largest variant is at least thrice as large as |
| 735 | + // the second-largest. |
| 736 | + if largest > slargest * 3 && slargest > 0 { |
| 737 | + cx.span_lint(VARIANT_SIZE_DIFFERENCES, |
| 738 | + enum_definition.variants[largest_index].span, |
| 739 | + &format!("enum variant is more than three times larger \ |
| 740 | + ({} bytes) than the next largest", largest)); |
| 741 | + } |
| 742 | + } |
| 743 | + } |
| 744 | + } |
| 745 | + } |
| 746 | +} |
0 commit comments