Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions src/libc/allocator.src
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,49 @@
public _malloc, _free, _realloc

public _calloc

if defined __TICE__

; uses the hardware specific $E40000 memory location

; void *calloc(size_t nmemb, size_t size)
_calloc:
pop de
pop bc
ex (sp), hl
push bc
push de
call __imulu
push hl
push hl
call _malloc
pop bc ; reset SP
; test for NULL
add hl, bc
; or a, a ; assumes that ptr + size does not overflow on TICE
sbc hl, bc
pop bc ; BC = size
ret z ; return NULL
; inlined bzero
push hl
ex de, hl ; DE = dest
; test if the size is zero
scf
sbc hl, hl
add hl, bc
jr nc, .finish
Comment on lines +33 to +37
Copy link
Member

@runer112 runer112 Sep 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I went down a bit of a rabbit hole here... I thought: "What's the point of malloc'ing 0 bytes? Could we treat that as an allocation failure and return null?" And sure enough, the C standard allows for exactly this:

If the size of the space requested is zero, the behavior is implementation-defined: either a null pointer is returned to indicate an error, or the behavior is as if the size were some nonzero value, except that the returned pointer shall not be used to access an object.

If we push the zero-size check into malloc, we can eliminate the check here. We'd have to conditionally keep the check here to be safe if _custom_malloc is being used, but otherwise the check can be cheaply pushed into __simple_malloc:

 	ex	(sp),hl
 	push	bc
 	ld	de,(_heap_ptr)
+	dec hl
 	add	hl,de
 	jr	c,.null
- 	ld	bc,___heaptop
+ 	ld	bc,___heaptop-1
 	sbc	hl,bc
 	jr	nc,.null
 	add	hl,bc

And _standard_malloc:

 
     /* add size of block header to real size */
     const size_t size = alloc_size + sizeof(block_t);
+    /* abort if alloc_size is 0 or size overflowed */
-    if (size < alloc_size)
+    if (size <= alloc_size)
     {
         return NULL;
     }

The same idea can be applied to the no hardware assumptions version of calloc.

; large region of all zeros on the Ti84CE
ld hl, $E40000 ; HL = src
ldir
.finish:
pop hl ; return value
ret

else

; makes no hardware assumptions

; void *calloc(size_t nmemb, size_t size)
_calloc:
pop de
pop bc
Expand All @@ -27,6 +70,8 @@ _calloc:
pop de
ret

end if

if defined ALLOCATOR_SIMPLE

_malloc := __simple_malloc
Expand Down
4 changes: 3 additions & 1 deletion src/libc/allocator_simple.src
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@

public __simple_malloc
__simple_malloc:
; returns NULL when size is zero
pop bc
ex (sp),hl
push bc
ld de,(_heap_ptr)
dec hl
add hl,de
jr c,.null
ld bc,___heaptop
ld bc,___heaptop-1
sbc hl,bc
jr nc,.null
add hl,bc
Expand Down
3 changes: 2 additions & 1 deletion src/libc/allocator_standard.c
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ void *_standard_malloc(size_t alloc_size)

/* add size of block header to real size */
const size_t size = alloc_size + sizeof(block_t);
if (size < alloc_size)
/* abort if alloc_size is 0 or size overflowed */
if (size <= alloc_size)
{
return NULL;
}
Expand Down
6 changes: 6 additions & 0 deletions test/standalone/asprintf_fprintf/src/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -418,6 +418,12 @@ int memccpy_tests(void) {
return __LINE__;
}

/* check that no crashes occur with small calloc sizes */
buf = (char*)calloc(1, sizeof(char));
free(buf);
buf = (char*)calloc(0, sizeof(char));
free(buf);

buf = (char*)calloc(file_size + 1, sizeof(char));
if (buf == NULL) {
perror("calloc failure");
Expand Down
Loading