|
/* Copyright (C) 2019 easyaspi314. MIT license. */ |
|
|
|
#include <string.h> |
|
#include <stdlib.h> |
|
#include <stdio.h> |
|
|
|
#ifndef __has_attribute |
|
# define __has_attribute(x) 0 |
|
#endif |
|
|
|
#if defined(__clang__) && __has_attribute(pass_object_size) |
|
# define _PASS_OBJECT_SIZE __attribute__((__pass_object_size__(2))) |
|
#else |
|
# define _PASS_OBJECT_SIZE |
|
#endif |
|
|
|
#if !defined(__GNUC__) |
|
# error "Please use a recent GCC-compatible compiler!" |
|
#endif |
|
|
|
#ifndef __OPTIMIZE__ |
|
# warning "Enable optimizations to make bounds checking work properly!" |
|
#endif |
|
|
|
static __inline__ void flush_line(FILE *__f) { |
|
/* clear out the buffer */ |
|
int __c; |
|
while ((__c = getc(__f)) != '\n' && __c != EOF) {} |
|
} |
|
|
|
#define flush_stdin() flush_line(stdin) |
|
|
|
static char *__gets_safe(char *const __buf _PASS_OBJECT_SIZE, size_t __len, size_t __len_calculated) |
|
{ |
|
char *__newline; |
|
size_t __tmp; |
|
|
|
/* Try to double check the object size */ |
|
if (__len_calculated == 0 && __len != 0 && (__tmp = __builtin_object_size(__buf, 2)) != 0) { |
|
__len_calculated = __tmp; |
|
} |
|
|
|
/* the user didn't supply a length, but we were able to calculate it */ |
|
if (__len == 0 && __len_calculated != 0) |
|
__len = __len_calculated; |
|
/* the user supplied a longer length than what we could calculate. We |
|
* ignore it in release mode, but we return NULL on debug mode. */ |
|
else if (__len > __len_calculated && __len_calculated != 0) { |
|
#ifdef NDEBUG |
|
__len = __len_calculated; |
|
#else |
|
fprintf(stderr, "gets_safe: Supplied length appears to be larger than the buffer size!\n"); |
|
return NULL; |
|
#endif |
|
/* Not even going to try on an unknown length */ |
|
} else if (__len == 0 && __len_calculated == 0) { |
|
#ifndef NDEBUG |
|
fprintf(stderr, "gets_safe: Unknown buffer size!\n"); |
|
#endif |
|
return NULL; |
|
} |
|
#ifndef NDEBUG |
|
if (__buf == NULL) { |
|
fprintf(stderr, "gets_safe: null buffer!\n"); |
|
return NULL; |
|
} |
|
#endif |
|
if (fgets(__buf, __len, stdin) == NULL) { |
|
#ifndef NDEBUG |
|
fprintf(stderr, "gets_safe: error reading line\n"); |
|
#endif |
|
return NULL; |
|
} |
|
__newline = strchr(__buf, '\n'); /* find '\n' */ |
|
if (__newline != NULL) { /* we have a full line, replace the terminator */ |
|
*__newline = '\0'; |
|
} else { /* don't have a full line, clean up the rest */ |
|
flush_stdin(); |
|
} |
|
return __buf; |
|
} |
|
|
|
/* Get the first argument in a variadic macro */ |
|
#define __gets_first_arg(x, ...) (x) |
|
/* pass to __gets_safe the buffer, the user-supplied length or zero, and the compiler-calculated length. */ |
|
#define __gets_impl(x, ...) __gets_safe((x), __gets_first_arg(__VA_ARGS__), __builtin_object_size((x), 2)) |
|
|
|
/* We add two extra arguments to be placeholders when the user doesn't supply a length. */ |
|
#define gets_safe(...) __gets_impl(__VA_ARGS__, 0, 0) |
|
#define gets(...) __gets_impl(__VA_ARGS__, 0, 0) |
|
|
|
#ifdef GETS_SAFE_TEST |
|
int main(void) |
|
{ |
|
unsigned len; |
|
char *buf; |
|
char arr[4]; |
|
|
|
puts("=> Enter a buffer size, larger than zero:"); |
|
|
|
while (scanf("%u", &len) != 1 || len == 0) { |
|
flush_stdin(); |
|
puts("=> Invalid input!"); |
|
} |
|
|
|
buf = malloc(len); |
|
|
|
if (buf == NULL) { |
|
puts("=> Error! Out of memory!"); |
|
return 1; |
|
} |
|
|
|
flush_stdin(); |
|
|
|
printf("=> Enter a string. It should truncate to %u chars:\n", len - 1); |
|
if (gets_safe(buf, len) != NULL) |
|
puts(buf); |
|
|
|
puts("=> Enter another string. It should truncate to 3 chars:"); |
|
if (gets_safe(arr) != NULL) |
|
puts(arr); |
|
|
|
puts("=> You shouldn't be able to enter a string here because the size isn't known."); |
|
if (gets_safe(buf) != NULL) |
|
puts("=> This shouldn't happen!"); |
|
|
|
puts("=> In release mode, you should only be able to type 3 chars here, despite giving a size of 100. In debug mode, you will get an error."); |
|
if (gets_safe(arr, 100) != NULL) |
|
puts(arr); |
|
free(buf); |
|
{ |
|
char *buf2 = malloc(10); |
|
if (buf2 == NULL) { |
|
puts("=> Error! Out of memory!"); |
|
return 1; |
|
} |
|
puts("=> Enter another string. This should truncate to 9 chars."); |
|
if (gets_safe(buf2) != NULL) |
|
puts(buf2); |
|
free(buf2); |
|
} |
|
} |
|
#endif |