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:
55
TODO.md
55
TODO.md
@@ -678,9 +678,19 @@ Each was found by a test written to assert correct behavior.
|
|||||||
`src/util.c:208` compares `s1->pitch * s1->h` bytes of `s2` without verifying
|
`src/util.c:208` compares `s1->pitch * s1->h` bytes of `s2` without verifying
|
||||||
the surfaces share dimensions, pitch, or format.
|
the surfaces share dimensions, pitch, or format.
|
||||||
6. **`akgl_string_initialize` overflows by four bytes when `init` is NULL.**
|
6. **`akgl_string_initialize` overflows by four bytes when `init` is NULL.**
|
||||||
`src/staticstring.c:17` does `memset(&obj->data, 0x00, sizeof(akgl_String))`,
|
**Fixed in 0.5.0**: it zeroes `sizeof(obj->data)`. The four bytes it used to
|
||||||
but `data` starts four bytes into the struct (after `refcount`), so the memset
|
run past the end of the object landed on the *next* pool slot's `refcount`,
|
||||||
runs four bytes past the end.
|
which is the field the allocator reads to decide whether a slot is free --
|
||||||
|
so the overrun could hand a live string out twice.
|
||||||
|
|
||||||
|
`tests/staticstring.c` claims two adjacent slots, stamps a sentinel into the
|
||||||
|
second's refcount, and initializes the first.
|
||||||
|
|
||||||
|
Fixed alongside it, because it is the same file and the same class:
|
||||||
|
`akgl_string_copy` accepted a `count` above `AKGL_MAX_STRING_LENGTH`, which
|
||||||
|
read past the end of one pool slot and wrote past the end of another. The
|
||||||
|
header documented that as behaviour. It is `AKERR_OUTOFBOUNDS` now, and a
|
||||||
|
negative count is refused too.
|
||||||
7. **Savegame name lengths disagree between writer and reader.**
|
7. **Savegame name lengths disagree between writer and reader.**
|
||||||
`akgl_game_save_actors` writes spritesheet names at
|
`akgl_game_save_actors` writes spritesheet names at
|
||||||
`AKGL_SPRITE_SHEET_MAX_FILENAME_LENGTH` (512) and character names at
|
`AKGL_SPRITE_SHEET_MAX_FILENAME_LENGTH` (512) and character names at
|
||||||
@@ -819,35 +829,24 @@ without coming here first. Ordered by blast radius.
|
|||||||
only record that a retry is wanted. Touches `src/util.c:117-121` only.
|
only record that a retry is wanted. Touches `src/util.c:117-121` only.
|
||||||
|
|
||||||
16. **`akgl_sprite_load_json` does not bound the `frames` array.**
|
16. **`akgl_sprite_load_json` does not bound the `frames` array.**
|
||||||
`src/sprite.c:165` sets `obj->frames` from `json_array_size()` and
|
**Fixed in 0.5.0.** The count is bounded against `AKGL_SPRITE_MAX_FRAMES`
|
||||||
`src/sprite.c:167` then writes that many entries into
|
before anything is written, and each element is read into an `int` and
|
||||||
`frameids[AKGL_SPRITE_MAX_FRAMES]`, which is 16. A sprite definition with 17
|
narrowed deliberately rather than written through a `uint32_t *` cast of a
|
||||||
or more frames writes past the array into the rest of `akgl_Sprite`, and
|
`uint8_t *`. A frame number that does not fit a `uint8_t` is refused too,
|
||||||
past the struct into the neighbouring pool slot. It is also written through
|
rather than truncated into an index that names a different tile.
|
||||||
a `uint32_t *` cast of a `uint8_t *`, so each element write touches four
|
|
||||||
bytes; that happens to work on a little-endian machine because the following
|
|
||||||
iterations overwrite the spill and the last write lands in the struct's
|
|
||||||
alignment padding, but it is not portable and it is what makes the overflow
|
|
||||||
reach four bytes past `frames` rather than one.
|
|
||||||
|
|
||||||
Fix: refuse a `frames` array longer than `AKGL_SPRITE_MAX_FRAMES` with
|
`tests/sprite.c` covers exactly the maximum (must load, and every id must
|
||||||
`AKERR_OUTOFBOUNDS`, and read into an `int` local before narrowing to
|
arrive), one past it, and the wide frame number, against three new fixtures.
|
||||||
`uint8_t`. Touches `src/sprite.c:164-169`.
|
Against the old code the middle case loads happily.
|
||||||
|
|
||||||
17. **Two more unbounded array loads in the tilemap loader.**
|
17. **Two more unbounded array loads in the tilemap loader.**
|
||||||
`src/tilemap.c:387-389` walks a Tiled object layer straight into
|
**Fixed in 0.5.0**, both with the bound at the top of the loop body, the
|
||||||
`curlayer->objects[j]` with no check against
|
shape `akgl_tilemap_load_layers` in the same file already used.
|
||||||
`AKGL_TILEMAP_MAX_OBJECTS_PER_LAYER` (128), and `src/tilemap.c:278-280`
|
|
||||||
fills `dest->tilesets[i]` with no check against `AKGL_TILEMAP_MAX_TILESETS`
|
|
||||||
(16).
|
|
||||||
Both are the same shape as item 16 and both are reachable from an ordinary
|
|
||||||
map file: 128 objects is not a large object layer. Note that
|
|
||||||
`akgl_tilemap_load_layers` *does* bound its loop and raises
|
|
||||||
`AKERR_OUTOFBOUNDS`, so the pattern to copy is already in the same file.
|
|
||||||
|
|
||||||
Fix: add the bound check at the top of each loop body, matching
|
`tests/tilemap.c` covers both against generated fixtures: an object layer
|
||||||
`akgl_tilemap_load_layers`. Touches `src/tilemap.c:387` and
|
of exactly 128 (must load) and of 129, and a map with 17 tilesets. The
|
||||||
`src/tilemap.c:278`.
|
object one is the reachable half -- 128 objects is not a large object layer
|
||||||
|
and `akgl_TilemapObject` is not small.
|
||||||
|
|
||||||
18. **`akgl_get_json_with_default` defaults on a status the array accessors never
|
18. **`akgl_get_json_with_default` defaults on a status the array accessors never
|
||||||
raise.** `src/json_helpers.c:164-165` handles `AKERR_KEY` and `AKERR_INDEX`,
|
raise.** `src/json_helpers.c:164-165` handles `AKERR_KEY` and `AKERR_INDEX`,
|
||||||
|
|||||||
@@ -40,10 +40,10 @@ typedef struct
|
|||||||
* @return `NULL` on success, otherwise an error context owned by the caller.
|
* @return `NULL` on success, otherwise an error context owned by the caller.
|
||||||
* @throws AKERR_NULLPOINTER If @p obj is `NULL`.
|
* @throws AKERR_NULLPOINTER If @p obj is `NULL`.
|
||||||
*
|
*
|
||||||
* @note Known defect: the `NULL` @p init path zeroes `sizeof(akgl_String)`
|
* @note Until 0.5.0 the `NULL` @p init path zeroed `sizeof(akgl_String)` bytes
|
||||||
* bytes starting at `data`, which is four bytes past the end of the
|
* starting at `data`, which is four bytes past the end of the buffer --
|
||||||
* buffer -- `refcount` sits in front of it. TODO.md, "Known and still
|
* `refcount` sits in front of it, so the overrun landed on the next pool
|
||||||
* open" item 6.
|
* slot's reference count.
|
||||||
*/
|
*/
|
||||||
akerr_ErrorContext AKERR_NOIGNORE *akgl_string_initialize(akgl_String *obj, char *init);
|
akerr_ErrorContext AKERR_NOIGNORE *akgl_string_initialize(akgl_String *obj, char *init);
|
||||||
/**
|
/**
|
||||||
@@ -58,11 +58,14 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_string_initialize(akgl_String *obj, char
|
|||||||
* @param count Maximum bytes to copy. 0 selects #AKGL_MAX_STRING_LENGTH, the
|
* @param count Maximum bytes to copy. 0 selects #AKGL_MAX_STRING_LENGTH, the
|
||||||
* whole buffer. A @p count shorter than the source truncates
|
* whole buffer. A @p count shorter than the source truncates
|
||||||
* without writing a terminator; a @p count longer than the source
|
* without writing a terminator; a @p count longer than the source
|
||||||
* zero-pads the remainder, per `strncpy`. Values above
|
* zero-pads the remainder, per `strncpy`. A negative @p count, or
|
||||||
* #AKGL_MAX_STRING_LENGTH overrun both buffers and are not
|
* one above #AKGL_MAX_STRING_LENGTH, is refused -- both buffers
|
||||||
* rejected.
|
* are exactly that long, so a larger count walked off the end of
|
||||||
|
* two pool slots at once.
|
||||||
* @return `NULL` on success, otherwise an error context owned by the caller.
|
* @return `NULL` on success, otherwise an error context owned by the caller.
|
||||||
* @throws AKERR_NULLPOINTER If @p src or @p dest is `NULL`.
|
* @throws AKERR_NULLPOINTER If @p src or @p dest is `NULL`.
|
||||||
|
* @throws AKERR_OUTOFBOUNDS If @p count is negative or above
|
||||||
|
* #AKGL_MAX_STRING_LENGTH.
|
||||||
* @throws errno Whatever `errno` holds if `strncpy` returns something other
|
* @throws errno Whatever `errno` holds if `strncpy` returns something other
|
||||||
* than @p dest. In practice `strncpy` always returns its destination, so
|
* than @p dest. In practice `strncpy` always returns its destination, so
|
||||||
* this path is unreachable rather than merely rare.
|
* this path is unreachable rather than merely rare.
|
||||||
|
|||||||
39
src/sprite.c
39
src/sprite.c
@@ -119,6 +119,8 @@ akerr_ErrorContext *akgl_sprite_load_json(char *filename)
|
|||||||
akgl_String *spritename = NULL;
|
akgl_String *spritename = NULL;
|
||||||
akgl_String *filename_copy = NULL;
|
akgl_String *filename_copy = NULL;
|
||||||
int i = 0;
|
int i = 0;
|
||||||
|
int framecount = 0;
|
||||||
|
int frameid = 0;
|
||||||
|
|
||||||
FAIL_ZERO_RETURN(errctx, filename, AKERR_NULLPOINTER, "Received null filename");
|
FAIL_ZERO_RETURN(errctx, filename, AKERR_NULLPOINTER, "Received null filename");
|
||||||
ATTEMPT {
|
ATTEMPT {
|
||||||
@@ -161,9 +163,40 @@ akerr_ErrorContext *akgl_sprite_load_json(char *filename)
|
|||||||
CATCH(errctx, akgl_get_json_boolean_value((json_t *)json, "loopReverse", &obj->loopReverse));
|
CATCH(errctx, akgl_get_json_boolean_value((json_t *)json, "loopReverse", &obj->loopReverse));
|
||||||
|
|
||||||
CATCH(errctx, akgl_get_json_array_value((json_t *)json, "frames", &frames));
|
CATCH(errctx, akgl_get_json_array_value((json_t *)json, "frames", &frames));
|
||||||
obj->frames = json_array_size((json_t *)frames);
|
// Bounded before anything is written. frameids is
|
||||||
for ( i = 0 ; i < obj->frames; i++ ) {
|
// AKGL_SPRITE_MAX_FRAMES bytes, and this loop used to take its count
|
||||||
CATCH(errctx, akgl_get_json_array_index_integer((json_t *)frames, i, (uint32_t *)&obj->frameids[i]));
|
// straight from the document -- so a definition with seventeen frames
|
||||||
|
// wrote past the array, past the rest of akgl_Sprite, and into the
|
||||||
|
// neighbouring pool slot.
|
||||||
|
framecount = (int)json_array_size((json_t *)frames);
|
||||||
|
FAIL_NONZERO_BREAK(
|
||||||
|
errctx,
|
||||||
|
(framecount > AKGL_SPRITE_MAX_FRAMES),
|
||||||
|
AKERR_OUTOFBOUNDS,
|
||||||
|
"Sprite %s declares %d frames; the maximum is %d",
|
||||||
|
(char *)&obj->name,
|
||||||
|
framecount,
|
||||||
|
AKGL_SPRITE_MAX_FRAMES
|
||||||
|
);
|
||||||
|
obj->frames = framecount;
|
||||||
|
for ( i = 0 ; i < framecount; i++ ) {
|
||||||
|
// Read into an int and narrow deliberately. The old form wrote
|
||||||
|
// through a uint32_t * cast of a uint8_t *, so every element write
|
||||||
|
// touched four bytes; it only appeared to work because the next
|
||||||
|
// iteration overwrote the spill and the last one landed in the
|
||||||
|
// struct's alignment padding.
|
||||||
|
CATCH(errctx, akgl_get_json_array_index_integer((json_t *)frames, i, &frameid));
|
||||||
|
FAIL_NONZERO_BREAK(
|
||||||
|
errctx,
|
||||||
|
((frameid < 0) || (frameid > UINT8_MAX)),
|
||||||
|
AKERR_OUTOFBOUNDS,
|
||||||
|
"Sprite %s frame %d is %d; frame numbers are 0..%d",
|
||||||
|
(char *)&obj->name,
|
||||||
|
i,
|
||||||
|
frameid,
|
||||||
|
UINT8_MAX
|
||||||
|
);
|
||||||
|
obj->frameids[i] = (uint8_t)frameid;
|
||||||
}
|
}
|
||||||
} CLEANUP {
|
} CLEANUP {
|
||||||
// The sprite copies every field it wants out of the document and the
|
// The sprite copies every field it wants out of the document and the
|
||||||
|
|||||||
@@ -14,7 +14,12 @@ akerr_ErrorContext *akgl_string_initialize(akgl_String *obj, char *init)
|
|||||||
if ( init != NULL ) {
|
if ( init != NULL ) {
|
||||||
strncpy((char *)&obj->data, init, AKGL_MAX_STRING_LENGTH);
|
strncpy((char *)&obj->data, init, AKGL_MAX_STRING_LENGTH);
|
||||||
} else {
|
} else {
|
||||||
memset(&obj->data, 0x00, sizeof(akgl_String));
|
// sizeof(obj->data), not sizeof(akgl_String). `data` starts after the
|
||||||
|
// `refcount` in front of it, so zeroing the size of the whole struct
|
||||||
|
// from the start of the buffer ran four bytes past the end of the
|
||||||
|
// object -- into the next pool slot's refcount, which is what makes a
|
||||||
|
// free slot look claimed or a claimed one look free.
|
||||||
|
memset(&obj->data, 0x00, sizeof(obj->data));
|
||||||
}
|
}
|
||||||
obj->refcount = 1;
|
obj->refcount = 1;
|
||||||
SUCCEED_RETURN(errctx);
|
SUCCEED_RETURN(errctx);
|
||||||
@@ -28,6 +33,16 @@ akerr_ErrorContext *akgl_string_copy(akgl_String *src, akgl_String *dest, int co
|
|||||||
if ( count == 0 ) {
|
if ( count == 0 ) {
|
||||||
count = AKGL_MAX_STRING_LENGTH;
|
count = AKGL_MAX_STRING_LENGTH;
|
||||||
}
|
}
|
||||||
|
// Both buffers are exactly AKGL_MAX_STRING_LENGTH bytes, so a larger count
|
||||||
|
// walks off the end of two pool slots at once. Refused rather than
|
||||||
|
// documented, which is what it used to be.
|
||||||
|
FAIL_NONZERO_RETURN(
|
||||||
|
errctx,
|
||||||
|
((count < 0) || (count > AKGL_MAX_STRING_LENGTH)),
|
||||||
|
AKERR_OUTOFBOUNDS,
|
||||||
|
"Copy count %d is outside 0..%d",
|
||||||
|
count,
|
||||||
|
AKGL_MAX_STRING_LENGTH);
|
||||||
if ( (char *)dest->data != strncpy((char *)&dest->data, (char *)&src->data, count) ) {
|
if ( (char *)dest->data != strncpy((char *)&dest->data, (char *)&src->data, count) ) {
|
||||||
FAIL_RETURN(errctx, errno, "strncpy");
|
FAIL_RETURN(errctx, errno, "strncpy");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -238,6 +238,17 @@ akerr_ErrorContext *akgl_tilemap_load_tilesets(akgl_Tilemap *dest, json_t *root,
|
|||||||
ATTEMPT {
|
ATTEMPT {
|
||||||
CATCH(errctx, akgl_get_json_array_value(root, "tilesets", &tilesets))
|
CATCH(errctx, akgl_get_json_array_value(root, "tilesets", &tilesets))
|
||||||
for (i = 0; i < json_array_size((json_t *)tilesets); i++) {
|
for (i = 0; i < json_array_size((json_t *)tilesets); i++) {
|
||||||
|
// The bound goes at the top of the body, matching
|
||||||
|
// akgl_tilemap_load_layers. Without it a map with seventeen
|
||||||
|
// tilesets wrote past the fixed table -- and unlike the layer
|
||||||
|
// count, nothing anywhere else was checking this one.
|
||||||
|
FAIL_NONZERO_BREAK(
|
||||||
|
errctx,
|
||||||
|
(i >= AKGL_TILEMAP_MAX_TILESETS),
|
||||||
|
AKERR_OUTOFBOUNDS,
|
||||||
|
"Map declares more than %d tilesets",
|
||||||
|
AKGL_TILEMAP_MAX_TILESETS
|
||||||
|
);
|
||||||
CATCH(errctx, akgl_get_json_array_index_object((json_t *)tilesets, i, &jstileset));
|
CATCH(errctx, akgl_get_json_array_index_object((json_t *)tilesets, i, &jstileset));
|
||||||
CATCH(errctx, akgl_tilemap_load_tilesets_each(jstileset, dest, i, dirname));
|
CATCH(errctx, akgl_tilemap_load_tilesets_each(jstileset, dest, i, dirname));
|
||||||
CATCH(errctx, akgl_tilemap_compute_tileset_offsets(dest, i));
|
CATCH(errctx, akgl_tilemap_compute_tileset_offsets(dest, i));
|
||||||
@@ -312,6 +323,14 @@ akerr_ErrorContext *akgl_tilemap_load_layer_objects(akgl_Tilemap *dest, json_t *
|
|||||||
len = json_array_size((json_t *)layerdata);
|
len = json_array_size((json_t *)layerdata);
|
||||||
curlayer = &dest->layers[layerid];
|
curlayer = &dest->layers[layerid];
|
||||||
for ( j = 0; j < len; j++ ) {
|
for ( j = 0; j < len; j++ ) {
|
||||||
|
FAIL_NONZERO_RETURN(
|
||||||
|
errctx,
|
||||||
|
(j >= AKGL_TILEMAP_MAX_OBJECTS_PER_LAYER),
|
||||||
|
AKERR_OUTOFBOUNDS,
|
||||||
|
"Object layer %d has more than %d objects",
|
||||||
|
layerid,
|
||||||
|
AKGL_TILEMAP_MAX_OBJECTS_PER_LAYER
|
||||||
|
);
|
||||||
PASS(errctx, akgl_get_json_array_index_object((json_t *)layerdata, j, &layerdatavalue));
|
PASS(errctx, akgl_get_json_array_index_object((json_t *)layerdata, j, &layerdatavalue));
|
||||||
curobj = &curlayer->objects[j];
|
curobj = &curlayer->objects[j];
|
||||||
PASS(errctx, akgl_get_json_string_value((json_t *)layerdatavalue, "name", &tmpstr));
|
PASS(errctx, akgl_get_json_string_value((json_t *)layerdatavalue, "name", &tmpstr));
|
||||||
|
|||||||
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);
|
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)
|
int main(void)
|
||||||
{
|
{
|
||||||
PREPARE_ERROR(errctx);
|
PREPARE_ERROR(errctx);
|
||||||
@@ -211,6 +263,7 @@ int main(void)
|
|||||||
CATCH(errctx, test_akgl_spritesheet_initialize());
|
CATCH(errctx, test_akgl_spritesheet_initialize());
|
||||||
CATCH(errctx, test_akgl_sprite_initialize());
|
CATCH(errctx, test_akgl_sprite_initialize());
|
||||||
CATCH(errctx, test_akgl_sprite_load_json());
|
CATCH(errctx, test_akgl_sprite_load_json());
|
||||||
|
CATCH(errctx, test_akgl_sprite_load_json_bounds_frames());
|
||||||
} CLEANUP {
|
} CLEANUP {
|
||||||
} PROCESS(errctx) {
|
} PROCESS(errctx) {
|
||||||
} FINISH_NORETURN(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)
|
int main(void)
|
||||||
{
|
{
|
||||||
|
|
||||||
@@ -151,6 +256,12 @@ int main(void)
|
|||||||
reset_string_heap();
|
reset_string_heap();
|
||||||
printf("test_akgl_string_initialize....\n");
|
printf("test_akgl_string_initialize....\n");
|
||||||
test_akgl_string_initialize();
|
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 {
|
} CLEANUP {
|
||||||
} PROCESS(errctx) {
|
} PROCESS(errctx) {
|
||||||
} FINISH_NORETURN(errctx);
|
} FINISH_NORETURN(errctx);
|
||||||
|
|||||||
@@ -229,6 +229,81 @@ akerr_ErrorContext *test_akgl_tilemap_compute_tileset_offsets(void)
|
|||||||
SUCCEED_RETURN(errctx);
|
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)
|
akerr_ErrorContext *test_akgl_tilemap_load_layer_objects(void)
|
||||||
{
|
{
|
||||||
akgl_String *pathstr;
|
akgl_String *pathstr;
|
||||||
@@ -538,6 +613,7 @@ int main(void)
|
|||||||
CATCH(errctx, test_akgl_tilemap_load_layer_tile());
|
CATCH(errctx, test_akgl_tilemap_load_layer_tile());
|
||||||
CATCH(errctx, test_akgl_tilemap_load_layers());
|
CATCH(errctx, test_akgl_tilemap_load_layers());
|
||||||
CATCH(errctx, test_akgl_tilemap_load_tilesets());
|
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_load());
|
||||||
//CATCH(errctx, test_akgl_tilemap_draw_tileset());
|
//CATCH(errctx, test_akgl_tilemap_draw_tileset());
|
||||||
//CATCH(errctx, test_akgl_tilemap_draw());
|
//CATCH(errctx, test_akgl_tilemap_draw());
|
||||||
|
|||||||
Reference in New Issue
Block a user