blob: a184a6b85f1f233c905364da21d9acca012e2a8e [file] [edit]
#include "hb-fuzzer.hh"
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <assert.h>
#include <time.h>
#include "hb-subset.h"
#include "hb-ot-cmap-table.hh"
#include <random>
#include <vector>
#ifndef HB_NO_SUBSET_DEPEND
#include <hb.h>
/*
* Depend Parity Checker — validates depend API correctness by comparing closures.
*
* For each font and starting glyph set, this checker:
* 1. Builds a closure by walking the hb_subset_depend_t dependency graph.
* 2. Builds the same closure using HarfBuzz's subset plan (the ground truth).
* 3. Asserts that both closures are identical (or that any extra glyphs in the
* depend closure can be explained by a known over-approximation).
*
* The depend closure mirrors subset's table order: cmap UVS → GSUB → MATH → COLR → glyf/CFF.
*/
/* ============================================================
* Configuration globals (set by main() in standalone mode)
* ============================================================ */
const char *_hb_depend_fuzzer_current_font_path = NULL;
unsigned _hb_depend_fuzzer_seed = 0;
uint64_t _hb_depend_fuzzer_single_test = 0; /* Specific test to run, or 0 for all */
unsigned _hb_depend_fuzzer_num_tests = 1024; /* Number of tests to run */
bool _hb_depend_fuzzer_verbose = false;
bool _hb_depend_fuzzer_report_over_approximation = false;
bool _hb_depend_fuzzer_report_under_approximation = false;
float _hb_depend_fuzzer_inject_over_approx = 0.0f;
float _hb_depend_fuzzer_inject_under_approx = 0.0f;
/* Explicit test mode */
bool _hb_depend_fuzzer_explicit_test = false;
const char *_hb_depend_fuzzer_explicit_unicodes = NULL;
const char *_hb_depend_fuzzer_explicit_gids = NULL;
bool _hb_depend_fuzzer_explicit_no_layout_closure = false;
const char *_hb_depend_fuzzer_explicit_layout_features = NULL;
/* Debug control (only used when HB_DEBUG_DEPEND > 0) */
static hb_codepoint_t debug_gid = HB_CODEPOINT_INVALID;
static bool trace_closure = false;
static bool config_initialized = false;
/* Debug messaging - follows HarfBuzz DEBUG_MSG pattern but simplified for parity checker */
#ifndef HB_DEBUG_DEPEND
#define HB_DEBUG_DEPEND 1
#endif
#if HB_DEBUG_DEPEND
#define DEBUG_MSG_DEPEND(msg, ...) fprintf (stderr, "%-10s" msg "\n", "DEPEND", ##__VA_ARGS__)
#else
#define DEBUG_MSG_DEPEND(msg, ...) do {} while (0)
#endif
/* Read env-var overrides for debug_gid and trace_closure. Called once on first run. */
static void
init_parity_config ()
{
if (config_initialized)
return;
config_initialized = true;
const char *debug_gid_str = getenv ("HB_DEPEND_PARITY_DEBUG_GID");
if (debug_gid_str)
debug_gid = (hb_codepoint_t) strtoul (debug_gid_str, NULL, 0);
const char *trace_closure_str = getenv ("HB_DEPEND_PARITY_TRACE_CLOSURE");
if (trace_closure_str && strcmp (trace_closure_str, "1") == 0)
trace_closure = true;
}
/* ============================================================
* Closure algorithm — depend API side
* ============================================================ */
/**
* deferred_ligature_t:
*
* Records a ligature edge whose dependent glyph cannot be added immediately
* because not all component glyphs are yet in the closure. Stored at deferral
* time so we do not need to re-scan edges when revisiting it.
*/
struct deferred_ligature_t
{
deferred_ligature_t () = default;
deferred_ligature_t (hb_codepoint_t dependent_,
hb_codepoint_t ligature_set_index_,
hb_subset_depend_edge_flags_t flags_)
: dependent (dependent_),
ligature_set_index (ligature_set_index_),
flags (flags_) {}
hb_codepoint_t dependent;
hb_codepoint_t ligature_set_index;
hb_subset_depend_edge_flags_t flags;
};
/* Returns true with probability |prob| using |rng| (always false if rng is null or prob <= 0) */
static bool
rng_prob_hit (std::mt19937 *rng, float prob)
{
if (!rng || prob <= 0.0f)
return false;
return std::uniform_real_distribution<float> (0.0f, 1.0f) (*rng) < prob;
}
/* Context shared across the table-stage calls inside compute_depend_closure */
struct depend_closure_ctx_t
{
hb_subset_depend_t *depend;
hb_set_t *glyphs; /* Accumulating closure set (modified in place) */
hb_set_t *active_features; /* NULL = no feature filter */
bool skip_gsub;
bool *hit_flagged_edge;
std::mt19937 *injection_rng;
std::vector<deferred_ligature_t> deferred_ligatures;
};
/* Check whether all context requirements encoded in |context_set_idx| are met.
*
* Elements in a context set can be:
* - Direct GID (value < 0x80000000): that specific glyph must be in the closure.
* - Indirect set reference (value >= 0x80000000): at least one glyph from the
* referenced set must be in the closure.
*
* Returns true if ALL requirements are satisfied (or if context_set_idx is invalid). */
static bool
check_context_satisfied (hb_subset_depend_t *depend,
hb_codepoint_t context_set_idx,
hb_set_t *current_closure)
{
if (context_set_idx == HB_CODEPOINT_INVALID)
return true;
hb_set_t *context_elements = hb_set_create ();
if (!hb_subset_depend_lookup_set (depend, context_set_idx, context_elements))
{
hb_set_destroy (context_elements);
return true; /* Invalid context set, don't filter */
}
bool satisfied = true;
hb_codepoint_t elem = HB_SET_VALUE_INVALID;
while (hb_set_next (context_elements, &elem))
{
if (elem < 0x80000000)
{
/* Direct GID reference */
if (!hb_set_has (current_closure, elem))
{
satisfied = false;
break;
}
}
else
{
/* Indirect set reference — at least one glyph from the set must be present */
hb_codepoint_t set_idx = elem & 0x7FFFFFFF;
hb_set_t *required_glyphs = hb_set_create ();
if (!hb_subset_depend_lookup_set (depend, set_idx, required_glyphs))
{
hb_set_destroy (required_glyphs);
continue; /* Invalid set reference, skip this requirement */
}
bool any_present = false;
hb_codepoint_t gid = HB_SET_VALUE_INVALID;
while (hb_set_next (required_glyphs, &gid))
{
if (hb_set_has (current_closure, gid))
{
any_present = true;
break;
}
}
hb_set_destroy (required_glyphs);
if (!any_present)
{
satisfied = false;
break;
}
}
}
hb_set_destroy (context_elements);
return satisfied;
}
/* Process edges for glyphs in |to_process| that belong to tables in |table_filter|.
* Appends newly reachable glyphs to ctx.glyphs and back to |to_process| for further expansion.
* Returns true if any new glyphs were added. */
static bool
process_edges_for_tables (depend_closure_ctx_t &ctx,
hb_set_t *to_process,
hb_set_t *table_filter)
{
bool added_any = false;
while (!hb_set_is_empty (to_process))
{
hb_codepoint_t gid = hb_set_get_min (to_process);
hb_set_del (to_process, gid);
if (gid == debug_gid)
DEBUG_MSG_DEPEND ("Processing glyph %u", gid);
unsigned int total = hb_subset_depend_lookup_glyph (ctx.depend, gid, 0, nullptr, nullptr);
for (unsigned int idx = 0; idx < total; idx++)
{
unsigned int count = 1;
hb_subset_depend_entry_t entry;
hb_subset_depend_lookup_glyph (ctx.depend, gid, idx, &count, &entry);
hb_tag_t table_tag = entry.table_tag;
hb_codepoint_t dependent = entry.dependent;
hb_tag_t layout_tag = entry.layout_tag;
hb_codepoint_t ligature_set = entry.ligature_set_index;
hb_codepoint_t context_set = entry.context_set_index;
hb_subset_depend_edge_flags_t flags = entry.flags;
/* Debug: trace all edges touching the watched glyph */
if (debug_gid != HB_CODEPOINT_INVALID &&
(gid == debug_gid || dependent == debug_gid))
DEBUG_MSG_DEPEND ("Considering edge: %u -> %u (feature=%c%c%c%c, ctx=%u)",
gid, dependent, HB_UNTAG (layout_tag), context_set);
/* Filter by table if requested */
if (table_filter && !hb_set_has (table_filter, table_tag))
continue;
if (dependent == debug_gid || gid == debug_gid)
DEBUG_MSG_DEPEND ("Edge: %u -> %u (table=%c%c%c%c, ligset=%u)",
gid, dependent, HB_UNTAG (table_tag), ligature_set);
/* UVS closure is handled via the cmap accelerator, not depend edges */
if (table_tag == HB_TAG ('c','m','a','p'))
continue;
/* Skip GSUB edges when requested */
if (ctx.skip_gsub && table_tag == HB_OT_TAG_GSUB)
continue;
/* Filter GSUB edges by active features; over-approx injection can bypass the filter */
if (ctx.active_features && table_tag == HB_OT_TAG_GSUB &&
!hb_set_has (ctx.active_features, layout_tag))
{
if (!rng_prob_hit (ctx.injection_rng, _hb_depend_fuzzer_inject_over_approx))
{
if (dependent == debug_gid)
DEBUG_MSG_DEPEND ("Skipping edge %u -> %u: feature %c%c%c%c not active",
gid, dependent, HB_UNTAG (layout_tag));
continue;
}
}
/* Skip edges whose context requirements are unsatisfied; over-approx injection can bypass */
if (context_set != HB_CODEPOINT_INVALID &&
!check_context_satisfied (ctx.depend, context_set, ctx.glyphs))
{
if (!rng_prob_hit (ctx.injection_rng, _hb_depend_fuzzer_inject_over_approx))
{
if (dependent == debug_gid)
DEBUG_MSG_DEPEND ("Skipping edge %u -> %u: context not satisfied (ctx_set=%u)",
gid, dependent, context_set);
continue;
}
}
if (ligature_set != HB_CODEPOINT_INVALID)
{
/* Defer: revisit once we know whether all components are in the closure.
* Feature filtering has already been applied above. */
ctx.deferred_ligatures.push_back (deferred_ligature_t (dependent, ligature_set, flags));
}
else
{
/* Under-approx injection: randomly drop this edge */
if (rng_prob_hit (ctx.injection_rng, _hb_depend_fuzzer_inject_under_approx))
continue;
if (!hb_set_has (ctx.glyphs, dependent))
{
if (ctx.hit_flagged_edge && (flags & 0x03))
*ctx.hit_flagged_edge = true;
if (dependent == debug_gid)
DEBUG_MSG_DEPEND ("Adding glyph %u to closure (from %u, feature=%c%c%c%c, ctx=%u)",
dependent, gid, HB_UNTAG (layout_tag), context_set);
if (trace_closure)
DEBUG_MSG_DEPEND ("Depend: %u -> %u (table=%c%c%c%c, feature=%c%c%c%c)",
gid, dependent, HB_UNTAG (table_tag), HB_UNTAG (layout_tag));
hb_set_add (ctx.glyphs, dependent);
hb_set_add (to_process, dependent);
added_any = true;
}
}
}
}
return added_any;
}
/* Compute closure using the depend API by following the dependency graph.
*
* On entry ctx.glyphs is the initial glyph set; on exit it holds the full closure.
* input_unicodes (may be NULL) is used for UVS (cmap format 14) handling only.
* Closure is built in table order: cmap UVS → GSUB → MATH → COLR → glyf/CFF.
* There is no loop back to GSUB after glyf/CFF: component glyphs are rendering
* details, not shaping inputs, and subset doesn't loop back either. */
static void
compute_depend_closure (hb_face_t *face, depend_closure_ctx_t &ctx,
const hb_set_t *input_unicodes)
{
if (debug_gid != HB_CODEPOINT_INVALID)
DEBUG_MSG_DEPEND ("Starting closure, glyph %u %s in initial set",
debug_gid,
hb_set_has (ctx.glyphs, debug_gid) ? "IS" : "is NOT");
if (trace_closure)
{
hb_codepoint_t gid = HB_SET_VALUE_INVALID;
fprintf (stderr, "DEPEND: Initial set:");
while (hb_set_next (ctx.glyphs, &gid))
fprintf (stderr, " %u", gid);
fprintf (stderr, "\n");
}
hb_set_t *to_process = hb_set_create ();
/* Stage 1: UVS closure (cmap format 14), processed before GSUB to match subset's order.
* For each (base unicode, variation selector) pair present in input_unicodes,
* look up the variant glyph and add it to the closure. */
if (input_unicodes)
{
hb_set_t *all_vs = hb_set_create ();
hb_face_collect_variation_selectors (face, all_vs);
hb_set_intersect (all_vs, input_unicodes);
if (!hb_set_is_empty (all_vs))
{
hb_font_t *font = hb_font_create (face);
hb_codepoint_t vs = HB_SET_VALUE_INVALID;
while (hb_set_next (all_vs, &vs))
{
hb_codepoint_t base = HB_SET_VALUE_INVALID;
while (hb_set_next (input_unicodes, &base))
{
if (hb_set_has (all_vs, base))
continue; /* base is itself a VS, skip */
hb_codepoint_t variant_glyph;
if (hb_font_get_variation_glyph (font, base, vs, &variant_glyph))
{
hb_set_add (ctx.glyphs, variant_glyph);
if (trace_closure && variant_glyph == debug_gid)
DEBUG_MSG_DEPEND ("UVS: Added glyph %u via U+%04X+U+%04X",
variant_glyph, base, vs);
}
}
}
hb_font_destroy (font);
}
hb_set_destroy (all_vs);
if (trace_closure)
DEBUG_MSG_DEPEND ("After UVS (cmap format 14): closure has %u glyphs",
hb_set_get_population (ctx.glyphs));
}
/* Stage 2: GSUB closure — iterate until stable, matching subset's internal behavior.
* Ligatures are resolved here (not after glyf/CFF): add a ligature only once all of
* its required components are in the closure. */
if (!ctx.skip_gsub)
{
hb_set_t *gsub_filter = hb_set_create ();
hb_set_add (gsub_filter, HB_OT_TAG_GSUB);
hb_set_t *ligature_set_glyphs = hb_set_create ();
unsigned prev_gsub_count;
unsigned iteration = 0;
do {
prev_gsub_count = hb_set_get_population (ctx.glyphs);
hb_set_union (to_process, ctx.glyphs);
process_edges_for_tables (ctx, to_process, gsub_filter);
/* Drain deferred ligatures: add each ligature whose component set is now fully satisfied */
bool lig_made_progress = true;
while (lig_made_progress && !ctx.deferred_ligatures.empty ())
{
lig_made_progress = false;
for (size_t i = 0; i < ctx.deferred_ligatures.size (); )
{
const deferred_ligature_t &def = ctx.deferred_ligatures[i];
hb_set_clear (ligature_set_glyphs);
hb_subset_depend_lookup_set (ctx.depend, def.ligature_set_index, ligature_set_glyphs);
bool set_satisfied = hb_set_is_subset (ligature_set_glyphs, ctx.glyphs);
/* Over-approx injection: pretend the set is satisfied even when it isn't */
if (!set_satisfied)
set_satisfied = rng_prob_hit (ctx.injection_rng, _hb_depend_fuzzer_inject_over_approx);
if (set_satisfied)
{
/* Under-approx injection: skip adding this ligature */
if (!rng_prob_hit (ctx.injection_rng, _hb_depend_fuzzer_inject_under_approx) &&
!hb_set_has (ctx.glyphs, def.dependent))
{
if (ctx.hit_flagged_edge && (def.flags & 0x03))
*ctx.hit_flagged_edge = true;
if (trace_closure)
DEBUG_MSG_DEPEND ("Adding GSUB ligature %u (components satisfied, flags=0x%02x)",
def.dependent, def.flags);
hb_set_add (ctx.glyphs, def.dependent);
lig_made_progress = true;
}
/* Erase by swapping with the last element */
ctx.deferred_ligatures[i] = ctx.deferred_ligatures.back ();
ctx.deferred_ligatures.pop_back ();
}
else
i++;
}
}
iteration++;
} while (hb_set_get_population (ctx.glyphs) > prev_gsub_count && iteration < 64);
hb_set_destroy (ligature_set_glyphs);
hb_set_destroy (gsub_filter);
if (trace_closure)
DEBUG_MSG_DEPEND ("After GSUB (%u iterations): closure has %u glyphs",
iteration, hb_set_get_population (ctx.glyphs));
}
/* Stage 3: MATH closure */
{
hb_set_t *math_filter = hb_set_create ();
hb_set_add (math_filter, HB_OT_TAG_MATH);
hb_set_union (to_process, ctx.glyphs);
process_edges_for_tables (ctx, to_process, math_filter);
hb_set_destroy (math_filter);
if (trace_closure)
DEBUG_MSG_DEPEND ("After MATH: closure has %u glyphs", hb_set_get_population (ctx.glyphs));
}
/* Stage 4: COLR closure */
{
hb_set_t *colr_filter = hb_set_create ();
hb_set_add (colr_filter, HB_TAG ('C','O','L','R'));
hb_set_union (to_process, ctx.glyphs);
process_edges_for_tables (ctx, to_process, colr_filter);
hb_set_destroy (colr_filter);
if (trace_closure)
DEBUG_MSG_DEPEND ("After COLR: closure has %u glyphs", hb_set_get_population (ctx.glyphs));
}
/* Stage 5: glyf/CFF closure */
{
hb_set_t *glyf_filter = hb_set_create ();
hb_set_add (glyf_filter, HB_TAG ('g','l','y','f'));
hb_set_add (glyf_filter, HB_TAG ('C','F','F',' '));
hb_set_union (to_process, ctx.glyphs);
process_edges_for_tables (ctx, to_process, glyf_filter);
hb_set_destroy (glyf_filter);
if (trace_closure)
DEBUG_MSG_DEPEND ("After glyf/CFF: closure has %u glyphs", hb_set_get_population (ctx.glyphs));
}
hb_set_destroy (to_process);
}
/* ============================================================
* Closure algorithm — subset side (ground truth)
* ============================================================ */
/* Compute closure using the HarfBuzz subset mechanism.
* If active_features is non-NULL and empty on entry, it is populated with the
* subset's default features so the depend side can use the same set. */
static void
compute_subset_closure (hb_face_t *face, hb_set_t *glyphs, bool skip_gsub,
hb_set_t *codepoints, hb_set_t *active_features)
{
hb_subset_input_t *input = hb_subset_input_create_or_fail ();
if (!input) return;
unsigned flags = HB_SUBSET_FLAGS_NO_BIDI_CLOSURE;
if (skip_gsub)
flags |= HB_SUBSET_FLAGS_NO_LAYOUT_CLOSURE;
hb_subset_input_set_flags (input, (hb_subset_flags_t) flags);
/* Populate input — either from codepoints or glyph IDs */
if (codepoints && !hb_set_is_empty (codepoints))
{
hb_set_t *input_codepoints = hb_subset_input_set (input, HB_SUBSET_SETS_UNICODE);
hb_set_union (input_codepoints, codepoints);
}
else
{
hb_set_t *input_glyphs = hb_subset_input_glyph_set (input);
hb_set_union (input_glyphs, glyphs);
}
/* Handle features:
* - active_features NULL: use subset defaults, don't capture them
* - active_features non-empty: clear defaults and use the specified set
* - active_features empty: capture defaults into it (output parameter) */
if (active_features && !skip_gsub)
{
hb_set_t *input_features = hb_subset_input_set (input, HB_SUBSET_SETS_LAYOUT_FEATURE_TAG);
if (hb_set_is_empty (active_features))
hb_set_union (active_features, input_features);
else
{
hb_set_clear (input_features);
hb_set_union (input_features, active_features);
}
}
hb_subset_plan_t *plan = hb_subset_plan_create_or_fail (face, input);
if (!plan)
{
hb_subset_input_destroy (input);
return;
}
/* Extract the retained glyph set from the plan's glyph mapping */
hb_map_t *glyph_map = hb_subset_plan_old_to_new_glyph_mapping (plan);
hb_set_clear (glyphs);
int idx = -1;
hb_codepoint_t key, value;
while (hb_map_next (glyph_map, &idx, &key, &value))
hb_set_add (glyphs, key);
if (debug_gid != HB_CODEPOINT_INVALID)
DEBUG_MSG_DEPEND ("Subset closure %s glyph %u",
hb_set_has (glyphs, debug_gid) ? "INCLUDES" : "does not include",
debug_gid);
hb_subset_plan_destroy (plan);
hb_subset_input_destroy (input);
}
/* ============================================================
* Comparison and reporting
* ============================================================ */
/* Print the parameters of a single test to stderr */
static void
print_test_params (uint64_t seed, bool is_gsub,
hb_set_t *start_glyphs, hb_set_t *codepoints, hb_set_t *features)
{
fprintf (stderr, "Test: seed=0x%016llx\n", (unsigned long long) seed);
fprintf (stderr, "Test type: %s\n", is_gsub ? "GSUB" : "non-GSUB");
if (start_glyphs && !hb_set_is_empty (start_glyphs))
{
fprintf (stderr, "Starting glyphs (%u): ", hb_set_get_population (start_glyphs));
hb_codepoint_t gid = HB_SET_VALUE_INVALID;
unsigned count = 0;
while (hb_set_next (start_glyphs, &gid) && count < 20)
{
fprintf (stderr, "%u ", gid);
count++;
}
if (count == 20 && hb_set_get_population (start_glyphs) > 20)
fprintf (stderr, "...");
fprintf (stderr, "\n");
}
if (codepoints && !hb_set_is_empty (codepoints))
{
fprintf (stderr, "Starting codepoints (%u): ", hb_set_get_population (codepoints));
hb_codepoint_t cp = HB_SET_VALUE_INVALID;
unsigned count = 0;
while (hb_set_next (codepoints, &cp) && count < 20)
{
fprintf (stderr, "U+%04X ", cp);
count++;
}
if (count == 20 && hb_set_get_population (codepoints) > 20)
fprintf (stderr, "...");
fprintf (stderr, "\n");
}
if (features && !hb_set_is_empty (features))
{
fprintf (stderr, "Active features (%u): ", hb_set_get_population (features));
hb_codepoint_t feature = HB_SET_VALUE_INVALID;
unsigned count = 0;
while (hb_set_next (features, &feature) && count < 10)
{
fprintf (stderr, "%c%c%c%c ", HB_UNTAG ((hb_tag_t) feature));
count++;
}
if (count == 10 && hb_set_get_population (features) > 10)
fprintf (stderr, "...");
fprintf (stderr, "\n");
}
}
/* Compare the depend-API closure against the subset-plan closure for one test case.
*
* Under-approximation (depend missed glyphs subset found) is always a bug: assert.
* Over-approximation (depend found extra glyphs) is expected when a flagged edge
* (FROM_CONTEXT_POSITION or FROM_NESTED_CONTEXT) was traversed, since depend cannot
* know whether the intermediate glyph consumer will actually be present; otherwise
* it is also a bug.
*
* Returns true if the two closures are identical. */
static bool
compare_closures (hb_face_t *face, hb_subset_depend_t *depend,
uint64_t seed, const char *font_path,
bool is_gsub, hb_set_t *start_glyphs, hb_set_t *codepoints,
hb_set_t *test_features, hb_map_t *unicode_to_glyph_cache)
{
bool skip_gsub = !is_gsub;
/* --- Subset closure (ground truth) --- */
hb_set_t *subset_closure = hb_set_create ();
hb_set_union (subset_closure, start_glyphs);
/* active_features: pre-populate if caller supplied features, else let subset fill defaults */
hb_set_t *active_features = hb_set_create ();
if (test_features)
hb_set_union (active_features, test_features);
compute_subset_closure (face, subset_closure, skip_gsub, codepoints, active_features);
if (trace_closure)
{
DEBUG_MSG_DEPEND ("Subset: Final closure has %u glyphs, checking for 82 and 124...",
hb_set_get_population (subset_closure));
DEBUG_MSG_DEPEND ("Subset: glyph 82 ('o') %s in closure",
hb_set_has (subset_closure, 82) ? "IS" : "is NOT");
DEBUG_MSG_DEPEND ("Subset: glyph 124 (ordmasculine) %s in closure",
hb_set_has (subset_closure, 124) ? "IS" : "is NOT");
}
/* --- Depend closure --- */
/* For GSUB tests starting from codepoints, map codepoints → glyphs via cmap only
* (not glyf/COLR/MATH), matching subset's own ordering. */
hb_set_t *actual_start_glyphs = hb_set_create ();
if (is_gsub && codepoints && !hb_set_is_empty (codepoints))
{
hb_set_add (actual_start_glyphs, 0); /* .notdef */
unsigned num_glyphs = hb_face_get_glyph_count (face);
hb_font_t *font = hb_font_create (face);
hb_codepoint_t cp = HB_SET_VALUE_INVALID;
while (hb_set_next (codepoints, &cp))
{
hb_codepoint_t gid;
if (hb_font_get_nominal_glyph (font, cp, &gid) && gid < num_glyphs)
hb_set_add (actual_start_glyphs, gid);
}
hb_font_destroy (font);
}
else
hb_set_union (actual_start_glyphs, start_glyphs);
hb_set_t *depend_closure = hb_set_create ();
hb_set_union (depend_closure, actual_start_glyphs);
hb_set_add (depend_closure, 0); /* subset always adds .notdef; see hb-subset-plan.cc:451 */
if (debug_gid != HB_CODEPOINT_INVALID && active_features)
{
hb_codepoint_t feat = HB_SET_VALUE_INVALID;
fprintf (stderr, "DEPEND: Active features (%u):", hb_set_get_population (active_features));
while (hb_set_next (active_features, &feat))
fprintf (stderr, " %c%c%c%c", HB_UNTAG (feat));
fprintf (stderr, "\n");
}
/* Seed RNG for reproducible error injection: same seed → same edges skipped/added */
std::mt19937 injection_rng_storage;
std::mt19937 *injection_rng = nullptr;
if (_hb_depend_fuzzer_inject_over_approx > 0.0f ||
_hb_depend_fuzzer_inject_under_approx > 0.0f)
{
injection_rng_storage.seed ((unsigned) seed);
injection_rng = &injection_rng_storage;
}
/* Build the unicode set for UVS (cmap format 14) closure.
* GSUB tests: use input codepoints directly.
* Non-GSUB tests: reverse-map starting glyphs to their unicodes
* (matches subset's behavior at hb-subset-plan.cc:310-319). */
hb_set_t *uvs_unicodes = hb_set_create ();
if (is_gsub && codepoints && !hb_set_is_empty (codepoints))
{
hb_set_union (uvs_unicodes, codepoints);
}
else
{
hb_map_t *unicode_to_glyph = unicode_to_glyph_cache;
if (!unicode_to_glyph)
{
unicode_to_glyph = hb_map_create ();
hb_face_collect_nominal_glyph_mapping (face, unicode_to_glyph, NULL);
}
int idx = -1;
hb_codepoint_t unicode, glyph;
while (hb_map_next (unicode_to_glyph, &idx, &unicode, &glyph))
if (hb_set_has (actual_start_glyphs, glyph))
hb_set_add (uvs_unicodes, unicode);
if (!unicode_to_glyph_cache)
hb_map_destroy (unicode_to_glyph);
}
bool hit_flagged_edge = false;
depend_closure_ctx_t ctx;
ctx.depend = depend;
ctx.glyphs = depend_closure;
ctx.active_features = skip_gsub ? nullptr : active_features;
ctx.skip_gsub = skip_gsub;
ctx.hit_flagged_edge = &hit_flagged_edge;
ctx.injection_rng = injection_rng;
compute_depend_closure (face, ctx, uvs_unicodes);
hb_set_destroy (uvs_unicodes);
hb_set_destroy (actual_start_glyphs);
/* --- Compare --- */
bool match = hb_set_is_equal (depend_closure, subset_closure);
hb_set_t *in_depend_not_subset = hb_set_create ();
hb_set_t *in_subset_not_depend = hb_set_create ();
hb_set_set (in_depend_not_subset, depend_closure);
hb_set_subtract (in_depend_not_subset, subset_closure);
hb_set_set (in_subset_not_depend, subset_closure);
hb_set_subtract (in_subset_not_depend, depend_closure);
/* Under-approximation: depend missed glyphs that subset found */
if (!hb_set_is_empty (in_subset_not_depend))
{
if (_hb_depend_fuzzer_report_under_approximation ||
_hb_depend_fuzzer_report_over_approximation ||
_hb_depend_fuzzer_verbose)
{
fprintf (stderr, "Test 0x%016llx: UNDER-APPROX - Depend missed %u glyphs:",
(unsigned long long) seed, hb_set_get_population (in_subset_not_depend));
hb_codepoint_t gid = HB_SET_VALUE_INVALID;
unsigned count = 0;
while (hb_set_next (in_subset_not_depend, &gid) && count < 20)
{
fprintf (stderr, " %u", gid);
count++;
}
if (count == 20)
fprintf (stderr, " ...");
fprintf (stderr, "\n");
}
if (!_hb_depend_fuzzer_report_under_approximation)
assert (0 && "Depend API missed glyphs that subset found");
}
/* Over-approximation: depend found extra glyphs not in subset closure.
* Expected when a flagged (contextual-position) edge was traversed; a bug otherwise. */
if (!hb_set_is_empty (in_depend_not_subset))
{
if (hit_flagged_edge)
{
if (_hb_depend_fuzzer_verbose || _hb_depend_fuzzer_report_over_approximation)
{
if (_hb_depend_fuzzer_report_over_approximation && !_hb_depend_fuzzer_verbose)
{
fprintf (stderr, "Test 0x%016llx: Depend found %u extra glyphs (expected, hit flagged edge):",
(unsigned long long) seed, hb_set_get_population (in_depend_not_subset));
hb_codepoint_t gid = HB_SET_VALUE_INVALID;
while (hb_set_next (in_depend_not_subset, &gid))
fprintf (stderr, " %u", gid);
fprintf (stderr, "\n");
}
else
{
fprintf (stderr, "\n=== DEPEND API OVER-APPROXIMATION (EXPECTED) ===\n");
fprintf (stderr, "Font: %s\n", font_path ? font_path : "(unknown)");
fprintf (stderr, "Test seed: 0x%016llx\n", (unsigned long long) seed);
fprintf (stderr, "Depend found %u extra glyphs not in subset closure:\n",
hb_set_get_population (in_depend_not_subset));
hb_codepoint_t gid = HB_SET_VALUE_INVALID;
unsigned count = 0;
while (hb_set_next (in_depend_not_subset, &gid) && count < 50)
{
fprintf (stderr, " %u", gid);
count++;
}
if (count == 50)
fprintf (stderr, " ...");
fprintf (stderr, "\n");
fprintf (stderr, "Note: Hit flagged edge from contextual position (intermediate glyph consumer).\n");
fprintf (stderr, "This is expected over-approximation, not a bug.\n");
fprintf (stderr, "===============================================\n\n");
}
}
}
else
{
/* Unexpected: no flagged edge, but depend still has extra glyphs.
*
* Before asserting, check whether this is a known self-feeding chain:
* a GSUB lookup whose output coverage overlaps its input coverage,
* causing the transitive depend closure to follow the chain further
* than the subsetter's iteration-capped closure. This is harmless
* over-approximation — no shaper re-applies a lookup to its own
* output within a single feature application. */
bool explained_by_self_feeding = false;
if (!skip_gsub && active_features)
{
/* Check each active feature for self-feeding: a feature where any
* dependent glyph also appears as a source glyph. */
hb_set_t *self_feeding_features = hb_set_create ();
unsigned num_glyphs = hb_face_get_glyph_count (face);
hb_codepoint_t feat_tag = HB_SET_VALUE_INVALID;
while (hb_set_next (active_features, &feat_tag))
{
hb_set_t *sources = hb_set_create ();
hb_set_t *dependents = hb_set_create ();
for (hb_codepoint_t gid = 0; gid < num_glyphs; gid++)
{
unsigned total = hb_subset_depend_lookup_glyph (depend, gid, 0, nullptr, nullptr);
for (unsigned j = 0; j < total; j++)
{
unsigned cnt = 1;
hb_subset_depend_entry_t entry;
hb_subset_depend_lookup_glyph (depend, gid, j, &cnt, &entry);
if (entry.table_tag != HB_OT_TAG_GSUB) continue;
if (entry.layout_tag != feat_tag) continue;
hb_set_add (sources, gid);
hb_set_add (dependents, entry.dependent);
}
}
hb_set_intersect (sources, dependents);
if (!hb_set_is_empty (sources))
hb_set_add (self_feeding_features, feat_tag);
hb_set_destroy (sources);
hb_set_destroy (dependents);
}
if (!hb_set_is_empty (self_feeding_features))
{
/* Check that every extra glyph is reachable from the subset closure
* through a chain of edges within a self-feeding feature. */
hb_set_t *reachable = hb_set_create ();
hb_set_union (reachable, subset_closure);
bool changed;
do
{
changed = false;
hb_codepoint_t gid = HB_SET_VALUE_INVALID;
while (hb_set_next (reachable, &gid))
{
unsigned total = hb_subset_depend_lookup_glyph (depend, gid, 0, nullptr, nullptr);
for (unsigned j = 0; j < total; j++)
{
unsigned cnt = 1;
hb_subset_depend_entry_t e;
hb_subset_depend_lookup_glyph (depend, gid, j, &cnt, &e);
if (e.table_tag != HB_OT_TAG_GSUB) continue;
if (!hb_set_has (self_feeding_features, e.layout_tag)) continue;
if (!hb_set_has (reachable, e.dependent))
{
hb_set_add (reachable, e.dependent);
changed = true;
}
}
}
} while (changed);
/* If every extra glyph is explained by self-feeding chains,
* this is known over-approximation. */
hb_set_t *unexplained = hb_set_create ();
hb_set_union (unexplained, in_depend_not_subset);
hb_set_subtract (unexplained, reachable);
if (hb_set_is_empty (unexplained))
explained_by_self_feeding = true;
hb_set_destroy (unexplained);
hb_set_destroy (reachable);
}
hb_set_destroy (self_feeding_features);
}
if (explained_by_self_feeding)
{
if (_hb_depend_fuzzer_verbose || _hb_depend_fuzzer_report_over_approximation)
fprintf (stderr, "Test 0x%016llx: Depend found %u extra glyphs (explained by self-feeding GSUB chain)\n",
(unsigned long long) seed, hb_set_get_population (in_depend_not_subset));
}
else
{
if (_hb_depend_fuzzer_report_under_approximation ||
_hb_depend_fuzzer_report_over_approximation ||
_hb_depend_fuzzer_verbose)
{
fprintf (stderr, "Test 0x%016llx: UNEXPECTED OVER-APPROX - Depend found %u extra glyphs WITHOUT hitting flagged edge:",
(unsigned long long) seed, hb_set_get_population (in_depend_not_subset));
hb_codepoint_t gid = HB_SET_VALUE_INVALID;
unsigned count = 0;
while (hb_set_next (in_depend_not_subset, &gid) && count < 20)
{
fprintf (stderr, " %u", gid);
count++;
}
if (count == 20)
fprintf (stderr, " ...");
fprintf (stderr, "\n");
}
if (!_hb_depend_fuzzer_report_under_approximation)
assert (0 && "Depend API has unexpected over-approximation without flagged edges");
}
}
}
hb_set_destroy (in_subset_not_depend);
hb_set_destroy (in_depend_not_subset);
hb_set_destroy (active_features);
hb_set_destroy (subset_closure);
hb_set_destroy (depend_closure);
return match;
}
/* ============================================================
* Random sampling utilities
* ============================================================ */
/* Use std::mt19937 for deterministic, cross-platform random behaviour */
static unsigned
rng_range (std::mt19937 &rng, unsigned min, unsigned max)
{
if (max <= min)
return min;
std::uniform_int_distribution<unsigned> dist (min, max);
return dist (rng);
}
/* Select n random distinct values from range [0, max) */
static void
rng_select_distinct (std::mt19937 &rng, hb_set_t *out, unsigned n, unsigned max)
{
if (n >= max)
{
for (unsigned i = 0; i < max; i++)
hb_set_add (out, i);
return;
}
while (hb_set_get_population (out) < n)
hb_set_add (out, rng_range (rng, 0, max - 1));
}
/* Select n random distinct values from an existing set */
static void
rng_select_from_set (std::mt19937 &rng, hb_set_t *out, unsigned n, const hb_set_t *source)
{
unsigned source_size = hb_set_get_population (source);
if (n == 0 || source_size == 0)
return;
if (n >= source_size)
{
hb_set_union (out, source);
return;
}
/* Convert source to array for indexed access */
hb_codepoint_t *array = (hb_codepoint_t *) malloc (source_size * sizeof (hb_codepoint_t));
unsigned idx = 0;
hb_codepoint_t val = HB_SET_VALUE_INVALID;
while (hb_set_next (source, &val))
array[idx++] = val;
while (hb_set_get_population (out) < n)
hb_set_add (out, array[rng_range (rng, 0, source_size - 1)]);
free (array);
}
/* ============================================================
* Font and input queries
* ============================================================ */
/* Return a newly-allocated set containing all GSUB feature tags for |face| */
static hb_set_t *
get_gsub_features (hb_face_t *face)
{
hb_set_t *features = hb_set_create ();
if (!hb_ot_layout_has_substitution (face))
return features;
unsigned int feature_count = hb_ot_layout_table_get_feature_tags (face,
HB_OT_TAG_GSUB,
0, nullptr, nullptr);
hb_tag_t *feature_tags = (hb_tag_t *) malloc (feature_count * sizeof (hb_tag_t));
if (feature_tags)
{
hb_ot_layout_table_get_feature_tags (face,
HB_OT_TAG_GSUB,
0, &feature_count, feature_tags);
for (unsigned int i = 0; i < feature_count; i++)
hb_set_add (features, feature_tags[i]);
free (feature_tags);
}
return features;
}
/* Parse comma-separated list of unicodes (U+XXXX, decimal, or ranges) into a set */
static bool
parse_unicodes (const char *str, hb_set_t *unicodes)
{
if (!str || !*str)
return false;
const char *p = str;
while (*p)
{
while (*p == ' ' || *p == ',')
p++;
if (!*p)
break;
const char *start = p;
hb_codepoint_t start_cp;
if (*p == 'U' && *(p + 1) == '+')
start_cp = (hb_codepoint_t) strtoul (p + 2, (char **) &p, 16);
else
start_cp = (hb_codepoint_t) strtoul (p, (char **) &p, 10);
if (p == start || (p == start + 2 && start[0] == 'U' && start[1] == '+'))
{
fprintf (stderr, "Error: Invalid unicode value at: %.20s\n", start);
return false;
}
if (*p == '-')
{
p++;
const char *end_start = p;
hb_codepoint_t end_cp;
if (*p == 'U' && *(p + 1) == '+')
end_cp = (hb_codepoint_t) strtoul (p + 2, (char **) &p, 16);
else
end_cp = (hb_codepoint_t) strtoul (p, (char **) &p, 10);
if (p == end_start || (p == end_start + 2 && end_start[0] == 'U' && end_start[1] == '+'))
{
fprintf (stderr, "Error: Invalid unicode value in range at: %.20s\n", end_start);
return false;
}
hb_set_add_range (unicodes, start_cp, end_cp);
}
else
hb_set_add (unicodes, start_cp);
}
return !hb_set_is_empty (unicodes);
}
/* Parse comma-separated list of glyph IDs (decimal or ranges) into a set */
static bool
parse_gids (const char *str, hb_set_t *gids)
{
if (!str || !*str)
return false;
const char *p = str;
while (*p)
{
while (*p == ' ' || *p == ',')
p++;
if (!*p)
break;
const char *start = p;
hb_codepoint_t start_gid = (hb_codepoint_t) strtoul (p, (char **) &p, 10);
if (p == start)
{
fprintf (stderr, "Error: Invalid glyph ID at: %.20s\n", start);
return false;
}
if (*p == '-')
{
p++;
const char *end_start = p;
hb_codepoint_t end_gid = (hb_codepoint_t) strtoul (p, (char **) &p, 10);
if (p == end_start)
{
fprintf (stderr, "Error: Invalid glyph ID in range at: %.20s\n", end_start);
return false;
}
hb_set_add_range (gids, start_gid, end_gid);
}
else
hb_set_add (gids, start_gid);
}
return !hb_set_is_empty (gids);
}
/* Parse comma-separated list of feature tags into a set.
* The special value "*" means all features from the face. */
static bool
parse_features (const char *str, hb_set_t *features, hb_face_t *face)
{
if (!str || !*str)
return false;
if (strcmp (str, "*") == 0)
{
hb_set_t *all_features = get_gsub_features (face);
hb_set_union (features, all_features);
hb_set_destroy (all_features);
return true;
}
const char *p = str;
while (*p)
{
while (*p == ' ' || *p == ',')
p++;
if (!*p)
break;
char tag_str[5] = {0};
int i = 0;
while (*p && *p != ',' && *p != ' ' && i < 4)
tag_str[i++] = *p++;
if (i > 0)
{
while (i < 4)
tag_str[i++] = ' ';
tag_str[4] = '\0';
hb_set_add (features, HB_TAG (tag_str[0], tag_str[1], tag_str[2], tag_str[3]));
}
}
return !hb_set_is_empty (features);
}
/* ============================================================
* Test parameter generation
* ============================================================ */
/* Test parameters generated from a 64-bit seed or explicit command-line args */
struct test_params
{
bool is_gsub; /* GSUB test (codepoint-based) vs non-GSUB (glyph-based) */
unsigned target_size; /* Target set size (may be clamped by font size) */
hb_set_t *glyphs; /* Starting glyphs (NULL on failure) */
hb_set_t *codepoints; /* Starting codepoints (NULL if non-GSUB) */
hb_set_t *features; /* Active features (NULL for default/all, only used for GSUB) */
};
/* Generate test parameters from explicit command-line arguments */
static test_params
generate_test_from_explicit (hb_face_t *face, hb_font_t *font)
{
test_params params;
params.glyphs = hb_set_create ();
params.codepoints = NULL;
params.features = NULL;
params.target_size = 0;
if (_hb_depend_fuzzer_explicit_unicodes)
{
params.is_gsub = true;
params.codepoints = hb_set_create ();
if (!parse_unicodes (_hb_depend_fuzzer_explicit_unicodes, params.codepoints))
{
fprintf (stderr, "Error: Failed to parse --unicodes argument\n");
hb_set_destroy (params.glyphs);
hb_set_destroy (params.codepoints);
params.glyphs = NULL;
params.codepoints = NULL;
return params;
}
hb_codepoint_t cp = HB_SET_VALUE_INVALID;
while (hb_set_next (params.codepoints, &cp))
{
hb_codepoint_t gid;
if (hb_font_get_nominal_glyph (font, cp, &gid))
hb_set_add (params.glyphs, gid);
}
params.target_size = hb_set_get_population (params.codepoints);
}
else if (_hb_depend_fuzzer_explicit_gids)
{
params.is_gsub = !_hb_depend_fuzzer_explicit_no_layout_closure;
if (!parse_gids (_hb_depend_fuzzer_explicit_gids, params.glyphs))
{
fprintf (stderr, "Error: Failed to parse --gids argument\n");
hb_set_destroy (params.glyphs);
params.glyphs = NULL;
return params;
}
params.target_size = hb_set_get_population (params.glyphs);
}
else
{
fprintf (stderr, "Error: Explicit mode requires --unicodes or --gids\n");
hb_set_destroy (params.glyphs);
params.glyphs = NULL;
return params;
}
if (_hb_depend_fuzzer_explicit_layout_features)
{
params.features = hb_set_create ();
if (!parse_features (_hb_depend_fuzzer_explicit_layout_features, params.features, face))
{
fprintf (stderr, "Error: Failed to parse --layout-features argument\n");
hb_set_destroy (params.features);
params.features = NULL;
}
}
return params;
}
/* Generate a test from a 64-bit seed.
*
* The seed encodes test type, size category, element selection, and feature selection:
*
* Test type distribution (seed % 100):
* - Non-GSUB single (1): 21%
* - Non-GSUB small (2–5): 17%
* - Non-GSUB medium (10–20): 10%
* - Non-GSUB large (50–100): 6%
* - Non-GSUB very large (N/10–N/4): 4%
* - GSUB single (1): 21%
* - GSUB small (2–5): 10%
* - GSUB medium (10–20): 6%
* - GSUB large (50–100): 4%
*
* Feature selection (GSUB tests, applied after type):
* - Single feature: 30%
* - Small (2–5): 35%
* - Medium (6–15): 20%
* - Large (16–50): 10%
* - All features: 5%
*/
static test_params
generate_test_from_seed (uint64_t seed, hb_face_t *face, hb_font_t *font)
{
test_params params;
params.glyphs = NULL;
params.codepoints = NULL;
params.features = NULL;
unsigned num_glyphs = hb_face_get_glyph_count (face);
hb_set_t *all_codepoints = hb_set_create ();
hb_face_collect_unicodes (face, all_codepoints);
unsigned num_codepoints = hb_set_get_population (all_codepoints);
/* Step 1: Determine test type and size range from seed % 100 */
unsigned type_selector = seed % 100;
/* Glyph-starting non-GSUB tests force subset-plan to reverse-map retained
* glyphs through cmap. Fonts like AdobeBlank cover most of Unicode, making
* that path too expensive to run hundreds of times in one Meson test. Use
* codepoint-starting tests for huge-cmap fonts; table dependency coverage
* still comes from the rest of the corpus. */
if (num_codepoints > 65536)
type_selector = 58 + (type_selector % 42);
unsigned min_size, max_size;
if (type_selector < 21) {
params.is_gsub = false; min_size = max_size = 1; /* Non-GSUB single (21%) */
} else if (type_selector < 38) {
params.is_gsub = false; min_size = 2; max_size = 5; /* Non-GSUB small (17%) */
} else if (type_selector < 48) {
params.is_gsub = false; min_size = 10; max_size = 20; /* Non-GSUB medium (10%) */
} else if (type_selector < 54) {
params.is_gsub = false; min_size = 50; max_size = 100; /* Non-GSUB large (6%) */
} else if (type_selector < 58) {
params.is_gsub = false; /* Non-GSUB very large (4%) */
min_size = num_glyphs / 10; max_size = num_glyphs / 4;
} else if (type_selector < 79) {
params.is_gsub = true; min_size = max_size = 1; /* GSUB single (21%) */
} else if (type_selector < 89) {
params.is_gsub = true; min_size = 2; max_size = 5; /* GSUB small (10%) */
} else if (type_selector < 95) {
params.is_gsub = true; min_size = 10; max_size = 20; /* GSUB medium (6%) */
} else {
params.is_gsub = true; min_size = 50; max_size = 100; /* GSUB large (4%) */
}
/* Step 2: Pick target size within range, then clamp to what the font has */
params.target_size = min_size + ((seed >> 8) % (max_size - min_size + 1));
if (params.is_gsub)
params.target_size = params.target_size < num_codepoints ? params.target_size : num_codepoints;
else
params.target_size = params.target_size < num_glyphs ? params.target_size : num_glyphs;
if (params.target_size < min_size)
{
hb_set_destroy (all_codepoints);
params.target_size = 0;
return params;
}
/* Step 3: Select specific elements using a separate RNG seeded from the high bits */
unsigned selection_seed = (unsigned)((seed >> 32) ^ seed);
std::mt19937 selection_rng (selection_seed);
if (params.is_gsub)
{
params.codepoints = hb_set_create ();
params.glyphs = hb_set_create ();
/* Select random codepoints by index */
hb_set_t *cp_indices = hb_set_create ();
rng_select_distinct (selection_rng, cp_indices, params.target_size, num_codepoints);
hb_codepoint_t next_idx = HB_SET_VALUE_INVALID;
hb_codepoint_t cp = HB_SET_VALUE_INVALID;
unsigned current_idx = 0;
bool have_idx = hb_set_next (cp_indices, &next_idx);
while (have_idx && hb_set_next (all_codepoints, &cp))
{
while (have_idx && current_idx == next_idx)
{
hb_set_add (params.codepoints, cp);
hb_codepoint_t gid;
if (hb_font_get_nominal_glyph (font, cp, &gid))
hb_set_add (params.glyphs, gid);
have_idx = hb_set_next (cp_indices, &next_idx);
}
current_idx++;
}
hb_set_destroy (cp_indices);
/* Select features to activate */
hb_set_t *available_features = get_gsub_features (face);
unsigned num_features = hb_set_get_population (available_features);
if (num_features > 0)
{
unsigned feature_selector = std::uniform_int_distribution<unsigned> (0, 99) (selection_rng);
unsigned num_to_select;
if (feature_selector < 30)
num_to_select = 1; /* Single (30%) */
else if (feature_selector < 65)
num_to_select = rng_range (selection_rng, 2, 5); /* Small (35%) */
else if (feature_selector < 85)
num_to_select = rng_range (selection_rng, 6, 15); /* Medium (20%) */
else if (feature_selector < 95)
num_to_select = rng_range (selection_rng, 16, 50); /* Large (10%) */
else
num_to_select = num_features; /* All (5%) */
if (num_to_select > num_features)
num_to_select = num_features;
params.features = hb_set_create ();
rng_select_from_set (selection_rng, params.features, num_to_select, available_features);
}
hb_set_destroy (available_features);
}
else
{
params.glyphs = hb_set_create ();
rng_select_distinct (selection_rng, params.glyphs, params.target_size, num_glyphs);
}
hb_set_destroy (all_codepoints);
return params;
}
#endif /* !HB_NO_SUBSET_DEPEND */
extern "C" int LLVMFuzzerTestOneInput (const uint8_t *data, size_t size)
{
#ifndef HB_NO_SUBSET_DEPEND
alloc_state = _fuzzing_alloc_state (data, size);
init_parity_config ();
hb_blob_t *blob = hb_blob_create ((const char *) data, size,
HB_MEMORY_MODE_READONLY, nullptr, nullptr);
hb_face_t *face = hb_face_create (blob, 0);
hb_subset_depend_t *depend = hb_subset_depend_from_face_or_fail (face);
if (!depend)
{
hb_face_destroy (face);
hb_blob_destroy (blob);
return 0;
}
/* Scale injection probabilities to achieve a target per-test detection rate
* regardless of font complexity.
*
* The user supplies a TARGET_RATE (e.g. 0.02 = 2%). We estimate the average
* number of edges traversed per test as E_total * 0.0025 (0.25% of all edges),
* then set per_edge_prob = TARGET_RATE / estimated_edges_per_test.
*
* The 0.0025 multiplier was tuned empirically across 8 system fonts
* (measured: 2.12% under, 3.20% over on 4000 tests at target rate 0.02). */
if (_hb_depend_fuzzer_inject_over_approx > 0.0f ||
_hb_depend_fuzzer_inject_under_approx > 0.0f)
{
unsigned total_edges = 0;
unsigned num_glyphs = hb_face_get_glyph_count (face);
for (unsigned gid = 0; gid < num_glyphs; gid++)
total_edges += hb_subset_depend_lookup_glyph (depend, gid, 0, nullptr, nullptr);
float estimated_edges_per_test = total_edges * 0.0025f;
if (estimated_edges_per_test < 5.0f)
estimated_edges_per_test = 5.0f;
if (_hb_depend_fuzzer_inject_over_approx > 0.0f)
{
float target_rate = _hb_depend_fuzzer_inject_over_approx;
_hb_depend_fuzzer_inject_over_approx = target_rate / estimated_edges_per_test;
}
if (_hb_depend_fuzzer_inject_under_approx > 0.0f)
{
float target_rate = _hb_depend_fuzzer_inject_under_approx;
_hb_depend_fuzzer_inject_under_approx = target_rate / estimated_edges_per_test;
}
if (_hb_depend_fuzzer_verbose)
{
fprintf (stderr, "# Adaptive error injection: %u total edges, est. %.0f edges/test\n",
total_edges, (double) estimated_edges_per_test);
fprintf (stderr, "# Calculated per-edge probabilities: over=%.6f under=%.6f\n",
(double) _hb_depend_fuzzer_inject_over_approx,
(double) _hb_depend_fuzzer_inject_under_approx);
}
}
hb_font_t *font = hb_font_create (face);
hb_map_t *unicode_to_glyph_cache = nullptr;
if (_hb_depend_fuzzer_explicit_test)
{
test_params params = generate_test_from_explicit (face, font);
if (params.glyphs == NULL)
fprintf (stderr, "Error: Failed to generate test from explicit parameters\n");
else
{
print_test_params (0, params.is_gsub, params.glyphs, params.codepoints, params.features);
if (!params.is_gsub && !unicode_to_glyph_cache)
{
unicode_to_glyph_cache = hb_map_create ();
hb_face_collect_nominal_glyph_mapping (face, unicode_to_glyph_cache, NULL);
}
compare_closures (face, depend, 0, _hb_depend_fuzzer_current_font_path,
params.is_gsub, params.glyphs, params.codepoints, params.features,
unicode_to_glyph_cache);
}
if (params.glyphs) hb_set_destroy (params.glyphs);
if (params.codepoints) hb_set_destroy (params.codepoints);
if (params.features) hb_set_destroy (params.features);
}
else if (_hb_depend_fuzzer_single_test != 0)
{
uint64_t test_seed = _hb_depend_fuzzer_single_test;
test_params params = generate_test_from_seed (test_seed, face, font);
if (params.target_size == 0 || params.glyphs == NULL)
fprintf (stderr, "Warning: Font too small for this test type\n");
else
{
print_test_params (test_seed, params.is_gsub, params.glyphs, params.codepoints, params.features);
if (!params.is_gsub && !unicode_to_glyph_cache)
{
unicode_to_glyph_cache = hb_map_create ();
hb_face_collect_nominal_glyph_mapping (face, unicode_to_glyph_cache, NULL);
}
compare_closures (face, depend, test_seed, _hb_depend_fuzzer_current_font_path,
params.is_gsub, params.glyphs, params.codepoints, params.features,
unicode_to_glyph_cache);
}
if (params.glyphs) hb_set_destroy (params.glyphs);
if (params.codepoints) hb_set_destroy (params.codepoints);
if (params.features) hb_set_destroy (params.features);
}
else
{
/* Generate T pseudo-random 64-bit seeds, then run compare_closures for each */
std::mt19937 test_rng (_hb_depend_fuzzer_seed);
std::uniform_int_distribution<uint32_t> uint32_dist;
for (unsigned i = 0; i < _hb_depend_fuzzer_num_tests; i++)
{
/* Keep high bit clear for gint64 compatibility with -t parameter */
uint64_t test_seed = (((uint64_t)uint32_dist (test_rng) << 32) | (uint64_t)uint32_dist (test_rng)) & 0x7FFFFFFFFFFFFFFFULL;
test_params params = generate_test_from_seed (test_seed, face, font);
if (params.target_size == 0 || params.glyphs == NULL)
continue;
if (_hb_depend_fuzzer_verbose)
print_test_params (test_seed, params.is_gsub, params.glyphs, params.codepoints, params.features);
if (!params.is_gsub && !unicode_to_glyph_cache)
{
unicode_to_glyph_cache = hb_map_create ();
hb_face_collect_nominal_glyph_mapping (face, unicode_to_glyph_cache, NULL);
}
compare_closures (face, depend, test_seed, _hb_depend_fuzzer_current_font_path,
params.is_gsub, params.glyphs, params.codepoints, params.features,
unicode_to_glyph_cache);
if (params.glyphs) hb_set_destroy (params.glyphs);
if (params.codepoints) hb_set_destroy (params.codepoints);
if (params.features) hb_set_destroy (params.features);
}
}
if (unicode_to_glyph_cache)
hb_map_destroy (unicode_to_glyph_cache);
hb_font_destroy (font);
hb_subset_depend_destroy (depend);
hb_face_destroy (face);
hb_blob_destroy (blob);
#endif /* !HB_NO_SUBSET_DEPEND */
return 0;
}