Report the failures that used to be crashes
Closes Defects items 30 and 31 and Known-and-still-open items 1, 2, 5, 9 and 11. Both string accessors in json_helpers.c ended their ATTEMPT block with FINISH(errctx, false), which swallows the failure, and then strncpy'd through the pointer akgl_heap_next_string never set. So the one condition the pool exists to report -- it is full, which in practice means something is not releasing -- arrived as a segfault somewhere else entirely. It is FINISH(errctx, true) now, and tests/json_helpers.c claims every slot and asserts AKGL_ERR_HEAP comes back out of both. That test segfaults against the old code, which is also how the tilemap leak test in the previous commit confirmed this one. akgl_tilemap_release tested layers[i].texture and destroyed tilesets[i].texture, so every tileset texture was freed twice on one release and no image layer's texture was freed at all. Pointers are cleared as they go, so a second release is safe instead of a use-after-free. akgl_game_update_fps called game.lowfpsfunc() unguarded, on a path taken on frame one because fps is 0 for the first second. Only akgl_game_init installs it, and renderer.h documents the other path deliberately -- a host that owns its window binds a backend instead. It installs the default when it finds NULL. akgl_controller_pushmap and akgl_controller_default checked only the upper bound, so a negative id indexed before akgl_controlmaps. The two test-harness helpers were quietly worthless. akgl_render_and_compare drew t1 on both passes, so it always reported a match and every image assertion built on it asserted nothing; and akgl_compare_sdl_surfaces memcmp'd s1->pitch * s1->h bytes out of both surfaces without checking that the second was the same size, so a smaller one was read past its end. Both fixed, both tested. tests/util.c also now calls the collide-point test it has defined and never run. 25/25 pass, memcheck clean, reindent --check and check_error_protocol clean. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -241,6 +241,59 @@ akerr_ErrorContext *test_akgl_tilemap_compute_tileset_offsets(void)
|
||||
* akgl_tilemap_load_layers already bounded its own loop and raised
|
||||
* AKERR_OUTOFBOUNDS, so the shape to copy was in the same file.
|
||||
*/
|
||||
/**
|
||||
* @brief Releasing a map twice must not free a texture twice.
|
||||
*
|
||||
* The layers loop tested `dest->layers[i].texture` and then destroyed
|
||||
* `dest->tilesets[i].texture` -- already destroyed by the loop above it. So
|
||||
* every tileset texture was freed twice on a single release, and no image
|
||||
* layer's texture was freed at all. Neither pointer was cleared either, so a
|
||||
* second release was a use-after-free on top of that.
|
||||
*/
|
||||
akerr_ErrorContext *test_akgl_tilemap_release_is_idempotent(void)
|
||||
{
|
||||
akgl_String *pathstr = NULL;
|
||||
PREPARE_ERROR(errctx);
|
||||
int i = 0;
|
||||
|
||||
ATTEMPT {
|
||||
akgl_gamemap = &akgl_default_gamemap;
|
||||
akgl_renderer = &akgl_default_renderer;
|
||||
memset((void *)akgl_gamemap, 0x00, sizeof(akgl_Tilemap));
|
||||
|
||||
CATCH(errctx, akgl_heap_next_string(&pathstr));
|
||||
snprintf((char *)&pathstr->data, AKGL_MAX_STRING_LENGTH, "%s%s",
|
||||
SDL_GetBasePath(), "assets/testmap.tmj");
|
||||
CATCH(errctx, akgl_tilemap_load((char *)&pathstr->data, akgl_gamemap));
|
||||
TEST_ASSERT(errctx, akgl_gamemap->numtilesets > 0,
|
||||
"the fixture map loaded no tilesets, so this test proves nothing");
|
||||
TEST_ASSERT(errctx, akgl_gamemap->tilesets[0].texture != NULL,
|
||||
"the fixture map's first tileset has no texture");
|
||||
|
||||
TEST_EXPECT_OK(errctx, akgl_tilemap_release(akgl_gamemap), "releasing a loaded map");
|
||||
for ( i = 0; i < AKGL_TILEMAP_MAX_TILESETS; i++ ) {
|
||||
TEST_ASSERT(errctx, akgl_gamemap->tilesets[i].texture == NULL,
|
||||
"tileset %d's texture pointer survived release", i);
|
||||
}
|
||||
for ( i = 0; i < AKGL_TILEMAP_MAX_LAYERS; i++ ) {
|
||||
TEST_ASSERT(errctx, akgl_gamemap->layers[i].texture == NULL,
|
||||
"layer %d's texture pointer survived release", i);
|
||||
}
|
||||
|
||||
// The one that used to be a use-after-free.
|
||||
TEST_EXPECT_OK(errctx, akgl_tilemap_release(akgl_gamemap), "releasing the same map again");
|
||||
TEST_EXPECT_OK(errctx, akgl_tilemap_release(akgl_gamemap), "releasing it a third time");
|
||||
TEST_EXPECT_STATUS(errctx, AKERR_NULLPOINTER, akgl_tilemap_release(NULL),
|
||||
"releasing a NULL map");
|
||||
} CLEANUP {
|
||||
if ( pathstr != NULL ) {
|
||||
IGNORE(akgl_heap_release_string(pathstr));
|
||||
}
|
||||
} PROCESS(errctx) {
|
||||
} FINISH(errctx, true);
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
|
||||
akerr_ErrorContext *test_akgl_tilemap_load_bounds_fixed_tables(void)
|
||||
{
|
||||
akgl_String *pathstr = NULL;
|
||||
@@ -614,6 +667,7 @@ int main(void)
|
||||
CATCH(errctx, test_akgl_tilemap_load_layers());
|
||||
CATCH(errctx, test_akgl_tilemap_load_tilesets());
|
||||
CATCH(errctx, test_akgl_tilemap_load_bounds_fixed_tables());
|
||||
CATCH(errctx, test_akgl_tilemap_release_is_idempotent());
|
||||
//CATCH(errctx, test_akgl_tilemap_load());
|
||||
//CATCH(errctx, test_akgl_tilemap_draw_tileset());
|
||||
//CATCH(errctx, test_akgl_tilemap_draw());
|
||||
|
||||
Reference in New Issue
Block a user