Bound every array a data file can index
Closes Defects items 16 and 17 and Known-and-still-open item 6. All three let an asset file, or a caller's argument, write past a fixed array. akgl_sprite_load_json took its frame count straight from the document and wrote that many entries into a 16-byte frameids -- through a uint32_t * cast of a uint8_t *, so each write touched four bytes and the overrun reached four bytes past the array, into the rest of akgl_Sprite and then the next pool slot. The count is checked first now, each id is read into an int and narrowed deliberately, and a frame number too large for a uint8_t is refused rather than truncated into an index for a different tile. The tilemap loader had the same shape twice: objects[j] with no check against AKGL_TILEMAP_MAX_OBJECTS_PER_LAYER and tilesets[i] with none against AKGL_TILEMAP_MAX_TILESETS. akgl_tilemap_load_layers already bounded its own loop, so the pattern was in the same file. The object one is the reachable half -- 128 objects is not a large object layer. akgl_string_initialize zeroed sizeof(akgl_String) starting at `data`, which begins after the refcount in front of it, so it ran four bytes past the end of the object and onto the *next* slot's refcount -- the field the allocator reads to decide whether a slot is free. Same file, same class, fixed with it: akgl_string_copy accepted a count larger than the buffers, reading past one pool slot and writing past another, which the header documented as behaviour. Every case has a test that fails against the old code, with five new fixtures. Exactly-the-maximum is asserted alongside one-past in each, so the bound cannot be fixed by making the limit off by one. 25/25 pass, memcheck clean, reindent --check clean. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1549
tests/assets/snippets/test_tilemap_max_objects.json
Normal file
1549
tests/assets/snippets/test_tilemap_max_objects.json
Normal file
File diff suppressed because it is too large
Load Diff
1561
tests/assets/snippets/test_tilemap_too_many_objects.json
Normal file
1561
tests/assets/snippets/test_tilemap_too_many_objects.json
Normal file
File diff suppressed because it is too large
Load Diff
225
tests/assets/snippets/test_tilemap_too_many_tilesets.json
Normal file
225
tests/assets/snippets/test_tilemap_too_many_tilesets.json
Normal file
@@ -0,0 +1,225 @@
|
||||
{
|
||||
"tilesets": [
|
||||
{
|
||||
"columns": 48,
|
||||
"firstgid": 1,
|
||||
"image": "assets/World_A1.png",
|
||||
"imageheight": 576,
|
||||
"imagewidth": 768,
|
||||
"margin": 0,
|
||||
"name": "World_A1_0",
|
||||
"spacing": 0,
|
||||
"tilecount": 1728,
|
||||
"tileheight": 16,
|
||||
"tilewidth": 16
|
||||
},
|
||||
{
|
||||
"columns": 48,
|
||||
"firstgid": 1729,
|
||||
"image": "assets/World_A1.png",
|
||||
"imageheight": 576,
|
||||
"imagewidth": 768,
|
||||
"margin": 0,
|
||||
"name": "World_A1_1",
|
||||
"spacing": 0,
|
||||
"tilecount": 1728,
|
||||
"tileheight": 16,
|
||||
"tilewidth": 16
|
||||
},
|
||||
{
|
||||
"columns": 48,
|
||||
"firstgid": 3457,
|
||||
"image": "assets/World_A1.png",
|
||||
"imageheight": 576,
|
||||
"imagewidth": 768,
|
||||
"margin": 0,
|
||||
"name": "World_A1_2",
|
||||
"spacing": 0,
|
||||
"tilecount": 1728,
|
||||
"tileheight": 16,
|
||||
"tilewidth": 16
|
||||
},
|
||||
{
|
||||
"columns": 48,
|
||||
"firstgid": 5185,
|
||||
"image": "assets/World_A1.png",
|
||||
"imageheight": 576,
|
||||
"imagewidth": 768,
|
||||
"margin": 0,
|
||||
"name": "World_A1_3",
|
||||
"spacing": 0,
|
||||
"tilecount": 1728,
|
||||
"tileheight": 16,
|
||||
"tilewidth": 16
|
||||
},
|
||||
{
|
||||
"columns": 48,
|
||||
"firstgid": 6913,
|
||||
"image": "assets/World_A1.png",
|
||||
"imageheight": 576,
|
||||
"imagewidth": 768,
|
||||
"margin": 0,
|
||||
"name": "World_A1_4",
|
||||
"spacing": 0,
|
||||
"tilecount": 1728,
|
||||
"tileheight": 16,
|
||||
"tilewidth": 16
|
||||
},
|
||||
{
|
||||
"columns": 48,
|
||||
"firstgid": 8641,
|
||||
"image": "assets/World_A1.png",
|
||||
"imageheight": 576,
|
||||
"imagewidth": 768,
|
||||
"margin": 0,
|
||||
"name": "World_A1_5",
|
||||
"spacing": 0,
|
||||
"tilecount": 1728,
|
||||
"tileheight": 16,
|
||||
"tilewidth": 16
|
||||
},
|
||||
{
|
||||
"columns": 48,
|
||||
"firstgid": 10369,
|
||||
"image": "assets/World_A1.png",
|
||||
"imageheight": 576,
|
||||
"imagewidth": 768,
|
||||
"margin": 0,
|
||||
"name": "World_A1_6",
|
||||
"spacing": 0,
|
||||
"tilecount": 1728,
|
||||
"tileheight": 16,
|
||||
"tilewidth": 16
|
||||
},
|
||||
{
|
||||
"columns": 48,
|
||||
"firstgid": 12097,
|
||||
"image": "assets/World_A1.png",
|
||||
"imageheight": 576,
|
||||
"imagewidth": 768,
|
||||
"margin": 0,
|
||||
"name": "World_A1_7",
|
||||
"spacing": 0,
|
||||
"tilecount": 1728,
|
||||
"tileheight": 16,
|
||||
"tilewidth": 16
|
||||
},
|
||||
{
|
||||
"columns": 48,
|
||||
"firstgid": 13825,
|
||||
"image": "assets/World_A1.png",
|
||||
"imageheight": 576,
|
||||
"imagewidth": 768,
|
||||
"margin": 0,
|
||||
"name": "World_A1_8",
|
||||
"spacing": 0,
|
||||
"tilecount": 1728,
|
||||
"tileheight": 16,
|
||||
"tilewidth": 16
|
||||
},
|
||||
{
|
||||
"columns": 48,
|
||||
"firstgid": 15553,
|
||||
"image": "assets/World_A1.png",
|
||||
"imageheight": 576,
|
||||
"imagewidth": 768,
|
||||
"margin": 0,
|
||||
"name": "World_A1_9",
|
||||
"spacing": 0,
|
||||
"tilecount": 1728,
|
||||
"tileheight": 16,
|
||||
"tilewidth": 16
|
||||
},
|
||||
{
|
||||
"columns": 48,
|
||||
"firstgid": 17281,
|
||||
"image": "assets/World_A1.png",
|
||||
"imageheight": 576,
|
||||
"imagewidth": 768,
|
||||
"margin": 0,
|
||||
"name": "World_A1_10",
|
||||
"spacing": 0,
|
||||
"tilecount": 1728,
|
||||
"tileheight": 16,
|
||||
"tilewidth": 16
|
||||
},
|
||||
{
|
||||
"columns": 48,
|
||||
"firstgid": 19009,
|
||||
"image": "assets/World_A1.png",
|
||||
"imageheight": 576,
|
||||
"imagewidth": 768,
|
||||
"margin": 0,
|
||||
"name": "World_A1_11",
|
||||
"spacing": 0,
|
||||
"tilecount": 1728,
|
||||
"tileheight": 16,
|
||||
"tilewidth": 16
|
||||
},
|
||||
{
|
||||
"columns": 48,
|
||||
"firstgid": 20737,
|
||||
"image": "assets/World_A1.png",
|
||||
"imageheight": 576,
|
||||
"imagewidth": 768,
|
||||
"margin": 0,
|
||||
"name": "World_A1_12",
|
||||
"spacing": 0,
|
||||
"tilecount": 1728,
|
||||
"tileheight": 16,
|
||||
"tilewidth": 16
|
||||
},
|
||||
{
|
||||
"columns": 48,
|
||||
"firstgid": 22465,
|
||||
"image": "assets/World_A1.png",
|
||||
"imageheight": 576,
|
||||
"imagewidth": 768,
|
||||
"margin": 0,
|
||||
"name": "World_A1_13",
|
||||
"spacing": 0,
|
||||
"tilecount": 1728,
|
||||
"tileheight": 16,
|
||||
"tilewidth": 16
|
||||
},
|
||||
{
|
||||
"columns": 48,
|
||||
"firstgid": 24193,
|
||||
"image": "assets/World_A1.png",
|
||||
"imageheight": 576,
|
||||
"imagewidth": 768,
|
||||
"margin": 0,
|
||||
"name": "World_A1_14",
|
||||
"spacing": 0,
|
||||
"tilecount": 1728,
|
||||
"tileheight": 16,
|
||||
"tilewidth": 16
|
||||
},
|
||||
{
|
||||
"columns": 48,
|
||||
"firstgid": 25921,
|
||||
"image": "assets/World_A1.png",
|
||||
"imageheight": 576,
|
||||
"imagewidth": 768,
|
||||
"margin": 0,
|
||||
"name": "World_A1_15",
|
||||
"spacing": 0,
|
||||
"tilecount": 1728,
|
||||
"tileheight": 16,
|
||||
"tilewidth": 16
|
||||
},
|
||||
{
|
||||
"columns": 48,
|
||||
"firstgid": 27649,
|
||||
"image": "assets/World_A1.png",
|
||||
"imageheight": 576,
|
||||
"imagewidth": 768,
|
||||
"margin": 0,
|
||||
"name": "World_A1_16",
|
||||
"spacing": 0,
|
||||
"tilecount": 1728,
|
||||
"tileheight": 16,
|
||||
"tilewidth": 16
|
||||
}
|
||||
]
|
||||
}
|
||||
31
tests/assets/testsprite_maxframes.json
Normal file
31
tests/assets/testsprite_maxframes.json
Normal file
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"spritesheet": {
|
||||
"filename": "spritesheet.png",
|
||||
"frame_width": 48,
|
||||
"frame_height": 48
|
||||
},
|
||||
"name": "testsprite_maxframes",
|
||||
"width": 48,
|
||||
"height": 48,
|
||||
"speed": 100,
|
||||
"loop": true,
|
||||
"loopReverse": true,
|
||||
"frames": [
|
||||
0,
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
5,
|
||||
6,
|
||||
7,
|
||||
8,
|
||||
9,
|
||||
10,
|
||||
11,
|
||||
12,
|
||||
13,
|
||||
14,
|
||||
15
|
||||
]
|
||||
}
|
||||
32
tests/assets/testsprite_toomanyframes.json
Normal file
32
tests/assets/testsprite_toomanyframes.json
Normal file
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"spritesheet": {
|
||||
"filename": "spritesheet.png",
|
||||
"frame_width": 48,
|
||||
"frame_height": 48
|
||||
},
|
||||
"name": "testsprite_toomanyframes",
|
||||
"width": 48,
|
||||
"height": 48,
|
||||
"speed": 100,
|
||||
"loop": true,
|
||||
"loopReverse": true,
|
||||
"frames": [
|
||||
0,
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
5,
|
||||
6,
|
||||
7,
|
||||
8,
|
||||
9,
|
||||
10,
|
||||
11,
|
||||
12,
|
||||
13,
|
||||
14,
|
||||
15,
|
||||
16
|
||||
]
|
||||
}
|
||||
17
tests/assets/testsprite_widecount.json
Normal file
17
tests/assets/testsprite_widecount.json
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"spritesheet": {
|
||||
"filename": "spritesheet.png",
|
||||
"frame_width": 48,
|
||||
"frame_height": 48
|
||||
},
|
||||
"name": "testsprite_widecount",
|
||||
"width": 48,
|
||||
"height": 48,
|
||||
"speed": 100,
|
||||
"loop": true,
|
||||
"loopReverse": true,
|
||||
"frames": [
|
||||
0,
|
||||
256
|
||||
]
|
||||
}
|
||||
@@ -184,6 +184,58 @@ akerr_ErrorContext *test_akgl_sprite_load_json(void)
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief A sprite definition must not be able to write past `frameids`.
|
||||
*
|
||||
* `frameids` is AKGL_SPRITE_MAX_FRAMES bytes and the loader took its count
|
||||
* straight from the document, so a definition with seventeen frames wrote past
|
||||
* the array, past the rest of akgl_Sprite, and into the next pool slot. It also
|
||||
* wrote through a `uint32_t *` cast of a `uint8_t *`, four bytes at a time,
|
||||
* which is why the overrun reached four bytes past the array rather than one.
|
||||
*
|
||||
* The neighbouring slot is claimed and stamped first, so the test fails on the
|
||||
* corruption rather than on whatever the corruption happens to do later.
|
||||
*/
|
||||
akerr_ErrorContext *test_akgl_sprite_load_json_bounds_frames(void)
|
||||
{
|
||||
PREPARE_ERROR(errctx);
|
||||
akgl_Sprite *loaded = NULL;
|
||||
int i = 0;
|
||||
|
||||
ATTEMPT {
|
||||
// Exactly the maximum is legal and must still load.
|
||||
TEST_EXPECT_OK(errctx, akgl_sprite_load_json("assets/testsprite_maxframes.json"),
|
||||
"loading a sprite with exactly AKGL_SPRITE_MAX_FRAMES frames");
|
||||
loaded = SDL_GetPointerProperty(AKGL_REGISTRY_SPRITE, "testsprite_maxframes", NULL);
|
||||
FAIL_ZERO_BREAK(errctx, loaded, AKERR_KEY, "the max-frames sprite is not in the registry");
|
||||
TEST_ASSERT(errctx, loaded->frames == AKGL_SPRITE_MAX_FRAMES,
|
||||
"max-frames sprite loaded %d frames, expected %d",
|
||||
loaded->frames, AKGL_SPRITE_MAX_FRAMES);
|
||||
for ( i = 0; i < AKGL_SPRITE_MAX_FRAMES; i++ ) {
|
||||
TEST_ASSERT(errctx, loaded->frameids[i] == (uint8_t)i,
|
||||
"max-frames sprite frame %d is %d, expected %d",
|
||||
i, loaded->frameids[i], i);
|
||||
}
|
||||
|
||||
// One more than the maximum is refused, and nothing is registered.
|
||||
TEST_EXPECT_STATUS(errctx, AKERR_OUTOFBOUNDS,
|
||||
akgl_sprite_load_json("assets/testsprite_toomanyframes.json"),
|
||||
"loading a sprite with more frames than the array holds");
|
||||
TEST_ASSERT(errctx,
|
||||
SDL_GetPointerProperty(AKGL_REGISTRY_SPRITE, "testsprite_toomanyframes", NULL) == NULL,
|
||||
"a sprite with too many frames was registered anyway");
|
||||
|
||||
// A frame number a uint8_t cannot hold is refused rather than truncated
|
||||
// to something that indexes a different tile.
|
||||
TEST_EXPECT_STATUS(errctx, AKERR_OUTOFBOUNDS,
|
||||
akgl_sprite_load_json("assets/testsprite_widecount.json"),
|
||||
"loading a sprite whose frame number does not fit a uint8_t");
|
||||
} CLEANUP {
|
||||
} PROCESS(errctx) {
|
||||
} FINISH(errctx, true);
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
PREPARE_ERROR(errctx);
|
||||
@@ -211,6 +263,7 @@ int main(void)
|
||||
CATCH(errctx, test_akgl_spritesheet_initialize());
|
||||
CATCH(errctx, test_akgl_sprite_initialize());
|
||||
CATCH(errctx, test_akgl_sprite_load_json());
|
||||
CATCH(errctx, test_akgl_sprite_load_json_bounds_frames());
|
||||
} CLEANUP {
|
||||
} PROCESS(errctx) {
|
||||
} FINISH_NORETURN(errctx);
|
||||
|
||||
@@ -130,6 +130,111 @@ void reset_string_heap(void)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief akgl_string_initialize must not write past the buffer it is zeroing.
|
||||
*
|
||||
* The `NULL` init path zeroed `sizeof(akgl_String)` bytes starting at `data`.
|
||||
* `data` begins after the `int refcount` in front of it, so that ran four bytes
|
||||
* past the end of the object -- straight onto the *next* pool slot's refcount,
|
||||
* which is the field the allocator uses to decide whether a slot is free.
|
||||
*
|
||||
* Claiming two adjacent slots and initializing the first is enough to catch it:
|
||||
* the second's refcount goes to zero and the pool believes it is free while the
|
||||
* caller is still holding it.
|
||||
*/
|
||||
akerr_ErrorContext *test_akgl_string_initialize_stays_in_bounds(void)
|
||||
{
|
||||
PREPARE_ERROR(errctx);
|
||||
akgl_String *first = NULL;
|
||||
akgl_String *second = NULL;
|
||||
|
||||
ATTEMPT {
|
||||
CATCH(errctx, akgl_heap_next_string(&first));
|
||||
CATCH(errctx, akgl_heap_next_string(&second));
|
||||
TEST_ASSERT(errctx, second == (first + 1),
|
||||
"the pool did not hand out adjacent slots; this test needs them");
|
||||
|
||||
// A sentinel the overrun would land on. akgl_heap_next_string has
|
||||
// already set it to 1; make it something an accidental write cannot
|
||||
// coincide with.
|
||||
second->refcount = 0x5A;
|
||||
|
||||
CATCH(errctx, akgl_string_initialize(first, NULL));
|
||||
|
||||
TEST_ASSERT(errctx, second->refcount == 0x5A,
|
||||
"initializing a string wrote past its buffer: the next slot's "
|
||||
"refcount is %d, expected %d",
|
||||
second->refcount, 0x5A);
|
||||
TEST_ASSERT(errctx, first->refcount == 1,
|
||||
"initializing a string left its own refcount at %d, expected 1",
|
||||
first->refcount);
|
||||
TEST_ASSERT(errctx, first->data[0] == '\0',
|
||||
"initializing a string with NULL did not zero its buffer");
|
||||
TEST_ASSERT(errctx, first->data[AKGL_MAX_STRING_LENGTH - 1] == '\0',
|
||||
"initializing a string with NULL did not zero its last byte");
|
||||
} CLEANUP {
|
||||
if ( second != NULL ) {
|
||||
second->refcount = 1;
|
||||
IGNORE(akgl_heap_release_string(second));
|
||||
}
|
||||
if ( first != NULL ) {
|
||||
IGNORE(akgl_heap_release_string(first));
|
||||
}
|
||||
} PROCESS(errctx) {
|
||||
} FINISH(errctx, true);
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief akgl_string_copy must refuse a count that would leave both buffers.
|
||||
*
|
||||
* Both slots are exactly AKGL_MAX_STRING_LENGTH bytes, so a larger count read
|
||||
* past the end of one and wrote past the end of the other. The header used to
|
||||
* document that as behaviour.
|
||||
*/
|
||||
akerr_ErrorContext *test_akgl_string_copy_bounds_its_count(void)
|
||||
{
|
||||
PREPARE_ERROR(errctx);
|
||||
akgl_String *src = NULL;
|
||||
akgl_String *dest = NULL;
|
||||
|
||||
ATTEMPT {
|
||||
CATCH(errctx, akgl_heap_next_string(&src));
|
||||
CATCH(errctx, akgl_heap_next_string(&dest));
|
||||
CATCH(errctx, akgl_string_initialize(src, "bounded"));
|
||||
CATCH(errctx, akgl_string_initialize(dest, NULL));
|
||||
|
||||
TEST_EXPECT_STATUS(errctx, AKERR_OUTOFBOUNDS,
|
||||
akgl_string_copy(src, dest, AKGL_MAX_STRING_LENGTH + 1),
|
||||
"copying one byte more than a pool string holds");
|
||||
TEST_EXPECT_STATUS(errctx, AKERR_OUTOFBOUNDS,
|
||||
akgl_string_copy(src, dest, -1),
|
||||
"copying a negative number of bytes");
|
||||
|
||||
// The boundary itself is legal: it is exactly the buffer.
|
||||
TEST_EXPECT_OK(errctx, akgl_string_copy(src, dest, AKGL_MAX_STRING_LENGTH),
|
||||
"copying exactly a pool string's length");
|
||||
TEST_ASSERT(errctx, strcmp((char *)&dest->data, "bounded") == 0,
|
||||
"a full-length copy did not transfer the contents");
|
||||
|
||||
// 0 still means "the whole buffer" rather than "nothing".
|
||||
CATCH(errctx, akgl_string_initialize(dest, NULL));
|
||||
TEST_EXPECT_OK(errctx, akgl_string_copy(src, dest, 0),
|
||||
"copying with a count of zero");
|
||||
TEST_ASSERT(errctx, strcmp((char *)&dest->data, "bounded") == 0,
|
||||
"a zero-count copy did not transfer the whole buffer");
|
||||
} CLEANUP {
|
||||
if ( dest != NULL ) {
|
||||
IGNORE(akgl_heap_release_string(dest));
|
||||
}
|
||||
if ( src != NULL ) {
|
||||
IGNORE(akgl_heap_release_string(src));
|
||||
}
|
||||
} PROCESS(errctx) {
|
||||
} FINISH(errctx, true);
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
|
||||
@@ -151,6 +256,12 @@ int main(void)
|
||||
reset_string_heap();
|
||||
printf("test_akgl_string_initialize....\n");
|
||||
test_akgl_string_initialize();
|
||||
reset_string_heap();
|
||||
printf("test_akgl_string_initialize_stays_in_bounds ...\n");
|
||||
CATCH(errctx, test_akgl_string_initialize_stays_in_bounds());
|
||||
reset_string_heap();
|
||||
printf("test_akgl_string_copy_bounds_its_count ...\n");
|
||||
CATCH(errctx, test_akgl_string_copy_bounds_its_count());
|
||||
} CLEANUP {
|
||||
} PROCESS(errctx) {
|
||||
} FINISH_NORETURN(errctx);
|
||||
|
||||
@@ -229,6 +229,81 @@ akerr_ErrorContext *test_akgl_tilemap_compute_tileset_offsets(void)
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief The loader must refuse a map that would overrun its fixed tables.
|
||||
*
|
||||
* Two loops indexed straight from the document. `curlayer->objects[j]` had no
|
||||
* check against AKGL_TILEMAP_MAX_OBJECTS_PER_LAYER, and `dest->tilesets[i]`
|
||||
* none against AKGL_TILEMAP_MAX_TILESETS. The object one is the reachable one:
|
||||
* 128 objects is not a large object layer, and akgl_TilemapObject is big, so
|
||||
* the 129th wrote well past the end of the layer.
|
||||
*
|
||||
* akgl_tilemap_load_layers already bounded its own loop and raised
|
||||
* AKERR_OUTOFBOUNDS, so the shape to copy was in the same file.
|
||||
*/
|
||||
akerr_ErrorContext *test_akgl_tilemap_load_bounds_fixed_tables(void)
|
||||
{
|
||||
akgl_String *pathstr = NULL;
|
||||
PREPARE_ERROR(errctx);
|
||||
json_t *doc = NULL;
|
||||
json_error_t errdata;
|
||||
|
||||
ATTEMPT {
|
||||
akgl_gamemap = &akgl_default_gamemap;
|
||||
akgl_renderer = &akgl_default_renderer;
|
||||
CATCH(errctx, akgl_heap_next_string(&pathstr));
|
||||
|
||||
// Exactly the maximum must still load.
|
||||
memset((void *)akgl_gamemap, 0x00, sizeof(akgl_Tilemap));
|
||||
snprintf((char *)&pathstr->data, AKGL_MAX_STRING_LENGTH, "%s%s",
|
||||
SDL_GetBasePath(), "assets/snippets/test_tilemap_max_objects.json");
|
||||
doc = json_load_file((char *)&pathstr->data, 0, &errdata);
|
||||
FAIL_ZERO_BREAK(errctx, doc, AKERR_NULLPOINTER, "max-objects fixture: %s", (char *)&errdata.text);
|
||||
snprintf((char *)&pathstr->data, AKGL_MAX_STRING_LENGTH, "%s%s", SDL_GetBasePath(), "assets");
|
||||
TEST_EXPECT_OK(errctx,
|
||||
akgl_tilemap_load_layer_objects(akgl_gamemap, doc, 0, pathstr),
|
||||
"loading an object layer with exactly the maximum objects");
|
||||
json_decref(doc);
|
||||
doc = NULL;
|
||||
|
||||
// One more must be refused rather than written.
|
||||
memset((void *)akgl_gamemap, 0x00, sizeof(akgl_Tilemap));
|
||||
snprintf((char *)&pathstr->data, AKGL_MAX_STRING_LENGTH, "%s%s",
|
||||
SDL_GetBasePath(), "assets/snippets/test_tilemap_too_many_objects.json");
|
||||
doc = json_load_file((char *)&pathstr->data, 0, &errdata);
|
||||
FAIL_ZERO_BREAK(errctx, doc, AKERR_NULLPOINTER, "too-many-objects fixture: %s", (char *)&errdata.text);
|
||||
snprintf((char *)&pathstr->data, AKGL_MAX_STRING_LENGTH, "%s%s", SDL_GetBasePath(), "assets");
|
||||
TEST_EXPECT_STATUS(errctx, AKERR_OUTOFBOUNDS,
|
||||
akgl_tilemap_load_layer_objects(akgl_gamemap, doc, 0, pathstr),
|
||||
"loading an object layer with one object too many");
|
||||
json_decref(doc);
|
||||
doc = NULL;
|
||||
|
||||
// And the tileset table.
|
||||
memset((void *)akgl_gamemap, 0x00, sizeof(akgl_Tilemap));
|
||||
snprintf((char *)&pathstr->data, AKGL_MAX_STRING_LENGTH, "%s%s",
|
||||
SDL_GetBasePath(), "assets/snippets/test_tilemap_too_many_tilesets.json");
|
||||
doc = json_load_file((char *)&pathstr->data, 0, &errdata);
|
||||
FAIL_ZERO_BREAK(errctx, doc, AKERR_NULLPOINTER, "too-many-tilesets fixture: %s", (char *)&errdata.text);
|
||||
snprintf((char *)&pathstr->data, AKGL_MAX_STRING_LENGTH, "%s%s", SDL_GetBasePath(), "assets");
|
||||
TEST_EXPECT_STATUS(errctx, AKERR_OUTOFBOUNDS,
|
||||
akgl_tilemap_load_tilesets(akgl_gamemap, doc, pathstr),
|
||||
"loading a map with one tileset too many");
|
||||
TEST_ASSERT(errctx, akgl_gamemap->numtilesets <= AKGL_TILEMAP_MAX_TILESETS,
|
||||
"numtilesets reached %d, past the %d the table holds",
|
||||
akgl_gamemap->numtilesets, AKGL_TILEMAP_MAX_TILESETS);
|
||||
} CLEANUP {
|
||||
if ( doc != NULL ) {
|
||||
json_decref(doc);
|
||||
}
|
||||
if ( pathstr != NULL ) {
|
||||
IGNORE(akgl_heap_release_string(pathstr));
|
||||
}
|
||||
} PROCESS(errctx) {
|
||||
} FINISH(errctx, true);
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
|
||||
akerr_ErrorContext *test_akgl_tilemap_load_layer_objects(void)
|
||||
{
|
||||
akgl_String *pathstr;
|
||||
@@ -538,6 +613,7 @@ int main(void)
|
||||
CATCH(errctx, test_akgl_tilemap_load_layer_tile());
|
||||
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_load());
|
||||
//CATCH(errctx, test_akgl_tilemap_draw_tileset());
|
||||
//CATCH(errctx, test_akgl_tilemap_draw());
|
||||
|
||||
Reference in New Issue
Block a user