blob: 94ff1b2434d3e2f0ca5c105b321dc7d1277a71b2 [file] [edit]
#include "hb-fuzzer.hh"
#include <glib.h>
#include <cassert>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <ctime>
/* Globals defined in hb-depend-fuzzer.cc */
extern const char *_hb_depend_fuzzer_current_font_path;
extern unsigned _hb_depend_fuzzer_seed;
extern uint64_t _hb_depend_fuzzer_single_test; /* Specific test to run, or 0 for all */
extern unsigned _hb_depend_fuzzer_num_tests; /* Number of tests to run (default 1024) */
extern bool _hb_depend_fuzzer_verbose;
extern bool _hb_depend_fuzzer_report_over_approximation;
extern bool _hb_depend_fuzzer_report_under_approximation;
extern float _hb_depend_fuzzer_inject_over_approx;
extern float _hb_depend_fuzzer_inject_under_approx;
/* Explicit test mode */
extern bool _hb_depend_fuzzer_explicit_test;
extern const char *_hb_depend_fuzzer_explicit_unicodes;
extern const char *_hb_depend_fuzzer_explicit_gids;
extern bool _hb_depend_fuzzer_explicit_no_layout_closure;
extern const char *_hb_depend_fuzzer_explicit_layout_features;
/* Container for injection rate variables passed to callback */
struct inject_rates_t {
float *over_approx;
float *under_approx;
};
/* Glib option parsing callback for optional float arguments with defaults */
static gboolean
parse_inject_rate (const gchar *option_name,
const gchar *value,
gpointer data,
GError **error)
{
inject_rates_t *rates = (inject_rates_t *) data;
float *target;
/* Determine which rate to set based on option name */
if (g_str_equal (option_name, "-o") || g_str_equal (option_name, "--inject-over-approx"))
target = rates->over_approx;
else if (g_str_equal (option_name, "-u") || g_str_equal (option_name, "--inject-under-approx"))
target = rates->under_approx;
else
return FALSE; /* Unknown option */
if (value)
*target = strtof (value, NULL);
else
*target = 0.02f; /* Default 2% */
return TRUE;
}
static void
print_usage (const char *prog)
{
fprintf (stderr, "Usage: %s [OPTIONS] FONT...\n", prog);
fprintf (stderr, "\n");
fprintf (stderr, "Validate depend API by comparing closures against subset.\n");
fprintf (stderr, "\n");
fprintf (stderr, "Test Modes:\n");
fprintf (stderr, " (default) Run random tests using seed\n");
fprintf (stderr, " -t, --test HEXSEED Run only specific test (64-bit hex with 0x prefix)\n");
fprintf (stderr, " --unicodes LIST Run explicit test with given unicodes (U+XXXX or decimal)\n");
fprintf (stderr, " --gids LIST Run explicit test with given glyph IDs (decimal)\n");
fprintf (stderr, "\n");
fprintf (stderr, "Explicit Test Options (used with --unicodes or --gids):\n");
fprintf (stderr, " --no-layout-closure Skip GSUB closure (use with --gids)\n");
fprintf (stderr, " --layout-features=LIST\n");
fprintf (stderr, " Specify GSUB features (comma-separated tags or * for all)\n");
fprintf (stderr, " Default: subset's default features\n");
fprintf (stderr, "\n");
fprintf (stderr, "General Options:\n");
fprintf (stderr, " -s, --seed SEED Use specific random seed (default: random)\n");
fprintf (stderr, " -n, --num-tests N Run N tests per font (default: 1024)\n");
fprintf (stderr, " -v, --verbose Enable verbose output (all test details)\n");
fprintf (stderr, " -r, --report-over-approximation\n");
fprintf (stderr, " Report when depend finds more glyphs than subset\n");
fprintf (stderr, " --report-under-approximation\n");
fprintf (stderr, " Report under-approximation instead of aborting\n");
fprintf (stderr, " -o, --inject-over-approx[=RATE]\n");
fprintf (stderr, " Inject over-approximation errors (default rate: 0.02 = 2%%)\n");
fprintf (stderr, " -u, --inject-under-approx[=RATE]\n");
fprintf (stderr, " Inject under-approximation errors (default rate: 0.02 = 2%%)\n");
fprintf (stderr, " -h, --help Show this help message\n");
fprintf (stderr, "\n");
fprintf (stderr, "Examples:\n");
fprintf (stderr, " %s fonts/*.ttf # Run 1024 tests on each font\n", prog);
fprintf (stderr, " %s -n 100 fonts/*.ttf # Run 100 tests per font\n", prog);
fprintf (stderr, " %s -v fonts/MyFont.ttf # Run with verbose output\n", prog);
fprintf (stderr, " %s -t 0x0000303900000015 fonts/MyFont.ttf # Run specific test\n", prog);
fprintf (stderr, " %s --unicodes=U+0041,U+0042 fonts/MyFont.ttf # Test with 'A' and 'B'\n", prog);
fprintf (stderr, " %s --gids=10,20,30 --no-layout-closure fonts/MyFont.ttf # Non-GSUB test\n", prog);
fprintf (stderr, " %s --unicodes=65-90 --layout-features=liga,calt fonts/MyFont.ttf # Custom features\n", prog);
}
int main (int argc, char **argv)
{
unsigned seed = 0;
gint64 single_test = 0; /* Use gint64 for glib */
unsigned num_tests = 1024;
gboolean verbose = FALSE;
gboolean report_over_approximation = FALSE;
gboolean report_under_approximation = FALSE;
gboolean seed_specified = FALSE;
float inject_over_approx = 0.0f;
float inject_under_approx = 0.0f;
/* Explicit test mode */
gboolean explicit_test = FALSE;
const char *explicit_unicodes = NULL;
const char *explicit_gids = NULL;
gboolean explicit_no_layout_closure = FALSE;
const char *explicit_layout_features = NULL;
/* Injection rate callback data */
inject_rates_t inject_rates = {&inject_over_approx, &inject_under_approx};
/* Define glib option entries */
GOptionEntry test_mode_entries[] =
{
{"test", 't', 0, G_OPTION_ARG_INT64, &single_test,
"Run only specific test (64-bit hex with 0x prefix)", "0xHEXSEED"},
{"unicodes", 0, 0, G_OPTION_ARG_STRING, &explicit_unicodes,
"Run explicit test with given unicodes (U+XXXX or decimal)", "LIST"},
{"gids", 0, 0, G_OPTION_ARG_STRING, &explicit_gids,
"Run explicit test with given glyph IDs (decimal)", "LIST"},
{NULL}
};
GOptionEntry explicit_test_entries[] =
{
{"no-layout-closure", 0, 0, G_OPTION_ARG_NONE, &explicit_no_layout_closure,
"Skip GSUB closure (use with --gids)", NULL},
{"layout-features", 0, 0, G_OPTION_ARG_STRING, &explicit_layout_features,
"Specify GSUB features (comma-separated tags or * for all, default: subset's default features)", "LIST"},
{NULL}
};
GOptionEntry general_entries[] =
{
{"seed", 's', 0, G_OPTION_ARG_INT, &seed,
"Use specific random seed (default: random)", "SEED"},
{"num-tests", 'n', 0, G_OPTION_ARG_INT, &num_tests,
"Run N tests per font (default: 1024)", "N"},
{"verbose", 'v', 0, G_OPTION_ARG_NONE, &verbose,
"Enable verbose output (all test details)", NULL},
{"report-over-approximation", 'r', 0, G_OPTION_ARG_NONE, &report_over_approximation,
"Report when depend finds more glyphs than subset", NULL},
{"report-under-approximation", 0, 0, G_OPTION_ARG_NONE, &report_under_approximation,
"Report under-approximation instead of aborting", NULL},
{"inject-over-approx", 'o', G_OPTION_FLAG_OPTIONAL_ARG, G_OPTION_ARG_CALLBACK, (gpointer) &parse_inject_rate,
"Inject over-approximation errors (default rate: 0.02 = 2%)", "[RATE]"},
{"inject-under-approx", 'u', G_OPTION_FLAG_OPTIONAL_ARG, G_OPTION_ARG_CALLBACK, (gpointer) &parse_inject_rate,
"Inject under-approximation errors (default rate: 0.02 = 2%)", "[RATE]"},
{NULL}
};
/* Parse command line options */
GError *error = NULL;
GOptionContext *context = g_option_context_new ("FONT... - Validate depend API by comparing closures against subset");
/* Add main entries (no callbacks, no user_data needed) */
GOptionGroup *main_group = g_option_group_new (nullptr, nullptr, nullptr, nullptr, nullptr);
g_option_group_add_entries (main_group, test_mode_entries);
g_option_context_set_main_group (context, main_group);
GOptionGroup *explicit_group = g_option_group_new ("explicit", "Explicit Test Options:", "Show explicit test options", NULL, NULL);
g_option_group_add_entries (explicit_group, explicit_test_entries);
g_option_context_add_group (context, explicit_group);
GOptionGroup *general_group = g_option_group_new ("general", "General Options:", "Show general options",
static_cast<gpointer>(&inject_rates), nullptr);
g_option_group_add_entries (general_group, general_entries);
g_option_context_add_group (context, general_group);
if (!g_option_context_parse (context, &argc, &argv, &error))
{
fprintf (stderr, "Error parsing options: %s\n", error->message);
g_error_free (error);
g_option_context_free (context);
return 1;
}
g_option_context_free (context);
/* Check if we have font files */
if (argc < 2)
{
fprintf (stderr, "Error: No font files specified\n\n");
print_usage (argv[0]);
return 1;
}
/* Derive seed_specified from whether seed was explicitly set */
/* Note: glib doesn't directly tell us if an option was set, so we check if seed != 0 */
seed_specified = (seed != 0);
/* Derive explicit_test from whether --unicodes or --gids was specified */
explicit_test = (explicit_unicodes != NULL || explicit_gids != NULL);
/* Validate test mode combinations */
if (explicit_test && single_test != 0)
{
fprintf (stderr, "Error: Cannot use both explicit test mode (--unicodes/--gids) and -t/--test\n\n");
print_usage (argv[0]);
return 1;
}
if (explicit_unicodes && explicit_gids)
{
fprintf (stderr, "Error: Cannot specify both --unicodes and --gids\n\n");
print_usage (argv[0]);
return 1;
}
if (!explicit_test && (explicit_no_layout_closure || explicit_layout_features))
{
fprintf (stderr, "Warning: --no-layout-closure and --layout-features are ignored without --unicodes or --gids\n");
}
/* Use random seed if not specified */
if (!seed_specified)
seed = (unsigned) time (NULL);
/* Set global configuration */
_hb_depend_fuzzer_seed = seed;
_hb_depend_fuzzer_single_test = (uint64_t) single_test;
_hb_depend_fuzzer_num_tests = num_tests;
_hb_depend_fuzzer_verbose = verbose;
_hb_depend_fuzzer_report_over_approximation = report_over_approximation;
_hb_depend_fuzzer_report_under_approximation = report_under_approximation;
_hb_depend_fuzzer_inject_over_approx = inject_over_approx;
_hb_depend_fuzzer_inject_under_approx = inject_under_approx;
/* Set explicit test mode configuration */
_hb_depend_fuzzer_explicit_test = explicit_test;
_hb_depend_fuzzer_explicit_unicodes = explicit_unicodes;
_hb_depend_fuzzer_explicit_gids = explicit_gids;
_hb_depend_fuzzer_explicit_no_layout_closure = explicit_no_layout_closure;
_hb_depend_fuzzer_explicit_layout_features = explicit_layout_features;
/* TAP version must be the first line */
printf ("TAP version 14\n");
/* Print test mode info */
if (explicit_test)
{
if (explicit_unicodes)
printf ("# Explicit test mode: --unicodes=%s\n", explicit_unicodes);
else if (explicit_gids)
printf ("# Explicit test mode: --gids=%s\n", explicit_gids);
if (explicit_no_layout_closure)
printf ("# Using --no-layout-closure\n");
if (explicit_layout_features)
printf ("# Using --layout-features=%s\n", explicit_layout_features);
}
else if (single_test == 0)
printf ("# Using seed: %u, running %u tests per font\n", seed, num_tests);
else
printf ("# Running single test: 0x%016llx\n", (unsigned long long) single_test);
/* Process font files (glib leaves non-option arguments in argv[1..argc-1]) */
int file_count = 0;
for (int i = 1; i < argc; i++)
{
file_count++;
hb_blob_t *blob = hb_blob_create_from_file_or_fail (argv[i]);
assert (blob);
unsigned len = 0;
const char *font_data = hb_blob_get_data (blob, &len);
printf ("# %s (%u bytes)\n", argv[i], len);
/* Set global so fuzzer can log font path on mismatch */
_hb_depend_fuzzer_current_font_path = argv[i];
LLVMFuzzerTestOneInput ((const uint8_t *) font_data, len);
/* Clear global after processing */
_hb_depend_fuzzer_current_font_path = NULL;
printf ("ok %d - %s\n", file_count, argv[i]);
hb_blob_destroy (blob);
}
printf ("1..%d\n", file_count);
return 0;
}