We just make these aliases for snprintf, which should work fine for all valid cases given our calling convention. Since they are actually implemented via snprintf (and the printf machinery in general), they are in stdio.asm, even though the functions are declared in <stdlib.h>.
Note that the input for strndup is not required to be a null-terminated string, so we need to avoid using operations like strlen on it. Also, we limit the length allocated for strndup to the minimum of n+1 or the length needed to hold the whole input string. Some implementations might always allocate n+1 bytes, but that could lead to avoidable allocation failures on a fairly memory-constrained system like the IIGS.
Note that this returns the actual alignment of the address, which can be greater than what can be used with alignas. In IIGS-specific code, it could be used to test for page or bank alignment.
The previous code could fail for times within the first or last few hours of the representable range, depending on the time zone. Specifically, it failed for times that would be outside the representable range if they were local time, but were representable when they were UTC times due to the time zone offset.
Several of the multiplies and divides are now implemented via shifts and additions, which should be significantly faster. (These also take advantage of the limited range of operands to use only 16-bit operations where possible.)
This uses a "year length" slightly longer than the real average. Because our (time_t)0 is November 13, this brings the cutoffs for the year estimates closer to the end of the year as years pass, which reduces the number of dates for which the initial estimate is incorrect. This means that there are fewer instances where an extra factor call is required, so it should produce a slight speedup on average.
If we ever move to 64-bit time_t, this fudge factor would have to be removed, since it is only valid within our 32-bit time_t range.
This now requires only one factor call for the year computation. Previously, year-1969 factor calls were required, so the new approach is significantly faster.
These are supported both with base=0 (where they indicate the number is in binary) and with base=2 (where the prefix is optional and does not change the base).
When base is 0 or 16 and the numeric part of the string (after possible leading white space and/or sign) starts with 0x or 0X but is not followed by a hex digit, only the 0 should be taken as part of the subject sequence. Thus, this is not an error condition (errno should not be set), the result should be 0, and if endptr is non-null, *endptr should be set to point to the x or X.
Note that like with fprintf, unsupported wN/wfN length modifiers do not cause undefined behavior. In the case of fscanf, they are handled similarly to a matching failure. In such cases, we assume that there was a pointer-sized parameter passed corresponding to the unsupported conversion (unless assignment was suppressed), so that and any parameters matching the rest of the format string are removed from the stack.
Note that fprintf is specified as returning a negative number for unsupported wN/wfN length modifiers (i.e. it's not undefined behavior). This implements that behavior. However, there's no good way to know how many parameter bytes were passed for the unsupported conversion, and thus no way no pull those and subsequent parameter bytes off the stack. Accordingly, we just leave them on the stack. That works fine if the caller has varargs stack repair code enabled, but not otherwise, so this is another situation where varargs stack repair code is necessary for full standards conformance. This generally shouldn't be an issue for real code, since we support the power-of-2 widths from 8 to 64, which are the only ones likely to be used in practice.
This makes the code slightly smaller and faster. It also fixes a theoretical bug, which is that the old code would report a matching failure if the number of digits was a multiple of 64K (of course, this would only have defined behavior if the vast majority of the digits were leading zeros).
Like in other places, the binary number support is conditional on ~C23ORLATER.
This is implemented by integrating the code for scanf 'i' and 'd' formats with the formats for unsigned integers. This shrinks the code size and reduces duplication. It also fixes at least one bug, which is that if a hexadecimal number was used as input to an 'i' conversion, the next character after it could be changed, e.g. capitalizing a letter.
A new label is added in the library named ~C23ORLATER. Its presence in the executable acts as a flag for enabling C23 library behavior, including the new 'b' format. The compiler will generate a reference to it when compiling a program in C23 mode, causing it to be linked in. This can then be detected in library code by including a soft (weak) reference to it, which will be non-zero if the flag is linked in (C23+) or zero if it is not (pre-C23).
This prints integers in binary, with an optional '0B' prefix. It is specified but optional in C23.
The similar 'b' format (differing only in the case used for the '0b' prefix) is required in C23, but not yet implemented here, since 'b' has historically been used for something different in ORCA/C. The C23 'b' format would be simple to implement with small adjustments to the entry code, but I plan to provide compatibility support to pick between the two versions based on the standard in use.
The ORCA/C compiler was updated to support 32-bit line numbers some time ago (as required by C99 and later), so assert should support them as well.
The code to return from any of the assert calls is also removed, since it was unreachable.
In some implementations, these functions could potentially be faster than free() and/or perform additional safety checks, but this implementation just ignores the extra arguments.
This is just equivalent to memset in ORCA/C, since there are no optimizations that would remove memset operations. Note that in addition to clearing the old data in memory, this implementation of memset/memset_explicit also will not leave any of the old data in registers or in the stack space that it used.
This implementation includes synchronization suitable for multithreading (albeit with inefficient busy waiting), even though ORCA/C does not currently support that. This could potentially be used with forked processes under GNO.
This is documented in TBR3 and is already declared in <misctool.h>, but did not previously have glue code. TBR3 says "Applications should never make this call," but it may be useful in system utilities.
This moves the free() call for the file buffer before the malloc() that occurs when closing a temp file, which should at least slightly reduce the chances that the malloc() call fails.
Previously, the code for closing a temporary file assumed that malloc would succeed. If it did not, the code would trash memory and (at least in my testing) crash the system. Now it checks for and handles malloc failures, although they will still lead to the temporary file not being deleted.
Here is a test program illustrating the problem:
#include <stdio.h>
#include <stdlib.h>
int main(void) {
FILE *f = tmpfile();
if (!f)
return 0;
void *p;
do {
p = malloc(8*1024);
} while (p);
fclose(f);
}
This can happen, e.g., if there is an IO error or if there is insufficient free disk space to flush the data. In this case, fclose should return -1 to report an error, but it should still effectively close the stream and deallocate the buffer for it. (This behavior is explicitly specified in the C99 and later standards.)
Previously, ORCA/C effectively left the stream open in these cases. As a result, the buffer was not deallocated. More importantly, this could cause the program to hang at exit, because the stream would never be removed from the list of open files.
Here is an example program that demonstrates the problem:
/*
* Run this on a volume with less than 1MB of free space, e.g. a floppy.
* The fclose return value should be -1 (EOF), indicating an error, but
* the two RealFreeMem values should be close to each other (indicating
* that the buffer was freed), and the program should not hang on exit.
*/
#include <stdio.h>
#include <stddef.h>
#include <memory.h>
#define BUFFER_SIZE 1000000
int main(void) {
size_t i;
int ret;
printf("At start, RealFreeMem = %lu\n", RealFreeMem());
FILE *f = fopen("testfile", "wb");
if (!f)
return 0;
setvbuf(f, NULL, _IOFBF, BUFFER_SIZE);
for (i = 0; i < BUFFER_SIZE; i++) {
putc('x', f);
}
ret = fclose(f);
printf("fclose return value = %d\n", ret);
printf("At end, RealFreeMem = %lu (should be close to start value)\n",
RealFreeMem());
}
This fixes the following issues:
*If n was 0x80000000 or greater, strncmp would return 0 without performing a comparison.
*If n was 0x1000000 or greater, strncmp might compare fewer characters than it should because the high byte of n was effectively ignored, causing it to return 0 when it should not.
Here is an example demonstrating these issues:
#pragma memorymodel 1
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#define LEN 100000
int main(void) {
char *s1 = malloc(LEN+1);
char *s2 = malloc(LEN+1);
if (!s1 || !s2)
return 0;
for (unsigned long i = 0; i < LEN; i++) {
s2[i] = s1[i] = '0' + (i & 0x07);
}
s1[LEN] = 'x';
return strncmp(s1,s2,0xFFFFFFFF);
}
This addresses the following issues:
*If the low-order 16 bits of n were 0x0000, no concatenation would be performed.
*If n was 0x1000000 or greater, the output could be cut off prematurely because the high byte of n was effectively ignored.
The following test program demonstrates these issues:
#pragma memorymodel 1
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#define LEN2 100000
int main(void) {
char *s1 = malloc(LEN2+2);
char *s2 = malloc(LEN2+1);
if (!s1 || !s2)
return 0;
for (unsigned long i = 0; i < LEN2; i++)
s2[i] = '0' + (i & 0x07);
strcpy(s1,"a");
strncat(s1, s2, 0x1000000);
puts(s1);
printf("len = %zu\n", strlen(s1));
}
There were two issues:
*If bit 15 of the n value was set, the second string would not be copied.
*If the length of the second string was 64K or more, it would not be copied properly because the pointers were not updated.
This test program demonstrates both issues:
#pragma memorymodel 1
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#define LEN2 100000
int main(void) {
char *s1 = malloc(LEN2+2);
char *s2 = malloc(LEN2+1);
if (!s1 || !s2)
return 0;
for (unsigned long i = 0; i < LEN2; i++)
s2[i] = '0' + (i & 0x07);
strcpy(s1,"a");
strncat(s1, s2, LEN2);
puts(s1);
printf("len = %zu\n", strlen(s1));
}
Previously, the pointer was not properly updated to account for the bank crossing, so the characters from the second string would be written to the wrong bank.
Here is an example that illustrates this:
#include <memory.h>
#include <string.h>
#include <orca.h>
#include <stdio.h>
int main(void) {
Handle hndl = NewHandle(0x1000f, userid(), 0xC000, 0);
if (toolerror())
return 0;
char *s = *hndl;
s = (void*)((unsigned long)s | 0xffff);
strcpy(s, "foo");
strcat(s, "bar");
strncat(s, "baz", 5);
puts(s);
}
If the format string is empty or contains only %n conversions, then nothing should be read from the stream, so no error should be indicated even if it is at EOF. If a directive does read from the stream and encounter EOF, that will be handled when the directive is processed.
This could cause scanf to pause waiting for input from the console in cases where it should not.