This adds a string buffer implementation + unit tests with the following features: - growing reallocation on append - variadic printf-like append function - indenting - error state tracking (stop appending on errors) This will be used in the GLSL shader construction to clean up the bad string length issues we have now. The variadic append is based on one Erik Faye-Lund wrote. v1.1: improve indenting v1.2: fix includes + use memcpy Reviewed-by: Erik Faye-Lund <erik.faye-lund@collabora.com> Signed-off-by: Dave Airlie <airlied@redhat.com>macos/master
parent
4aa32df2cf
commit
e56000111e
@ -0,0 +1,151 @@ |
|||||||
|
/**************************************************************************
|
||||||
|
* |
||||||
|
* Copyright (C) 2019 Red Hat Inc. |
||||||
|
* |
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a |
||||||
|
* copy of this software and associated documentation files (the "Software"), |
||||||
|
* to deal in the Software without restriction, including without limitation |
||||||
|
* the rights to use, copy, modify, merge, publish, distribute, sublicense, |
||||||
|
* and/or sell copies of the Software, and to permit persons to whom the |
||||||
|
* Software is furnished to do so, subject to the following conditions: |
||||||
|
* |
||||||
|
* The above copyright notice and this permission notice shall be included |
||||||
|
* in all copies or substantial portions of the Software. |
||||||
|
* |
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS |
||||||
|
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL |
||||||
|
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR |
||||||
|
* OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, |
||||||
|
* ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR |
||||||
|
* OTHER DEALINGS IN THE SOFTWARE. |
||||||
|
* |
||||||
|
**************************************************************************/ |
||||||
|
#ifndef VREND_STRBUF_H |
||||||
|
#define VREND_STRBUF_H |
||||||
|
|
||||||
|
#include <stdlib.h> |
||||||
|
#include <stdbool.h> |
||||||
|
#include <stdio.h> |
||||||
|
#include <stdarg.h> |
||||||
|
#include "util/u_math.h" |
||||||
|
|
||||||
|
/* shader string buffer */ |
||||||
|
struct vrend_strbuf { |
||||||
|
/* NULL terminated string storage */ |
||||||
|
char *buf; |
||||||
|
/* allocation size (must be >= strlen(str) + 1) */ |
||||||
|
size_t alloc_size; |
||||||
|
/* size of string stored without terminating NULL */ |
||||||
|
size_t size; |
||||||
|
bool error_state; |
||||||
|
int indent_level; |
||||||
|
}; |
||||||
|
|
||||||
|
static inline void strbuf_set_error(struct vrend_strbuf *sb) |
||||||
|
{ |
||||||
|
sb->error_state = true; |
||||||
|
} |
||||||
|
|
||||||
|
static inline bool strbuf_get_error(struct vrend_strbuf *sb) |
||||||
|
{ |
||||||
|
return sb->error_state; |
||||||
|
} |
||||||
|
|
||||||
|
static inline void strbuf_indent(struct vrend_strbuf *sb) |
||||||
|
{ |
||||||
|
sb->indent_level++; |
||||||
|
} |
||||||
|
|
||||||
|
static inline void strbuf_outdent(struct vrend_strbuf *sb) |
||||||
|
{ |
||||||
|
if (sb->indent_level <= 0) { |
||||||
|
strbuf_set_error(sb); |
||||||
|
return; |
||||||
|
} |
||||||
|
sb->indent_level--; |
||||||
|
} |
||||||
|
|
||||||
|
static inline size_t strbuf_get_len(struct vrend_strbuf *sb) |
||||||
|
{ |
||||||
|
return sb->size; |
||||||
|
} |
||||||
|
|
||||||
|
static inline void strbuf_free(struct vrend_strbuf *sb) |
||||||
|
{ |
||||||
|
free(sb->buf); |
||||||
|
} |
||||||
|
|
||||||
|
static inline bool strbuf_alloc(struct vrend_strbuf *sb, int initial_size) |
||||||
|
{ |
||||||
|
sb->buf = malloc(initial_size); |
||||||
|
if (!sb->buf) |
||||||
|
return false; |
||||||
|
sb->alloc_size = initial_size; |
||||||
|
sb->buf[0] = 0; |
||||||
|
sb->error_state = false; |
||||||
|
sb->size = 0; |
||||||
|
sb->indent_level = 0; |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
/* this might need tuning */ |
||||||
|
#define STRBUF_MIN_MALLOC 1024 |
||||||
|
|
||||||
|
static inline void strbuf_append(struct vrend_strbuf *sb, const char *addstr) |
||||||
|
{ |
||||||
|
int new_len = strlen(addstr) + sb->indent_level; |
||||||
|
if (strbuf_get_error(sb)) |
||||||
|
return; |
||||||
|
if (sb->size + new_len + 1 > sb->alloc_size) { |
||||||
|
/* Reallocate to the larger size of current alloc + min realloc,
|
||||||
|
* or the resulting string size if larger. |
||||||
|
*/ |
||||||
|
size_t new_size = MAX2(sb->size + new_len + 1, sb->alloc_size + STRBUF_MIN_MALLOC); |
||||||
|
char *new = realloc(sb->buf, new_size); |
||||||
|
if (!new) { |
||||||
|
strbuf_set_error(sb); |
||||||
|
return; |
||||||
|
} |
||||||
|
sb->buf = new; |
||||||
|
sb->alloc_size = new_size; |
||||||
|
} |
||||||
|
if (sb->indent_level) { |
||||||
|
memset(sb->buf + sb->size, '\t', sb->indent_level); |
||||||
|
sb->size += sb->indent_level; |
||||||
|
sb->buf[sb->size] = '\0'; |
||||||
|
new_len -= sb->indent_level; |
||||||
|
} |
||||||
|
memcpy(sb->buf + sb->size, addstr, new_len); |
||||||
|
sb->size += new_len; |
||||||
|
sb->buf[sb->size] = '\0'; |
||||||
|
} |
||||||
|
|
||||||
|
static inline void strbuf_vappendf(struct vrend_strbuf *sb, const char *fmt, va_list ap) |
||||||
|
{ |
||||||
|
char buf[512]; |
||||||
|
int len = vsnprintf(buf, sizeof(buf), fmt, ap); |
||||||
|
if (len < (int)sizeof(buf)) { |
||||||
|
strbuf_append(sb, buf); |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
char *tmp = malloc(len); |
||||||
|
if (!tmp) { |
||||||
|
strbuf_set_error(sb); |
||||||
|
return; |
||||||
|
} |
||||||
|
vsnprintf(tmp, len, fmt, ap); |
||||||
|
strbuf_append(sb, tmp); |
||||||
|
free(tmp); |
||||||
|
} |
||||||
|
|
||||||
|
__attribute__((format(printf, 2, 3))) |
||||||
|
static inline void strbuf_appendf(struct vrend_strbuf *sb, const char *fmt, ...) |
||||||
|
{ |
||||||
|
va_list va; |
||||||
|
va_start(va, fmt); |
||||||
|
strbuf_vappendf(sb, fmt, va); |
||||||
|
va_end(va); |
||||||
|
} |
||||||
|
#endif |
@ -0,0 +1,274 @@ |
|||||||
|
/**************************************************************************
|
||||||
|
* |
||||||
|
* Copyright (C) 2019 Red Hat Inc. |
||||||
|
* |
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a |
||||||
|
* copy of this software and associated documentation files (the "Software"), |
||||||
|
* to deal in the Software without restriction, including without limitation |
||||||
|
* the rights to use, copy, modify, merge, publish, distribute, sublicense, |
||||||
|
* and/or sell copies of the Software, and to permit persons to whom the |
||||||
|
* Software is furnished to do so, subject to the following conditions: |
||||||
|
* |
||||||
|
* The above copyright notice and this permission notice shall be included |
||||||
|
* in all copies or substantial portions of the Software. |
||||||
|
* |
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS |
||||||
|
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL |
||||||
|
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR |
||||||
|
* OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, |
||||||
|
* ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR |
||||||
|
* OTHER DEALINGS IN THE SOFTWARE. |
||||||
|
* |
||||||
|
**************************************************************************/ |
||||||
|
#include <check.h> |
||||||
|
#include <stdio.h> |
||||||
|
#include "../src/vrend_strbuf.h" |
||||||
|
|
||||||
|
/* Test the vrend strbuf implementation */ |
||||||
|
|
||||||
|
START_TEST(strbuf_init) |
||||||
|
{ |
||||||
|
struct vrend_strbuf sb; |
||||||
|
bool ret; |
||||||
|
ret = strbuf_alloc(&sb, 1024); |
||||||
|
ck_assert_int_eq(ret, true); |
||||||
|
ck_assert_int_eq(sb.alloc_size, 1024); |
||||||
|
strbuf_free(&sb); |
||||||
|
} |
||||||
|
END_TEST |
||||||
|
|
||||||
|
START_TEST(strbuf_add_small_string) |
||||||
|
{ |
||||||
|
struct vrend_strbuf sb; |
||||||
|
bool ret; |
||||||
|
char str[27] = {}; |
||||||
|
ret = strbuf_alloc(&sb, 1024); |
||||||
|
ck_assert_int_eq(ret, true); |
||||||
|
|
||||||
|
for (int i = 0; i < 26; i++) |
||||||
|
str[i] = 'a' + i; |
||||||
|
str[26] = 0; |
||||||
|
strbuf_append(&sb, str); |
||||||
|
ck_assert_int_eq(strbuf_get_error(&sb), false); |
||||||
|
ck_assert_int_eq(sb.size, strlen(sb.buf)); |
||||||
|
strbuf_free(&sb); |
||||||
|
} |
||||||
|
END_TEST |
||||||
|
|
||||||
|
START_TEST(strbuf_add_small_string_twice) |
||||||
|
{ |
||||||
|
struct vrend_strbuf sb; |
||||||
|
bool ret; |
||||||
|
char str[27] = {}; |
||||||
|
ret = strbuf_alloc(&sb, 1024); |
||||||
|
ck_assert_int_eq(ret, true); |
||||||
|
|
||||||
|
for (int i = 0; i < 26; i++) |
||||||
|
str[i] = 'a' + i; |
||||||
|
str[26] = 0; |
||||||
|
strbuf_append(&sb, str); |
||||||
|
strbuf_append(&sb, str); |
||||||
|
ck_assert_int_eq(strbuf_get_error(&sb), false); |
||||||
|
ck_assert_int_eq(strbuf_get_len(&sb), strlen(sb.buf)); |
||||||
|
strbuf_free(&sb); |
||||||
|
} |
||||||
|
END_TEST |
||||||
|
|
||||||
|
START_TEST(strbuf_add_large_string) |
||||||
|
{ |
||||||
|
struct vrend_strbuf sb; |
||||||
|
bool ret; |
||||||
|
char str[256]; |
||||||
|
ret = strbuf_alloc(&sb, 128); |
||||||
|
ck_assert_int_eq(ret, true); |
||||||
|
|
||||||
|
for (int i = 0; i < 255; i++) |
||||||
|
str[i] = 'a' + (i % 26); |
||||||
|
str[255] = 0; |
||||||
|
strbuf_append(&sb, str); |
||||||
|
|
||||||
|
ck_assert_int_eq(strbuf_get_error(&sb), false); |
||||||
|
ck_assert_int_eq(strbuf_get_len(&sb), strlen(sb.buf)); |
||||||
|
ck_assert_int_eq(sb.alloc_size, 128 + STRBUF_MIN_MALLOC); |
||||||
|
strbuf_free(&sb); |
||||||
|
} |
||||||
|
END_TEST |
||||||
|
|
||||||
|
START_TEST(strbuf_add_huge_string) |
||||||
|
{ |
||||||
|
struct vrend_strbuf sb; |
||||||
|
bool ret; |
||||||
|
char str[2048]; |
||||||
|
ret = strbuf_alloc(&sb, 128); |
||||||
|
ck_assert_int_eq(ret, true); |
||||||
|
|
||||||
|
for (int i = 0; i < 2047; i++) |
||||||
|
str[i] = 'a' + (i % 26); |
||||||
|
str[2047] = 0; |
||||||
|
strbuf_append(&sb, str); |
||||||
|
|
||||||
|
ck_assert_int_eq(strbuf_get_error(&sb), false); |
||||||
|
ck_assert_int_eq(strbuf_get_len(&sb), strlen(sb.buf)); |
||||||
|
ck_assert_int_eq(sb.alloc_size, 2048); |
||||||
|
ck_assert_int_ge(sb.alloc_size, strbuf_get_len(&sb) + 1); |
||||||
|
strbuf_free(&sb); |
||||||
|
} |
||||||
|
END_TEST |
||||||
|
|
||||||
|
START_TEST(strbuf_test_boundary) |
||||||
|
{ |
||||||
|
struct vrend_strbuf sb; |
||||||
|
bool ret; |
||||||
|
char str[128]; |
||||||
|
ret = strbuf_alloc(&sb, 128); |
||||||
|
ck_assert_int_eq(ret, true); |
||||||
|
|
||||||
|
for (int i = 0; i < 127; i++) |
||||||
|
str[i] = 'a' + (i % 26); |
||||||
|
str[127] = 0; |
||||||
|
strbuf_append(&sb, str); |
||||||
|
ck_assert_int_eq(strbuf_get_error(&sb), false); |
||||||
|
ck_assert_int_eq(strbuf_get_len(&sb), strlen(sb.buf)); |
||||||
|
ck_assert_int_eq(sb.alloc_size, 128); |
||||||
|
ck_assert_int_ge(sb.alloc_size, strbuf_get_len(&sb) + 1); |
||||||
|
strbuf_free(&sb); |
||||||
|
} |
||||||
|
END_TEST |
||||||
|
|
||||||
|
START_TEST(strbuf_test_boundary2) |
||||||
|
{ |
||||||
|
struct vrend_strbuf sb; |
||||||
|
bool ret; |
||||||
|
char str[513]; |
||||||
|
ret = strbuf_alloc(&sb, 1024); |
||||||
|
ck_assert_int_eq(ret, true); |
||||||
|
|
||||||
|
for (int i = 0; i < 512; i++) |
||||||
|
str[i] = 'a' + (i % 26); |
||||||
|
str[512] = 0; |
||||||
|
strbuf_append(&sb, str); |
||||||
|
strbuf_append(&sb, str); |
||||||
|
ck_assert_int_eq(strbuf_get_error(&sb), false); |
||||||
|
ck_assert_int_eq(strbuf_get_len(&sb), strlen(sb.buf)); |
||||||
|
/* we should have 512 + 512 + 1 at least */ |
||||||
|
ck_assert_int_ge(sb.alloc_size, strbuf_get_len(&sb) + 1); |
||||||
|
ck_assert_int_gt(sb.alloc_size, 1024); |
||||||
|
|
||||||
|
strbuf_free(&sb); |
||||||
|
} |
||||||
|
END_TEST |
||||||
|
|
||||||
|
START_TEST(strbuf_test_indent) |
||||||
|
{ |
||||||
|
struct vrend_strbuf sb; |
||||||
|
bool ret; |
||||||
|
char str[9]; |
||||||
|
ret = strbuf_alloc(&sb, 1024); |
||||||
|
ck_assert_int_eq(ret, true); |
||||||
|
|
||||||
|
for (int i = 0; i < 8; i++) |
||||||
|
str[i] = 'a' + i; |
||||||
|
str[8] = 0; |
||||||
|
|
||||||
|
strbuf_indent(&sb); |
||||||
|
strbuf_append(&sb, str); |
||||||
|
strbuf_outdent(&sb); |
||||||
|
strbuf_append(&sb, str); |
||||||
|
/* make sure the TAB got added */ |
||||||
|
ck_assert_int_eq(strbuf_get_error(&sb), false); |
||||||
|
|
||||||
|
ck_assert_str_eq(sb.buf, "\tabcdefghabcdefgh"); |
||||||
|
strbuf_free(&sb); |
||||||
|
} |
||||||
|
END_TEST |
||||||
|
|
||||||
|
START_TEST(strbuf_test_indent2) |
||||||
|
{ |
||||||
|
struct vrend_strbuf sb; |
||||||
|
bool ret; |
||||||
|
char str[513]; |
||||||
|
ret = strbuf_alloc(&sb, 1024); |
||||||
|
ck_assert_int_eq(ret, true); |
||||||
|
|
||||||
|
strbuf_indent(&sb); |
||||||
|
for (int i = 0; i < 512; i++) |
||||||
|
str[i] = 'a' + (i % 26); |
||||||
|
str[512] = 0; |
||||||
|
strbuf_append(&sb, str); |
||||||
|
strbuf_outdent(&sb); |
||||||
|
/* make sure the TAB got added */ |
||||||
|
ck_assert_int_eq(strbuf_get_len(&sb), strlen(str) + 1); |
||||||
|
strbuf_append(&sb, str); |
||||||
|
ck_assert_int_eq(strbuf_get_error(&sb), false); |
||||||
|
ck_assert_int_eq(strbuf_get_len(&sb), strlen(sb.buf)); |
||||||
|
/* we should have 512 + 512 + 1 at least */ |
||||||
|
ck_assert_int_ge(sb.alloc_size, strbuf_get_len(&sb) + 1); |
||||||
|
ck_assert_int_gt(sb.alloc_size, 1024); |
||||||
|
|
||||||
|
strbuf_free(&sb); |
||||||
|
} |
||||||
|
END_TEST |
||||||
|
|
||||||
|
START_TEST(strbuf_test_appendf) |
||||||
|
{ |
||||||
|
struct vrend_strbuf sb; |
||||||
|
bool ret; |
||||||
|
ret = strbuf_alloc(&sb, 1024); |
||||||
|
ck_assert_int_eq(ret, true); |
||||||
|
strbuf_appendf(&sb, "%d", 5); |
||||||
|
ck_assert_str_eq(sb.buf, "5"); |
||||||
|
strbuf_free(&sb); |
||||||
|
} |
||||||
|
END_TEST |
||||||
|
|
||||||
|
START_TEST(strbuf_test_appendf_str) |
||||||
|
{ |
||||||
|
struct vrend_strbuf sb; |
||||||
|
bool ret; |
||||||
|
ret = strbuf_alloc(&sb, 1024); |
||||||
|
ck_assert_int_eq(ret, true); |
||||||
|
strbuf_appendf(&sb, "%s5", "hello"); |
||||||
|
ck_assert_str_eq(sb.buf, "hello5"); |
||||||
|
strbuf_free(&sb); |
||||||
|
} |
||||||
|
END_TEST |
||||||
|
|
||||||
|
static Suite *init_suite(void) |
||||||
|
{ |
||||||
|
Suite *s; |
||||||
|
TCase *tc_core; |
||||||
|
|
||||||
|
s = suite_create("vrend_strbuf"); |
||||||
|
tc_core = tcase_create("strbuf"); |
||||||
|
|
||||||
|
suite_add_tcase(s, tc_core); |
||||||
|
|
||||||
|
tcase_add_test(tc_core, strbuf_init); |
||||||
|
tcase_add_test(tc_core, strbuf_add_small_string); |
||||||
|
tcase_add_test(tc_core, strbuf_add_small_string_twice); |
||||||
|
tcase_add_test(tc_core, strbuf_add_large_string); |
||||||
|
tcase_add_test(tc_core, strbuf_add_huge_string); |
||||||
|
tcase_add_test(tc_core, strbuf_test_boundary); |
||||||
|
tcase_add_test(tc_core, strbuf_test_boundary2); |
||||||
|
tcase_add_test(tc_core, strbuf_test_indent); |
||||||
|
tcase_add_test(tc_core, strbuf_test_indent2); |
||||||
|
tcase_add_test(tc_core, strbuf_test_appendf); |
||||||
|
tcase_add_test(tc_core, strbuf_test_appendf_str); |
||||||
|
return s; |
||||||
|
} |
||||||
|
|
||||||
|
int main(void) |
||||||
|
{ |
||||||
|
Suite *s; |
||||||
|
SRunner *sr; |
||||||
|
int number_failed; |
||||||
|
|
||||||
|
s = init_suite(); |
||||||
|
sr = srunner_create(s); |
||||||
|
|
||||||
|
srunner_run_all(sr, CK_NORMAL); |
||||||
|
number_failed = srunner_ntests_failed(sr); |
||||||
|
srunner_free(sr); |
||||||
|
return number_failed == 0 ? EXIT_SUCCESS : EXIT_FAILURE; |
||||||
|
}; |
Loading…
Reference in new issue