Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 25 additions & 9 deletions font-codegen/src/fields.rs
Original file line number Diff line number Diff line change
Expand Up @@ -733,7 +733,11 @@ impl Field {
Some(return_type)
}
}
pub(crate) fn table_getter(&self, generic: Option<&syn::Ident>) -> Option<TokenStream> {
pub(crate) fn table_getter(
&self,
generic: Option<&syn::Ident>,
sanitize: bool,
) -> Option<TokenStream> {
let return_type = self.table_getter_return_type()?;
let name = &self.name;
let is_array = self.is_array();
Expand Down Expand Up @@ -767,7 +771,7 @@ impl Field {
}

let docs = &self.attrs.docs;
let offset_getter = self.typed_offset_field_getter(generic, None);
let offset_getter = self.typed_offset_field_getter(generic, None, sanitize);

Some(quote! {
#( #docs )*
Expand All @@ -780,7 +784,7 @@ impl Field {
})
}

pub(crate) fn record_getter(&self, record: &Record) -> Option<TokenStream> {
pub(crate) fn record_getter(&self, record: &Record, sanitize: bool) -> Option<TokenStream> {
if !self.has_getter() {
return None;
}
Expand Down Expand Up @@ -813,7 +817,7 @@ impl Field {
}
};

let offset_getter = self.typed_offset_field_getter(None, Some(record));
let offset_getter = self.typed_offset_field_getter(None, Some(record), sanitize);
Some(quote! {
#(#docs)*
pub fn #name(&self) -> #add_borrow_just_for_record #return_type {
Expand Down Expand Up @@ -856,6 +860,7 @@ impl Field {
&self,
generic: Option<&syn::Ident>,
record: Option<&Record>,
sanitize: bool,
) -> Option<TokenStream> {
let (offset_type, target) = match &self.typ {
_ if self.attrs.offset_getter.is_some() => return None,
Expand All @@ -871,7 +876,13 @@ impl Field {
let getter_name = self.offset_getter_name().unwrap();
let target_is_generic =
matches!(target, OffsetTarget::Table(ident) if Some(ident) == generic);
let where_read_clause = target_is_generic.then(|| quote!(where T: FontRead<'a, Args = ()>));
let where_read_clause = target_is_generic.then(|| {
if sanitize {
quote!(where T: Sanitize<'a, Args = ()> + Default)
} else {
quote!(where T: FontRead<'a, Args = ()>)
}
});
// if a record, data is passed in
let input_data_if_needed = record.is_some().then(|| quote!(, data: FontData<'a>));
let decl_lifetime_if_needed =
Expand All @@ -890,10 +901,11 @@ impl Field {
let OffsetTarget::Table(target_ident) = target else {
panic!("I don't think arrays of offsets to arrays are in the spec?");
};
let array_type = if self.is_nullable() {
quote!(ArrayOfNullableOffsets)
} else {
quote!(ArrayOfOffsets)
let array_type = match (self.is_nullable(), sanitize) {
(true, true) => quote!(SanitizedArrayOfNullableOffsets),
(true, false) => quote!(ArrayOfNullableOffsets),
(false, true) => quote!(SanitizedArrayOfOffsets),
(false, false) => quote!(ArrayOfOffsets),
};

let target_lifetime = (!target_is_generic).then(|| quote!(<'a>));
Expand Down Expand Up @@ -923,11 +935,15 @@ impl Field {
}
})
} else {
let use_fast_resolve = sanitize && matches!(target, OffsetTarget::Table(_));

let mut return_type = target.getter_return_type(target_is_generic);
if self.is_nullable() || self.attrs.conditional.is_some() {
return_type = quote!(Option<#return_type>);
}
let resolve = match self.attrs.read_offset_args.as_deref() {
None if use_fast_resolve => quote!(fast_resolve(data, ())),
Some(_) if use_fast_resolve => quote!(fast_resolve(data, args)),
None => quote!(resolve(data)),
Some(_) => quote!(resolve_with_args(data, args)),
};
Expand Down
65 changes: 55 additions & 10 deletions font-codegen/src/format_group.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,21 @@ pub(crate) fn generate(item: &TableFormat, items: &Items) -> syn::Result<TokenSt

let sanitize = items.sanitize.then(|| generate_sanitize(item));

let font_read_body = if items.sanitize {
quote! {
Self::read_checked(data, ())
}
} else {
quote! {
let format: #format = data.read_at(#format_offset)?;
#maybe_allow_lint
match format {
#( #match_arms ),*
other => Err(ReadError::InvalidFormat(other.into())),
}
}
};

Ok(quote! {
#( #docs )*
#[derive(Clone)]
Expand All @@ -111,12 +126,7 @@ pub(crate) fn generate(item: &TableFormat, items: &Items) -> syn::Result<TokenSt

impl<'a> FontRead<'a> for #name<'a> {
fn read_with_args(data: FontData<'a>, _: ()) -> Result<Self, ReadError> {
let format: #format = data.read_at(#format_offset)?;
#maybe_allow_lint
match format {
#( #match_arms ),*
other => Err(ReadError::InvalidFormat(other.into())),
}
#font_read_body
}
}

Expand Down Expand Up @@ -169,11 +179,15 @@ fn generate_sanitize(item: &TableFormat) -> TokenStream {
let format = &item.format;
let format_offset = item.format_offset();

let mut has_any_match_stmt = false;
let match_arms: Vec<_> = item
let non_write_only: Vec<_> = item
.variants
.iter()
.filter(|v| v.attrs.write_only.is_none())
.collect();

let mut has_any_match_stmt = false;
let match_arms: Vec<_> = non_write_only
.iter()
.map(|variant| {
let typ = variant.type_name();
let lhs = if let Some(expr) = variant.attrs.match_stmt.as_deref() {
Expand All @@ -187,18 +201,49 @@ fn generate_sanitize(item: &TableFormat) -> TokenStream {
})
.collect();

// `read_fast` dispatches on the format like `sanitize`, but constructs the
// matched variant via its own `read_fast`. This mirrors the `FontRead` body we
// generate for non-sanitized format groups: an unreadable or unknown format
// yields `None` rather than a silent fallback to some other variant.
let fast_read_arms: Vec<_> = non_write_only
.iter()
.map(|variant| {
let var_name = &variant.name;
let typ = variant.type_name();
let lhs = if let Some(expr) = variant.attrs.match_stmt.as_deref() {
let expr = &expr.expr;
quote!(format if #expr)
} else {
quote!(#typ::FORMAT)
};
quote!(#lhs => #typ::read_fast(data, ()).map(#name::#var_name),)
})
.collect();

// soundness: `non_write_only` must be non-empty for the enum to make sense.
let _ = non_write_only.first().expect("format group needs variants");

let maybe_allow_lint = has_any_match_stmt.then(|| quote!(#[allow(clippy::redundant_guards)]));

quote! {
impl Sanitize for #name<'_> {
fn sanitize(ctx: &mut SanitizeContext, _args: ()) -> Result<(), ReadError> {
impl<'a> Sanitize<'a> for #name<'a> {
fn sanitize(ctx: &mut SanitizeContext<'a, '_>, _args: ()) -> Result<(), ReadError> {
let format: #format = ctx.peek_at(#format_offset)?;
#maybe_allow_lint
match format {
#( #match_arms )*
other => Err(ReadError::InvalidFormat(other.into())),
}
}

fn read_fast(data: FontData<'a>, _args: ()) -> Option<Self> {
let format = data.read_at::<#format>(#format_offset).ok()?;
#maybe_allow_lint
match format {
#( #fast_read_arms )*
_ => None,
}
}
}
}
}
Expand Down
33 changes: 29 additions & 4 deletions font-codegen/src/generic_group.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ fn generate_sanitize(item: &GenericGroup, items: &Items) -> Option<TokenStream>
let name = &item.name;
let inner = &item.inner_type;

let match_arms: Vec<_> = item
let sanitize_arms: Vec<_> = item
.variants
.iter()
.map(|var| {
Expand All @@ -120,15 +120,40 @@ fn generate_sanitize(item: &GenericGroup, items: &Items) -> Option<TokenStream>
})
.collect();

// `read_fast` dispatches on the discriminant like `sanitize`, constructing the
// matched variant via its inner table's `read_fast`. As with `sanitize`, an
// unreadable or unknown discriminant yields `None`.
let read_fast_arms: Vec<_> = item
.variants
.iter()
.map(|var| {
let type_id = &var.type_id;
let var_name = &var.name;
let typ = &var.typ;
quote!(#type_id => #inner::<#typ>::read_fast(data, ()).map(#name::#var_name),)
})
.collect();

// soundness: a generic group must have at least one variant.
let _ = item.variants.first().expect("generic group needs variants");

Some(quote! {
impl Sanitize for #name<'_> {
fn sanitize(ctx: &mut SanitizeContext, _args: ()) -> Result<(), ReadError> {
impl<'a> Sanitize<'a> for #name<'a> {
fn sanitize(ctx: &mut SanitizeContext<'a, '_>, _args: ()) -> Result<(), ReadError> {
let discriminant = #inner::read_discriminant(ctx.data())?;
match discriminant {
#( #match_arms )*
#( #sanitize_arms )*
other => Err(ReadError::InvalidFormat(other as _)),
}
}

fn read_fast(data: FontData<'a>, _args: ()) -> Option<Self> {
let discriminant = #inner::read_discriminant(data).ok()?;
match discriminant {
#( #read_fast_arms )*
_ => None,
}
}
}
})
}
Expand Down
4 changes: 3 additions & 1 deletion font-codegen/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,9 @@ pub(crate) fn generate_compile_module(
.iter()
.map(|item| match item {
Item::Record(item) => record::generate_compile(item, &items.parse_module_path),
Item::Table(item) => table::generate_compile(item, &items.parse_module_path),
Item::Table(item) => {
table::generate_compile(item, &items.parse_module_path, items.sanitize)
}
Item::GenericGroup(item) => {
generic_group::generate_compile(item, &items.parse_module_path)
}
Expand Down
7 changes: 5 additions & 2 deletions font-codegen/src/record.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,10 @@ pub(crate) fn generate(item: &Record, all_items: &Items) -> syn::Result<TokenStr
let docs = &fld.attrs.docs;
quote!( #( #docs )* )
});
let getters = item.fields.iter().map(|fld| fld.record_getter(item));
let getters = item
.fields
.iter()
.map(|fld| fld.record_getter(item, all_items.sanitize));
let traversal_impl = generate_traversal(item)?;

let lifetime = &item.lifetime;
Expand Down Expand Up @@ -186,7 +189,7 @@ fn generate_sanitize(item: &Record, needs_read_args: bool) -> syn::Result<TokenS
impl SanitizeStruct for #name #lifetime {
#can_skip

fn sanitize_struct(&self, ctx: &mut SanitizeContext<'_>, #args_arg) -> Result<(), ReadError> {
fn sanitize_struct(&self, ctx: &mut SanitizeContext, #args_arg) -> Result<(), ReadError> {
#destructure_args
#( #stmts )*
ctx.finish()
Expand Down
Loading
Loading