[fuzzing] Fix hb-raster/vector fuzzer issues (#5816)

* [fuzzing] Guard raster/vector create_or_fail results

The raster and vector fuzzers called create_or_fail() and then
immediately dereferenced the returned pointers. Under CIFuzz's
allocation-failure coverage, that turns a valid NULL return into a
harness crash instead of treating it as an out-of-memory case.

Return early after cleaning up when draw or paint context creation
fails. This keeps the fuzzers aligned with the create_or_fail()
contract and avoids null-deref crashes unrelated to library logic.

Tested:
- meson compile -C build hb-raster-fuzzer hb-vector-fuzzer
- build/test/fuzzing/hb-vector-fuzzer test/fuzzing/fonts/TwemojiMozilla.subset.ttf
- build/test/fuzzing/hb-raster-fuzzer test/fuzzing/fonts/TwemojiMozilla.subset.ttf

Assisted-by: OpenAI Codex

* [vector] Avoid OOM leak in paint initialization

hb_vector_paint_ensure_initialized() used group_stack.push() and
then treated the returned pointer as a normal success indicator.
On allocation failure push() returns the Crap() sentinel, so the
subsequent root->alloc(4096) allocated backing storage into the
failure sink and leaked under malloc-failure fuzzing.

Use push_or_fail() instead and only allocate the root body after a
real push succeeds.

Tested:
- build/test/fuzzing/hb-vector-fuzzer test/fuzzing/fonts/TwemojiMozilla.subset.ttf

Assisted-by: OpenAI Codex

* [vector] Avoid double-free on blob creation failure

hb_svg_blob_from_buffer() handed buffer ownership to hb_blob_create()
in the non-recycled path and then ran its own failure cleanup. When
hb_blob_create() failed, it had already invoked the destroy callback,
so the local cleanup freed the same buffer again.

Use hb_blob_create_or_fail() there instead so allocation failure
returns nullptr without triggering the empty-blob fallback and its
conflicting cleanup semantics.

Tested:
- meson compile -C build hb-vector-fuzzer

Assisted-by: OpenAI Codex

* [vector] Avoid double-free in draw blob creation

Mirror the blob-creation failure fix from vector paint in the SVG
draw path. The non-recycled branch handed ownership to
hb_blob_create(), which may already run the destroy callback on
failure, and then fell into local cleanup that freed the same buffer
again.

Switch that branch to hb_blob_create_or_fail() so failure reports
nullptr without conflicting cleanup.

Tested:
- meson compile -C build hb-vector-fuzzer

Assisted-by: OpenAI Codex

* [vector] Fix OOM handling in SVG subset id collection

hb_svg_add_unique_id() used v->push() and tested the returned
pointer for null. hb_vector_t::push() reports allocation failure via
the vector error state and returns the Crap() sentinel, so the old
check did not reliably stop on OOM.

Check v->in_error() after push() before writing into the new slot.

Assisted-by: OpenAI Codex

* [vector] Guard paint render after init failure

hb_vector_paint_render() assumed ensure_initialized() had created
the root group body and indexed group_stack[0] unconditionally.
Under malloc-failure fuzzing, initialization can fail while extents
are already set, leaving group_stack empty and causing a null-deref.

Return nullptr if initialization did not produce the root group.

Assisted-by: OpenAI Codex

* [vector] Propagate paint init failures

hb_vector_paint_ensure_initialized() can fail under malloc-failure
fuzzing, but several paint callbacks assumed the root group existed
immediately afterward and accessed current_body() or group_stack[0].

Make initialization report success and bail out at the callers that
require the root group. Also use push_or_fail() for the extra group
stack entries created by grouping and glyph caching.

Assisted-by: OpenAI Codex

* [raster] Bound SVG path parsing work

hb_raster_svg_parse_path_data() had no complexity cap, unlike the
other new raster SVG subsystems that already bound recursion depth,
attribute count, and gradient stops. Malformed path data could
therefore spend excessive time in parsing and bbox/render work under
fuzzing.

Add a hard limit on parsed path segments and expose it through
hb-limits.hh so the budget is defined alongside the other shared
parser/work limits.

Assisted-by: OpenAI Codex

* [raster] Bound temporary clip mask buffers

hb_raster_paint_push_clip_from_emitter() and the general clip
rectangle path could allocate and clear full-surface temporary A8 clip
masks with no explicit size cap, leading to timeout-scale work on
malformed inputs.

Reuse the raster buffer size policy for these temporary masks as well,
and move that limit into hb-limits.hh so raster images and clip masks
share the same bound.

Assisted-by: OpenAI Codex

* [raster] Reduce buffer size cap

Lower HB_RASTER_MAX_BUFFER_SIZE from 1<<30 to 1<<24 so the shared
raster image / clip-mask limit meaningfully constrains pathological
fuzzer inputs instead of allowing timeout-scale temporary buffers.

Assisted-by: OpenAI Codex
diff --git a/src/hb-cplusplus.hh b/src/hb-cplusplus.hh
index b6b5c8f..14944ee 100644
--- a/src/hb-cplusplus.hh
+++ b/src/hb-cplusplus.hh
@@ -138,52 +138,56 @@
   static constexpr auto get_user_data = _get_user_data;
 };
 
-#define HB_DEFINE_VTABLE(name) \
+#define HB_DEFINE_VTABLE(name, empty) \
 	template<> \
 	struct vtable<hb_##name##_t> \
 	     : vtable_t<hb_##name##_t, \
-			&hb_##name##_get_empty, \
+			empty, \
 			&hb_##name##_reference, \
 			&hb_##name##_destroy, \
 			&hb_##name##_set_user_data, \
 			&hb_##name##_get_user_data> {}
 
-HB_DEFINE_VTABLE (buffer);
-HB_DEFINE_VTABLE (blob);
-HB_DEFINE_VTABLE (face);
-HB_DEFINE_VTABLE (font);
-HB_DEFINE_VTABLE (font_funcs);
-HB_DEFINE_VTABLE (map);
-HB_DEFINE_VTABLE (set);
-HB_DEFINE_VTABLE (shape_plan);
-HB_DEFINE_VTABLE (unicode_funcs);
-HB_DEFINE_VTABLE (draw_funcs);
-HB_DEFINE_VTABLE (paint_funcs);
-
-#undef HB_DEFINE_VTABLE
+HB_DEFINE_VTABLE (buffer, &hb_buffer_get_empty);
+HB_DEFINE_VTABLE (blob, &hb_blob_get_empty);
+HB_DEFINE_VTABLE (face, &hb_face_get_empty);
+HB_DEFINE_VTABLE (font, &hb_font_get_empty);
+HB_DEFINE_VTABLE (font_funcs, &hb_font_funcs_get_empty);
+HB_DEFINE_VTABLE (map, &hb_map_get_empty);
+HB_DEFINE_VTABLE (set, &hb_set_get_empty);
+HB_DEFINE_VTABLE (shape_plan, &hb_shape_plan_get_empty);
+HB_DEFINE_VTABLE (unicode_funcs, &hb_unicode_funcs_get_empty);
+HB_DEFINE_VTABLE (draw_funcs, &hb_draw_funcs_get_empty);
+HB_DEFINE_VTABLE (paint_funcs, &hb_paint_funcs_get_empty);
 
 
 #ifdef HB_SUBSET_H
 
-#define HB_DEFINE_VTABLE(name) \
-	template<> \
-	struct vtable<hb_##name##_t> \
-	     : vtable_t<hb_##name##_t, \
-			nullptr, \
-			&hb_##name##_reference, \
-			&hb_##name##_destroy, \
-			&hb_##name##_set_user_data, \
-			&hb_##name##_get_user_data> {}
-
-
-HB_DEFINE_VTABLE (subset_input);
-HB_DEFINE_VTABLE (subset_plan);
-
-#undef HB_DEFINE_VTABLE
+HB_DEFINE_VTABLE (subset_input, nullptr);
+HB_DEFINE_VTABLE (subset_plan, nullptr);
 
 #endif
 
 
+#ifdef HB_RASTER_H
+
+HB_DEFINE_VTABLE (raster_image, nullptr);
+HB_DEFINE_VTABLE (raster_draw, nullptr);
+HB_DEFINE_VTABLE (raster_paint, nullptr);
+
+#endif
+
+
+#ifdef HB_VECTOR_H
+
+HB_DEFINE_VTABLE (vector_draw, nullptr);
+HB_DEFINE_VTABLE (vector_paint, nullptr);
+
+#endif
+
+#undef HB_DEFINE_VTABLE
+
+
 } // namespace hb
 
 /* Workaround for GCC < 7, see: