Skip to content
Merged
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
4 changes: 2 additions & 2 deletions .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ jobs:
# your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v5

# Add any setup steps before running the `github/codeql-action/init` action.
# This includes steps like installing compilers or runtimes (`actions/setup-node`
Expand Down Expand Up @@ -89,7 +89,7 @@ jobs:
if: matrix.build-mode == 'manual'
shell: bash
run: |
make BUILD=debug static shared
make -j 4 BUILD=strict

- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v4
Expand Down
103 changes: 67 additions & 36 deletions io/stdoc_scanf.c
Comment thread
uoctamika marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
// stdoc/io/stdoc_scanf.c

#include <stdoc/io/stdoc_scanf.h>
#include <stdbool.h>
#include <stddef.h>
Expand All @@ -10,17 +8,17 @@
#endif

/*
* Syscall read for aarch64, x86_64, i386, arm.
* Returns number of bytes read, 0 on EOF, -1 on error.
* Raw read syscall. Returns number of bytes read, 0 on EOF, -1 on error.
* Supports aarch64, x86_64, i386, arm.
*/
static long syscall_read(void* buf, unsigned long count)
{
long ret;
#if defined(__aarch64__)
register long x8 __asm__("x8") = 63;
register long x0 __asm__("x0") = 0;
register long x8 __asm__("x8") = 63; // read syscall number
register long x0 __asm__("x0") = 0; // stdin file descriptor
register long x1 __asm__("x1") = (long)buf;
register long x2 __asm__("x2") = count;
register unsigned long x2 __asm__("x2") = count; // use unsigned to avoid sign warning
__asm__ volatile("svc #0"
: "=r"(x0)
: "r"(x8), "r"(x0), "r"(x1), "r"(x2)
Expand Down Expand Up @@ -51,18 +49,24 @@ static long syscall_read(void* buf, unsigned long count)
}

/*
* Buffered input context.
* Scan context holds the input buffer and state.
* - buf: internal buffer
* - pos: current read index in buffer
* - len: number of valid bytes in buffer
* - eof: flag set when end-of-file reached
* - pushback: single-character pushback (-1 if none)
*/
struct scan_ctx {
char buf[STDOC_SCANF_BUF_SIZE];
int pos; // current read position in buffer
int len; // number of valid bytes in buffer
int eof; // EOF flag
int pushback; // single-character pushback (-1 if none)
int pos;
int len;
int eof;
int pushback;
};

/*
* Refill buffer from stdin, return first character or -1 on EOF/error.
* Refill the buffer by reading from stdin.
* Returns the first character read, or -1 on EOF/error.
*/
static int refill(struct scan_ctx* ctx)
{
Expand All @@ -78,7 +82,8 @@ static int refill(struct scan_ctx* ctx)
}

/*
* Get next character, consuming it. Handles pushback and buffer.
* Get the next character from input, consuming it.
* Handles pushback and buffer refill. Returns -1 on EOF.
*/
static int next_char(struct scan_ctx* ctx)
{
Expand All @@ -93,15 +98,16 @@ static int next_char(struct scan_ctx* ctx)
}

/*
* Push one character back (only one level guaranteed).
* Push one character back into the input stream.
* Only one level of pushback is guaranteed.
*/
static void unget_char(struct scan_ctx* ctx, int c)
{
ctx->pushback = c;
}

/*
* Skip whitespace: space, tab, newline, carriage return, form feed, vertical tab.
* Skip whitespace characters: space, tab, newline, carriage return, form feed, vertical tab.
*/
static void skip_whitespace(struct scan_ctx* ctx)
{
Expand All @@ -115,8 +121,8 @@ static void skip_whitespace(struct scan_ctx* ctx)
}

/*
* Read unsigned integer in given base (10 or 16).
* Returns 1 on success, 0 on failure.
* Read an unsigned integer in the given base (10 or 16).
* Returns 1 on success and stores the value in 'out', 0 on failure.
*/
static int read_unsigned(struct scan_ctx* ctx, unsigned int* out, int base)
{
Expand All @@ -127,7 +133,7 @@ static int read_unsigned(struct scan_ctx* ctx, unsigned int* out, int base)
unsigned long val = 0;
int consumed = 0;

/* Handle optional 0x prefix for hex */
// Handle optional 0x prefix for hexadecimal numbers
if (base == 16 && c == '0') {
int n = next_char(ctx);
if (n == 'x' || n == 'X') {
Expand All @@ -149,9 +155,12 @@ static int read_unsigned(struct scan_ctx* ctx, unsigned int* out, int base)
digit = c - 'A' + 10;

if (digit < 0 || digit >= base) break;
if (val > (ULONG_MAX - digit) / (unsigned long)base) break; /* overflow protection */

val = val * base + digit;
// Use unsigned casting to avoid sign-conversion warnings
unsigned long udigit = (unsigned long)digit;
unsigned long ubase = (unsigned long)base;
if (val > (ULONG_MAX - udigit) / ubase) break; // overflow check
val = val * ubase + udigit;
consumed = 1;
c = next_char(ctx);
}
Expand All @@ -162,7 +171,8 @@ static int read_unsigned(struct scan_ctx* ctx, unsigned int* out, int base)
}

/*
* Read signed decimal integer.
* Read a signed decimal integer.
* Returns 1 on success and stores the value in 'out', 0 on failure.
*/
static int read_signed(struct scan_ctx* ctx, int* out)
{
Expand All @@ -179,11 +189,12 @@ static int read_signed(struct scan_ctx* ctx, int* out)
}

if (c == -1 || (c < '0' || c > '9')) return 0;
unget_char(ctx, c); /* put first digit back for read_unsigned */
unget_char(ctx, c); // push back first digit for read_unsigned

unsigned int uval;
if (!read_unsigned(ctx, &uval, 10)) return 0;

// Handle signed overflow
if (sign == -1) {
if (uval > (unsigned int)INT_MAX + 1U)
*out = INT_MIN;
Expand All @@ -199,7 +210,8 @@ static int read_signed(struct scan_ctx* ctx, int* out)
}

/*
* Read a single character (no whitespace skipping).
* Read a single character (does NOT skip whitespace).
* Returns 1 on success and stores the character in 'out', 0 on EOF.
*/
static int read_char(struct scan_ctx* ctx, char* out)
{
Expand All @@ -210,8 +222,9 @@ static int read_char(struct scan_ctx* ctx, char* out)
}

/*
* Read a whitespace-delimited string into buffer, null-terminated.
* max_len includes space for null terminator.
* Read a whitespace-delimited string into the provided buffer.
* The buffer will be null-terminated. max_len includes the null terminator.
* Returns 1 on success, 0 on failure.
*/
static int read_string(struct scan_ctx* ctx, char* out, size_t max_len)
{
Expand All @@ -232,7 +245,8 @@ static int read_string(struct scan_ctx* ctx, char* out, size_t max_len)
}

/*
* Read a pointer value (hexadecimal, optional 0x prefix).
* Read a pointer value in hexadecimal (optional 0x prefix).
* Returns 1 on success and stores the pointer in 'out', 0 on failure.
*/
static int read_pointer(struct scan_ctx* ctx, void** out)
{
Expand All @@ -243,6 +257,7 @@ static int read_pointer(struct scan_ctx* ctx, void** out)
unsigned long addr = 0;
int consumed = 0;

// Optional 0x prefix
if (c == '0') {
int n = next_char(ctx);
if (n == 'x' || n == 'X') {
Expand All @@ -264,8 +279,9 @@ static int read_pointer(struct scan_ctx* ctx, void** out)
digit = c - 'A' + 10;
else break;

if (addr > (ULONG_MAX - digit) / 16) break;
addr = addr * 16 + digit;
unsigned long udigit = (unsigned long)digit;
if (addr > (ULONG_MAX - udigit) / 16) break; // overflow check
addr = addr * 16 + udigit;
consumed = 1;
c = next_char(ctx);
}
Expand All @@ -277,6 +293,19 @@ static int read_pointer(struct scan_ctx* ctx, void** out)

/*
* stdoc_scanf – minimal buffered scanf implementation.
* Reads from stdin according to the format string and stores values into the provided arguments.
*
* Supported format specifiers:
* %d – signed decimal integer (int*)
* %u – unsigned decimal integer (unsigned int*)
* %x – unsigned hexadecimal integer (unsigned int*)
* %c – character (char*) – does NOT skip whitespace
* %s – string (char*) – skips leading whitespace, stops at whitespace
* %p – pointer (void**) – hexadecimal with optional 0x prefix
* %% – literal '%'
*
* Returns the number of successfully matched and assigned items,
* or -1 if EOF occurs before any conversion.
*/
int stdoc_scanf(const char* format, ...)
{
Expand All @@ -295,7 +324,8 @@ int stdoc_scanf(const char* format, ...)
while (*p) {
if (*p == '%') {
p++;
if (*p == '%') { /* literal '%' */
// Handle literal '%'
if (*p == '%') {
int c = next_char(&ctx);
if (c != '%') {
va_end(args);
Expand All @@ -305,7 +335,8 @@ int stdoc_scanf(const char* format, ...)
continue;
}

bool skip_ws = (*p != 'c'); /* skip whitespace except for %c */
// Skip whitespace for all specifiers except %c
bool skip_ws = (*p != 'c');
if (skip_ws) skip_whitespace(&ctx);

switch (*p) {
Expand Down Expand Up @@ -343,7 +374,7 @@ int stdoc_scanf(const char* format, ...)
}
case 's': {
char* ptr = va_arg(args, char*);
if (read_string(&ctx, ptr, 4096))
if (read_string(&ctx, ptr, 4096)) // default limit, no width specifier yet
items++;
else
goto done;
Expand All @@ -357,16 +388,16 @@ int stdoc_scanf(const char* format, ...)
goto done;
break;
}
default: /* unknown specifier, abort */
goto done;
default:
goto done; // unknown specifier, abort
}
p++;
} else if (*p == ' ' || *p == '\t' || *p == '\n') {
/* Whitespace in format: skip any whitespace in input */
// Whitespace in format: skip any whitespace in input
skip_whitespace(&ctx);
p++;
} else {
/* Literal character match */
// Match literal character
int c = next_char(&ctx);
if (c != (unsigned char)*p)
goto done;
Expand Down
Loading