Skip to content

Commit

Permalink
Initial implementation of our custom r_str_printf api
Browse files Browse the repository at this point in the history
  • Loading branch information
trufae authored Feb 8, 2024
1 parent 4e7470a commit bf78d09
Show file tree
Hide file tree
Showing 6 changed files with 171 additions and 1 deletion.
2 changes: 2 additions & 0 deletions libr/include/r_util/r_str.h
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,8 @@ R_API char *r_str_version(const char *program);
R_API char *r_str_ss(const char* msg, const char *nl, int cs);

R_API char *r_str_after(char *s, char c);
R_API int r_str_printf(R_NONNULL char *buffer, size_t buffer_size, R_NONNULL const char *format, ...);
/// __attribute__ ((format (printf, 3, 4)));
R_API int r_str_scanf(R_NONNULL const char *buffer, R_NONNULL const char *format, ...);
/// __attribute__ ((format (scanf, 2, 3)));

Expand Down
2 changes: 1 addition & 1 deletion libr/util/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ OBJS+=udiff.o bdiff.o stack.o queue.o tree.o idpool.o assert.o bplist.o
OBJS+=punycode.o pkcs7.o x509.o asn1.o asn1_str.o json_parser.o json_indent.o skiplist.o
OBJS+=pj.o rbtree.o intervaltree.o qrcode.o vector.o str_constpool.o str_trim.o
OBJS+=ascii_table.o protobuf.o graph_drawable.o axml.o sstext.o new_rbtree.o token.o
OBJS+=rvc.o rvc_git.o rvc_rvc.o bscanf.o
OBJS+=rvc.o rvc_git.o rvc_rvc.o bscanf.o rprintf.o

ifeq (${HAVE_GPERF},1)
OBJS+=d/ascii.o
Expand Down
1 change: 1 addition & 0 deletions libr/util/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ r_util_sources = [
'list.c',
'log.c',
'bscanf.c',
'rprintf.c',
'mem.c',
'name.c',
'new_rbtree.c',
Expand Down
145 changes: 145 additions & 0 deletions libr/util/rprintf.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
// snprintf implementation for radare2 (safe+custom extensions)

// %b for boolean (true|false)
// %n is forbidden
// modifier for padding
// %P -> pad with zeroes
// always null terminate

#include <r_util.h>

enum flag_itoa {
FILL_ZERO = 1,
PUT_PLUS = 2,
PUT_MINUS = 4,
BASE_2 = 8,
BASE_10 = 16,
};

static char *sitoa(char * buf, char *buf_end, unsigned int num, int width, enum flag_itoa flags) {
unsigned int base;
if (flags & BASE_2) {
base = 2;
} else if (flags & BASE_10) {
base = 10;
} else {
base = 16;
}
char tmp[32];
char *p = tmp;
do {
int rem = num % base;
*p++ = (rem <= 9) ? (rem + '0') : (rem + 'a' - 0xA);
} while ((num /= base));
width -= p - tmp;
char fill = (flags & FILL_ZERO)? '0' : ' ';
while (0 <= --width) {
*(buf++) = fill;
}
if (flags & PUT_MINUS) {
*(buf++) = '-';
} else if (flags & PUT_PLUS) {
*(buf++) = '+';
}
do {
*(buf++) = *(--p);
} while (tmp < p);
return buf;
}

static int my_vsnprintf(char * buf, size_t buf_size, const char *fmt, va_list va) {
char c;
const char *save = buf;
char *buf_end = buf + buf_size;
while ((c = *fmt++)) {
int width = 0;
enum flag_itoa flags = 0;
if (c != '%') {
*(buf++) = c;
continue;
}
redo_spec:
c = *fmt++;
switch (c) {
case '%':
*(buf++) = c;
break;
case 'c':
*(buf++) = va_arg (va, int);
break;
case 'd':
{
int num = va_arg (va, int);
if (num < 0) {
num = -num;
flags |= PUT_MINUS;
}
buf = sitoa (buf, buf_end, num, width, flags | BASE_10);
}
break;
case 'u':
buf = sitoa (buf, buf_end, va_arg (va, unsigned int), width, flags | BASE_10);
break;
case 'x':
buf = sitoa (buf, buf_end, va_arg (va, unsigned int), width, flags);
break;
case 'b':
buf = sitoa (buf, buf_end, va_arg (va, unsigned int), width, flags | BASE_2);
break;
case 's':;
const char *p = va_arg (va, const char *);
if (p) {
while (*p)
*(buf++) = *(p++);
}
break;
case 'm':;
const uint8_t *m = va_arg (va, const uint8_t *);
width = R_MIN (width, 64); // buffer limited to 256!
if (m)
for (;;) {
buf = sitoa (buf, buf_end, *(m++), 2, FILL_ZERO);
if (--width <= 0)
break;
*(buf++) = ':';
}
break;
case '0':
if (!width) {
flags |= FILL_ZERO;
}
// fall through
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
width = width * 10 + c - '0';
goto redo_spec;
case '*':
width = va_arg (va, unsigned int);
goto redo_spec;
case '+':
flags |= PUT_PLUS;
goto redo_spec;
case '\0':
default:
*(buf++) = '?';
}
width = 0;
}
*buf = '\0';
return buf - save;
}

R_API int r_str_printf(R_NONNULL char * buf, size_t buf_size, R_NONNULL const char * fmt, ...) {
va_list va;
va_start (va,fmt);
int ret = my_vsnprintf (buf, buf_size, fmt, va);
va_end (va);
return ret;
}
2 changes: 2 additions & 0 deletions test/unit/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ if get_option('enable_tests')
'stack',
'str',
'strbuf',
'scanf',
'printf',
'table',
'tree',
'uleb128',
Expand Down
20 changes: 20 additions & 0 deletions test/unit/test_printf.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#include <r_util.h>
#include "minunit.h"

bool test_r_str_printf(void) {
char res[32];
int count = r_str_printf (res, sizeof (res), "Hello %s", "World");
mu_assert_streq (res, "Hello World", "truncated string in custom scanf failed");
mu_assert_eq (count, 11, "return value");

mu_end;
}

bool all_tests(void) {
mu_run_test (test_r_str_printf);
return tests_passed != tests_run;
}

int main(int argc, char **argv) {
return all_tests ();
}

0 comments on commit bf78d09

Please sign in to comment.