Repository: googlefonts/fontations (rust)

Issue:
IntSet::remove_range() subtract with overflow
Minimal test case:

```rust
let mut set = IntSet::<u32>::empty();
set.remove_range(0..=u32::MAX);
```

Using the range `0..=u32::MAX - 512` works but any greater end range fails. Initializing the set with `all()` succeeds.

The overflow occurs here: https://github.com/googlefonts/fontations/blob/a52dbf59cda0aa16909d0af93fe74c518278a085/int-set/src/bitset.rs#L110

@garretrieger 

Pull request description:
[int-set] Fix subtraction overflow in remove_range.
Fixes #997

Patch:
diff --git a/font-codegen/src/table.rs b/font-codegen/src/table.rs
index 2782ca10..991b1305 100644
--- a/font-codegen/src/table.rs
+++ b/font-codegen/src/table.rs
@@ -677,10 +677,28 @@ fn generate_format_shared_getters(item: &TableFormat, items: &Items) -> syn::Res
         .iter()
         .map(|fld| generate_format_getter_for_shared_field(item, fld));
 
+    // and we also want to have a wrapper for offset_data():
+    let data_arms = item
+        .variants
+        .iter()
+        .filter(|v| v.attrs.write_only.is_none())
+        .map(|variant| {
+            let var_name = &variant.name;
+            quote!(Self::#var_name(item) => item.offset_data(), )
+        });
+
     // now we have a collection of fields present on all variants, and
     // we need to actually generate the wrapping getter
 
-    Ok(quote!( #( #getters )* ))
+    Ok(quote! {
+        #[doc = "Return the `FontData` used to resolve offsets for this table."]
+        pub fn offset_data(&self) -> FontData<'a> {
+            match self {
+                #( #data_arms )*
+            }
+        }
+        #( #getters )*
+    })
 }
 
 fn generate_format_getter_for_shared_field(item: &TableFormat, field: &Field) -> TokenStream {
diff --git a/int-set/src/bitset.rs b/int-set/src/bitset.rs
index 7805248f..0ecfc389 100644
--- a/int-set/src/bitset.rs
+++ b/int-set/src/bitset.rs
@@ -107,7 +107,7 @@ impl BitSet {
 
         for major in major_start..=major_end {
             let page_start = start.max(self.major_start(major));
-            let page_end = end.min(self.major_start(major + 1) - 1);
+            let page_end = end.min(self.major_start(major + 1).wrapping_sub(1));
             if let Some(page) = self.page_for_major_mut(major) {
                 page.remove_range(page_start, page_end);
             }
@@ -885,6 +885,25 @@ mod test {
         assert_eq!(bitset.iter().collect::<Vec<u32>>(), vec![5, 2000]);
     }
 
+    #[test]
+    fn remove_range_boundary() {
+        let mut set = BitSet::empty();
+
+        set.remove_range(u32::MAX - 10..=u32::MAX);
+        assert!(!set.contains(u32::MAX));
+        set.insert_range(u32::MAX - 10..=u32::MAX);
+        assert!(set.contains(u32::MAX));
+        set.remove_range(u32::MAX - 10..=u32::MAX);
+        assert!(!set.contains(u32::MAX));
+
+        set.remove_range(0..=10);
+        assert!(!set.contains(0));
+        set.insert_range(0..=10);
+        assert!(set.contains(0));
+        set.remove_range(0..=10);
+        assert!(!set.contains(0));
+    }
+
     #[test]
     fn remove_to_empty_page() {
         let mut bitset = BitSet::empty();
diff --git a/int-set/src/lib.rs b/int-set/src/lib.rs
index 0c44c5f6..391a0449 100644
--- a/int-set/src/lib.rs
+++ b/int-set/src/lib.rs
@@ -1345,6 +1345,44 @@ mod test {
         assert!(all.contains(4));
     }
 
+    #[test]
+    fn insert_remove_range_boundary() {
+        let mut set = IntSet::<u32>::empty();
+
+        set.remove_range(u32::MAX - 10..=u32::MAX);
+        assert!(!set.contains(u32::MAX));
+        set.insert_range(u32::MAX - 10..=u32::MAX);
+        assert!(set.contains(u32::MAX));
+        set.remove_range(u32::MAX - 10..=u32::MAX);
+        assert!(!set.contains(u32::MAX));
+
+        set.remove_range(0..=10);
+        assert!(!set.contains(0));
+        set.insert_range(0..=10);
+        assert!(set.contains(0));
+        set.remove_range(0..=10);
+        assert!(!set.contains(0));
+    }
+
+    #[test]
+    fn insert_remove_range_exclusive_boundary() {
+        let mut set = IntSet::<u32>::all();
+
+        set.remove_range(u32::MAX - 10..=u32::MAX);
+        assert!(!set.contains(u32::MAX));
+        set.insert_range(u32::MAX - 10..=u32::MAX);
+        assert!(set.contains(u32::MAX));
+        set.remove_range(u32::MAX - 10..=u32::MAX);
+        assert!(!set.contains(u32::MAX));
+
+        set.remove_range(0..=10);
+        assert!(!set.contains(0));
+        set.insert_range(0..=10);
+        assert!(set.contains(0));
+        set.remove_range(0..=10);
+        assert!(!set.contains(0));
+    }
+
     struct SetOpInput {
         has_x: bool,
         inverted: bool,
diff --git a/read-fonts/generated/generated_aat.rs b/read-fonts/generated/generated_aat.rs
index 4cef4bd0..808f69f3 100644
--- a/read-fonts/generated/generated_aat.rs
+++ b/read-fonts/generated/generated_aat.rs
@@ -18,6 +18,18 @@ pub enum Lookup<'a> {
 }
 
 impl<'a> Lookup<'a> {
+    ///Return the `FontData` used to resolve offsets for this table.
+    pub fn offset_data(&self) -> FontData<'a> {
+        match self {
+            Self::Format0(item) => item.offset_data(),
+            Self::Format2(item) => item.offset_data(),
+            Self::Format4(item) => item.offset_data(),
+            Self::Format6(item) => item.offset_data(),
+            Self::Format8(item) => item.offset_data(),
+            Self::Format10(item) => item.offset_data(),
+        }
+    }
+
     /// Format number is set to 0.
     pub fn format(&self) -> u16 {
         match self {
diff --git a/read-fonts/generated/generated_base.rs b/read-fonts/generated/generated_base.rs
index 06639780..c472e5ec 100644
--- a/read-fonts/generated/generated_base.rs
+++ b/read-fonts/generated/generated_base.rs
@@ -919,6 +919,15 @@ pub enum BaseCoord<'a> {
 }
 
 impl<'a> BaseCoord<'a> {
+    ///Return the `FontData` used to resolve offsets for this table.
+    pub fn offset_data(&self) -> FontData<'a> {
+        match self {
+            Self::Format1(item) => item.offset_data(),
+            Self::Format2(item) => item.offset_data(),
+            Self::Format3(item) => item.offset_data(),
+        }
+    }
+
     /// Format identifier — format = 1
     pub fn base_coord_format(&self) -> u16 {
         match self {
diff --git a/read-fonts/generated/generated_bitmap.rs b/read-fonts/generated/generated_bitmap.rs
index b47a975f..8a2171b7 100644
--- a/read-fonts/generated/generated_bitmap.rs
+++ b/read-fonts/generated/generated_bitmap.rs
@@ -825,6 +825,17 @@ pub enum IndexSubtable<'a> {
 }
 
 impl<'a> IndexSubtable<'a> {
+    ///Return the `FontData` used to resolve offsets for this table.
+    pub fn offset_data(&self) -> FontData<'a> {
+        match self {
+            Self::Format1(item) => item.offset_data(),
+            Self::Format2(item) => item.offset_data(),
+            Self::Format3(item) => item.offset_data(),
+            Self::Format4(item) => item.offset_data(),
+            Self::Format5(item) => item.offset_data(),
+        }
+    }
+
     /// Format of this IndexSubTable.
     pub fn index_format(&self) -> u16 {
         match self {
diff --git a/read-fonts/generated/generated_cmap.rs b/read-fonts/generated/generated_cmap.rs
index f5623b94..5bca4800 100644
--- a/read-fonts/generated/generated_cmap.rs
+++ b/read-fonts/generated/generated_cmap.rs
@@ -229,6 +229,21 @@ pub enum CmapSubtable<'a> {
 }
 
 impl<'a> CmapSubtable<'a> {
+    ///Return the `FontData` used to resolve offsets for this table.
+    pub fn offset_data(&self) -> FontData<'a> {
+        match self {
+            Self::Format0(item) => item.offset_data(),
+            Self::Format2(item) => item.offset_data(),
+            Self::Format4(item) => item.offset_data(),
+            Self::Format6(item) => item.offset_data(),
+            Self::Format8(item) => item.offset_data(),
+            Self::Format10(item) => item.offset_data(),
+            Self::Format12(item) => item.offset_data(),
+            Self::Format13(item) => item.offset_data(),
+            Self::Format14(item) => item.offset_data(),
+        }
+    }
+
     /// Format number is set to 0.
     pub fn format(&self) -> u16 {
         match self {
diff --git a/read-fonts/generated/generated_colr.rs b/read-fonts/generated/generated_colr.rs
index c7763719..a595c283 100644
--- a/read-fonts/generated/generated_colr.rs
+++ b/read-fonts/generated/generated_colr.rs
@@ -773,6 +773,14 @@ pub enum ClipBox<'a> {
 }
 
 impl<'a> ClipBox<'a> {
+    ///Return the `FontData` used to resolve offsets for this table.
+    pub fn offset_data(&self) -> FontData<'a> {
+        match self {
+            Self::Format1(item) => item.offset_data(),
+            Self::Format2(item) => item.offset_data(),
+        }
+    }
+
     /// Set to 1.
     pub fn format(&self) -> u8 {
         match self {
@@ -1533,6 +1541,44 @@ pub enum Paint<'a> {
 }
 
 impl<'a> Paint<'a> {
+    ///Return the `FontData` used to resolve offsets for this table.
+    pub fn offset_data(&self) -> FontData<'a> {
+        match self {
+            Self::ColrLayers(item) => item.offset_data(),
+            Self::Solid(item) => item.offset_data(),
+            Self::VarSolid(item) => item.offset_data(),
+            Self::LinearGradient(item) => item.offset_data(),
+            Self::VarLinearGradient(item) => item.offset_data(),
+            Self::RadialGradient(item) => item.offset_data(),
+            Self::VarRadialGradient(item) => item.offset_data(),
+            Self::SweepGradient(item) => item.offset_data(),
+            Self::VarSweepGradient(item) => item.offset_data(),
+            Self::Glyph(item) => item.offset_data(),
+            Self::ColrGlyph(item) => item.offset_data(),
+            Self::Transform(item) => item.offset_data(),
+            Self::VarTransform(item) => item.offset_data(),
+            Self::Translate(item) => item.offset_data(),
+            Self::VarTranslate(item) => item.offset_data(),
+            Self::Scale(item) => item.offset_data(),
+            Self::VarScale(item) => item.offset_data(),
+            Self::ScaleAroundCenter(item) => item.offset_data(),
+            Self::VarScaleAroundCenter(item) => item.offset_data(),
+            Self::ScaleUniform(item) => item.offset_data(),
+            Self::VarScaleUniform(item) => item.offset_data(),
+            Self::ScaleUniformAroundCenter(item) => item.offset_data(),
+            Self::VarScaleUniformAroundCenter(item) => item.offset_data(),
+            Self::Rotate(item) => item.offset_data(),
+            Self::VarRotate(item) => item.offset_data(),
+            Self::RotateAroundCenter(item) => item.offset_data(),
+            Self::VarRotateAroundCenter(item) => item.offset_data(),
+            Self::Skew(item) => item.offset_data(),
+            Self::VarSkew(item) => item.offset_data(),
+            Self::SkewAroundCenter(item) => item.offset_data(),
+            Self::VarSkewAroundCenter(item) => item.offset_data(),
+            Self::Composite(item) => item.offset_data(),
+        }
+    }
+
     /// Set to 1.
     pub fn format(&self) -> u8 {
         match self {
diff --git a/read-fonts/generated/generated_gdef.rs b/read-fonts/generated/generated_gdef.rs
index a0f26a07..b85a696d 100644
--- a/read-fonts/generated/generated_gdef.rs
+++ b/read-fonts/generated/generated_gdef.rs
@@ -664,6 +664,15 @@ pub enum CaretValue<'a> {
 }
 
 impl<'a> CaretValue<'a> {
+    ///Return the `FontData` used to resolve offsets for this table.
+    pub fn offset_data(&self) -> FontData<'a> {
+        match self {
+            Self::Format1(item) => item.offset_data(),
+            Self::Format2(item) => item.offset_data(),
+            Self::Format3(item) => item.offset_data(),
+        }
+    }
+
     /// Format identifier: format = 1
     pub fn caret_value_format(&self) -> u16 {
         match self {
diff --git a/read-fonts/generated/generated_glyf.rs b/read-fonts/generated/generated_glyf.rs
index 5be60544..f1877ae3 100644
--- a/read-fonts/generated/generated_glyf.rs
+++ b/read-fonts/generated/generated_glyf.rs
@@ -1126,6 +1126,14 @@ pub enum Glyph<'a> {
 }
 
 impl<'a> Glyph<'a> {
+    ///Return the `FontData` used to resolve offsets for this table.
+    pub fn offset_data(&self) -> FontData<'a> {
+        match self {
+            Self::Simple(item) => item.offset_data(),
+            Self::Composite(item) => item.offset_data(),
+        }
+    }
+
     /// If the number of contours is greater than or equal to zero,
     /// this is a simple glyph. If negative, this is a composite glyph
     /// — the value -1 should be used for composite glyphs.
diff --git a/read-fonts/generated/generated_gpos.rs b/read-fonts/generated/generated_gpos.rs
index 54f67570..a44b990b 100644
--- a/read-fonts/generated/generated_gpos.rs
+++ b/read-fonts/generated/generated_gpos.rs
@@ -598,6 +598,15 @@ pub enum AnchorTable<'a> {
 }
 
 impl<'a> AnchorTable<'a> {
+    ///Return the `FontData` used to resolve offsets for this table.
+    pub fn offset_data(&self) -> FontData<'a> {
+        match self {
+            Self::Format1(item) => item.offset_data(),
+            Self::Format2(item) => item.offset_data(),
+            Self::Format3(item) => item.offset_data(),
+        }
+    }
+
     /// Format identifier, = 1
     pub fn anchor_format(&self) -> u16 {
         match self {
@@ -1100,6 +1109,14 @@ pub enum SinglePos<'a> {
 }
 
 impl<'a> SinglePos<'a> {
+    ///Return the `FontData` used to resolve offsets for this table.
+    pub fn offset_data(&self) -> FontData<'a> {
+        match self {
+            Self::Format1(item) => item.offset_data(),
+            Self::Format2(item) => item.offset_data(),
+        }
+    }
+
     /// Format identifier: format = 1
     pub fn pos_format(&self) -> u16 {
         match self {
@@ -1411,6 +1428,14 @@ pub enum PairPos<'a> {
 }
 
 impl<'a> PairPos<'a> {
+    ///Return the `FontData` used to resolve offsets for this table.
+    pub fn offset_data(&self) -> FontData<'a> {
+        match self {
+            Self::Format1(item) => item.offset_data(),
+            Self::Format2(item) => item.offset_data(),
+        }
+    }
+
     /// Format identifier: format = 1
     pub fn pos_format(&self) -> u16 {
         match self {
diff --git a/read-fonts/generated/generated_gsub.rs b/read-fonts/generated/generated_gsub.rs
index b243e0bd..650e25bb 100644
--- a/read-fonts/generated/generated_gsub.rs
+++ b/read-fonts/generated/generated_gsub.rs
@@ -249,6 +249,14 @@ pub enum SingleSubst<'a> {
 }
 
 impl<'a> SingleSubst<'a> {
+    ///Return the `FontData` used to resolve offsets for this table.
+    pub fn offset_data(&self) -> FontData<'a> {
+        match self {
+            Self::Format1(item) => item.offset_data(),
+            Self::Format2(item) => item.offset_data(),
+        }
+    }
+
     /// Format identifier: format = 1
     pub fn subst_format(&self) -> u16 {
         match self {
diff --git a/read-fonts/generated/generated_layout.rs b/read-fonts/generated/generated_layout.rs
index 6ba967ed..bd8055c4 100644
--- a/read-fonts/generated/generated_layout.rs
+++ b/read-fonts/generated/generated_layout.rs
@@ -1191,6 +1191,14 @@ pub enum CoverageTable<'a> {
 }
 
 impl<'a> CoverageTable<'a> {
+    ///Return the `FontData` used to resolve offsets for this table.
+    pub fn offset_data(&self) -> FontData<'a> {
+        match self {
+            Self::Format1(item) => item.offset_data(),
+            Self::Format2(item) => item.offset_data(),
+        }
+    }
+
     /// Format identifier — format = 1
     pub fn coverage_format(&self) -> u16 {
         match self {
@@ -1488,6 +1496,14 @@ pub enum ClassDef<'a> {
 }
 
 impl<'a> ClassDef<'a> {
+    ///Return the `FontData` used to resolve offsets for this table.
+    pub fn offset_data(&self) -> FontData<'a> {
+        match self {
+            Self::Format1(item) => item.offset_data(),
+            Self::Format2(item) => item.offset_data(),
+        }
+    }
+
     /// Format identifier — format = 1
     pub fn class_format(&self) -> u16 {
         match self {
@@ -2411,6 +2427,15 @@ pub enum SequenceContext<'a> {
 }
 
 impl<'a> SequenceContext<'a> {
+    ///Return the `FontData` used to resolve offsets for this table.
+    pub fn offset_data(&self) -> FontData<'a> {
+        match self {
+            Self::Format1(item) => item.offset_data(),
+            Self::Format2(item) => item.offset_data(),
+            Self::Format3(item) => item.offset_data(),
+        }
+    }
+
     /// Format identifier: format = 1
     pub fn format(&self) -> u16 {
         match self {
@@ -3587,6 +3612,15 @@ pub enum ChainedSequenceContext<'a> {
 }
 
 impl<'a> ChainedSequenceContext<'a> {
+    ///Return the `FontData` used to resolve offsets for this table.
+    pub fn offset_data(&self) -> FontData<'a> {
+        match self {
+            Self::Format1(item) => item.offset_data(),
+            Self::Format2(item) => item.offset_data(),
+            Self::Format3(item) => item.offset_data(),
+        }
+    }
+
     /// Format identifier: format = 1
     pub fn format(&self) -> u16 {
         match self {
@@ -3875,6 +3909,16 @@ pub enum DeviceOrVariationIndex<'a> {
     VariationIndex(VariationIndex<'a>),
 }
 
+impl<'a> DeviceOrVariationIndex<'a> {
+    ///Return the `FontData` used to resolve offsets for this table.
+    pub fn offset_data(&self) -> FontData<'a> {
+        match self {
+            Self::Device(item) => item.offset_data(),
+            Self::VariationIndex(item) => item.offset_data(),
+        }
+    }
+}
+
 impl<'a> FontRead<'a> for DeviceOrVariationIndex<'a> {
     fn read(data: FontData<'a>) -> Result<Self, ReadError> {
         let format: DeltaFormat = data.read_at(4usize)?;
@@ -4194,6 +4238,17 @@ pub enum Condition<'a> {
 }
 
 impl<'a> Condition<'a> {
+    ///Return the `FontData` used to resolve offsets for this table.
+    pub fn offset_data(&self) -> FontData<'a> {
+        match self {
+            Self::Format1AxisRange(item) => item.offset_data(),
+            Self::Format2VariableValue(item) => item.offset_data(),
+            Self::Format3And(item) => item.offset_data(),
+            Self::Format4Or(item) => item.offset_data(),
+            Self::Format5Negate(item) => item.offset_data(),
+        }
+    }
+
     /// Format, = 1
     pub fn format(&self) -> u16 {
         match self {
diff --git a/read-fonts/generated/generated_postscript.rs b/read-fonts/generated/generated_postscript.rs
index 3975819e..1994b1b8 100644
--- a/read-fonts/generated/generated_postscript.rs
+++ b/read-fonts/generated/generated_postscript.rs
@@ -208,6 +208,15 @@ pub enum FdSelect<'a> {
 }
 
 impl<'a> FdSelect<'a> {
+    ///Return the `FontData` used to resolve offsets for this table.
+    pub fn offset_data(&self) -> FontData<'a> {
+        match self {
+            Self::Format0(item) => item.offset_data(),
+            Self::Format3(item) => item.offset_data(),
+            Self::Format4(item) => item.offset_data(),
+        }
+    }
+
     /// Format = 0.
     pub fn format(&self) -> u8 {
         match self {
diff --git a/read-fonts/generated/generated_stat.rs b/read-fonts/generated/generated_stat.rs
index 9e0fffec..523b2a0a 100644
--- a/read-fonts/generated/generated_stat.rs
+++ b/read-fonts/generated/generated_stat.rs
@@ -348,6 +348,16 @@ pub enum AxisValue<'a> {
 }
 
 impl<'a> AxisValue<'a> {
+    ///Return the `FontData` used to resolve offsets for this table.
+    pub fn offset_data(&self) -> FontData<'a> {
+        match self {
+            Self::Format1(item) => item.offset_data(),
+            Self::Format2(item) => item.offset_data(),
+            Self::Format3(item) => item.offset_data(),
+            Self::Format4(item) => item.offset_data(),
+        }
+    }
+
     /// Format identifier — set to 1.
     pub fn format(&self) -> u16 {
         match self {
diff --git a/read-fonts/generated/generated_variations.rs b/read-fonts/generated/generated_variations.rs
index ee983ec3..fab580b7 100644
--- a/read-fonts/generated/generated_variations.rs
+++ b/read-fonts/generated/generated_variations.rs
@@ -398,6 +398,14 @@ pub enum DeltaSetIndexMap<'a> {
 }
 
 impl<'a> DeltaSetIndexMap<'a> {
+    ///Return the `FontData` used to resolve offsets for this table.
+    pub fn offset_data(&self) -> FontData<'a> {
+        match self {
+            Self::Format0(item) => item.offset_data(),
+            Self::Format1(item) => item.offset_data(),
+        }
+    }
+
     /// DeltaSetIndexMap format: set to 0.
     pub fn format(&self) -> u8 {
         match self {


Test patch:
diff --git a/read-fonts/generated/generated_test_formats.rs b/read-fonts/generated/generated_test_formats.rs
index 7c04da12..37f5e3a0 100644
--- a/read-fonts/generated/generated_test_formats.rs
+++ b/read-fonts/generated/generated_test_formats.rs
@@ -229,6 +229,15 @@ pub enum MyTable<'a> {
 }
 
 impl<'a> MyTable<'a> {
+    ///Return the `FontData` used to resolve offsets for this table.
+    pub fn offset_data(&self) -> FontData<'a> {
+        match self {
+            Self::Format1(item) => item.offset_data(),
+            Self::MyFormat22(item) => item.offset_data(),
+            Self::Format3(item) => item.offset_data(),
+        }
+    }
+
     pub fn format(&self) -> u16 {
         match self {
             Self::Format1(item) => item.format(),