Merge branch 'tb/ban-strtok'

Mark strtok() and strtok_r() to be banned.

* tb/ban-strtok:
  banned.h: mark `strtok()` and `strtok_r()` as banned
  t/helper/test-json-writer.c: avoid using `strtok()`
  t/helper/test-oidmap.c: avoid using `strtok()`
  t/helper/test-hashmap.c: avoid using `strtok()`
  string-list: introduce `string_list_setlen()`
  string-list: multi-delimiter `string_list_split_in_place()`
This commit is contained in:
Junio C Hamano 2023-05-02 10:13:35 -07:00
commit d699e27bd4
12 changed files with 161 additions and 51 deletions

View File

@ -18,6 +18,10 @@
#define strncpy(x,y,n) BANNED(strncpy) #define strncpy(x,y,n) BANNED(strncpy)
#undef strncat #undef strncat
#define strncat(x,y,n) BANNED(strncat) #define strncat(x,y,n) BANNED(strncat)
#undef strtok
#define strtok(x,y) BANNED(strtok)
#undef strtok_r
#define strtok_r(x,y,z) BANNED(strtok_r)
#undef sprintf #undef sprintf
#undef vsprintf #undef vsprintf

View File

@ -1685,11 +1685,11 @@ static int get_schedule_cmd(const char **cmd, int *is_available)
if (is_available) if (is_available)
*is_available = 0; *is_available = 0;
string_list_split_in_place(&list, testing, ',', -1); string_list_split_in_place(&list, testing, ",", -1);
for_each_string_list_item(item, &list) { for_each_string_list_item(item, &list) {
struct string_list pair = STRING_LIST_INIT_NODUP; struct string_list pair = STRING_LIST_INIT_NODUP;
if (string_list_split_in_place(&pair, item->string, ':', 2) != 2) if (string_list_split_in_place(&pair, item->string, ":", 2) != 2)
continue; continue;
if (!strcmp(*cmd, pair.items[0].string)) { if (!strcmp(*cmd, pair.items[0].string)) {

2
diff.c
View File

@ -139,7 +139,7 @@ static int parse_dirstat_params(struct diff_options *options, const char *params
int i; int i;
if (*params_copy) if (*params_copy)
string_list_split_in_place(&params, params_copy, ',', -1); string_list_split_in_place(&params, params_copy, ",", -1);
for (i = 0; i < params.nr; i++) { for (i = 0; i < params.nr; i++) {
const char *p = params.items[i].string; const char *p = params.items[i].string;
if (!strcmp(p, "changes")) { if (!strcmp(p, "changes")) {

View File

@ -964,7 +964,7 @@ void string_list_add_refs_from_colon_sep(struct string_list *list,
char *globs_copy = xstrdup(globs); char *globs_copy = xstrdup(globs);
int i; int i;
string_list_split_in_place(&split, globs_copy, ':', -1); string_list_split_in_place(&split, globs_copy, ":", -1);
string_list_remove_empty_items(&split, 0); string_list_remove_empty_items(&split, 0);
for (i = 0; i < split.nr; i++) for (i = 0; i < split.nr; i++)

View File

@ -651,7 +651,7 @@ static struct snapshot *create_snapshot(struct packed_ref_store *refs)
snapshot->buf, snapshot->buf,
snapshot->eof - snapshot->buf); snapshot->eof - snapshot->buf);
string_list_split_in_place(&traits, p, ' ', -1); string_list_split_in_place(&traits, p, " ", -1);
if (unsorted_string_list_has_string(&traits, "fully-peeled")) if (unsorted_string_list_has_string(&traits, "fully-peeled"))
snapshot->peeled = PEELED_FULLY; snapshot->peeled = PEELED_FULLY;

View File

@ -203,6 +203,15 @@ void string_list_clear_func(struct string_list *list, string_list_clear_func_t c
list->nr = list->alloc = 0; list->nr = list->alloc = 0;
} }
void string_list_setlen(struct string_list *list, size_t nr)
{
if (list->strdup_strings)
BUG("cannot setlen a string_list which owns its entries");
if (nr > list->nr)
BUG("cannot grow a string_list with setlen");
list->nr = nr;
}
struct string_list_item *string_list_append_nodup(struct string_list *list, struct string_list_item *string_list_append_nodup(struct string_list *list,
char *string) char *string)
{ {
@ -301,7 +310,7 @@ int string_list_split(struct string_list *list, const char *string,
} }
int string_list_split_in_place(struct string_list *list, char *string, int string_list_split_in_place(struct string_list *list, char *string,
int delim, int maxsplit) const char *delim, int maxsplit)
{ {
int count = 0; int count = 0;
char *p = string, *end; char *p = string, *end;
@ -315,7 +324,7 @@ int string_list_split_in_place(struct string_list *list, char *string,
string_list_append(list, p); string_list_append(list, p);
return count; return count;
} }
end = strchr(p, delim); end = strpbrk(p, delim);
if (end) { if (end) {
*end = '\0'; *end = '\0';
string_list_append(list, p); string_list_append(list, p);

View File

@ -134,6 +134,16 @@ typedef void (*string_list_clear_func_t)(void *p, const char *str);
/** Call a custom clear function on each util pointer */ /** Call a custom clear function on each util pointer */
void string_list_clear_func(struct string_list *list, string_list_clear_func_t clearfunc); void string_list_clear_func(struct string_list *list, string_list_clear_func_t clearfunc);
/*
* Set the length of a string_list to `nr`, provided that (a) `list`
* does not own its own storage, and (b) that `nr` is no larger than
* `list->nr`.
*
* Useful when "shrinking" `list` to write over existing entries that
* are no longer used without reallocating.
*/
void string_list_setlen(struct string_list *list, size_t nr);
/** /**
* Apply `func` to each item. If `func` returns nonzero, the * Apply `func` to each item. If `func` returns nonzero, the
* iteration aborts and the return value is propagated. * iteration aborts and the return value is propagated.
@ -270,5 +280,5 @@ int string_list_split(struct string_list *list, const char *string,
* list->strdup_strings must *not* be set. * list->strdup_strings must *not* be set.
*/ */
int string_list_split_in_place(struct string_list *list, char *string, int string_list_split_in_place(struct string_list *list, char *string,
int delim, int maxsplit); const char *delim, int maxsplit);
#endif /* STRING_LIST_H */ #endif /* STRING_LIST_H */

View File

@ -2,6 +2,7 @@
#include "git-compat-util.h" #include "git-compat-util.h"
#include "hashmap.h" #include "hashmap.h"
#include "strbuf.h" #include "strbuf.h"
#include "string-list.h"
struct test_entry struct test_entry
{ {
@ -150,6 +151,7 @@ static void perf_hashmap(unsigned int method, unsigned int rounds)
*/ */
int cmd__hashmap(int argc, const char **argv) int cmd__hashmap(int argc, const char **argv)
{ {
struct string_list parts = STRING_LIST_INIT_NODUP;
struct strbuf line = STRBUF_INIT; struct strbuf line = STRBUF_INIT;
int icase; int icase;
struct hashmap map = HASHMAP_INIT(test_entry_cmp, &icase); struct hashmap map = HASHMAP_INIT(test_entry_cmp, &icase);
@ -159,21 +161,26 @@ int cmd__hashmap(int argc, const char **argv)
/* process commands from stdin */ /* process commands from stdin */
while (strbuf_getline(&line, stdin) != EOF) { while (strbuf_getline(&line, stdin) != EOF) {
char *cmd, *p1 = NULL, *p2 = NULL; char *cmd, *p1, *p2;
unsigned int hash = 0; unsigned int hash = 0;
struct test_entry *entry; struct test_entry *entry;
/* break line into command and up to two parameters */ /* break line into command and up to two parameters */
cmd = strtok(line.buf, DELIM); string_list_setlen(&parts, 0);
string_list_split_in_place(&parts, line.buf, DELIM, 2);
string_list_remove_empty_items(&parts, 0);
/* ignore empty lines */ /* ignore empty lines */
if (!cmd || *cmd == '#') if (!parts.nr)
continue;
if (!*parts.items[0].string || *parts.items[0].string == '#')
continue; continue;
p1 = strtok(NULL, DELIM); cmd = parts.items[0].string;
if (p1) { p1 = parts.nr >= 1 ? parts.items[1].string : NULL;
p2 = parts.nr >= 2 ? parts.items[2].string : NULL;
if (p1)
hash = icase ? strihash(p1) : strhash(p1); hash = icase ? strihash(p1) : strhash(p1);
p2 = strtok(NULL, DELIM);
}
if (!strcmp("add", cmd) && p1 && p2) { if (!strcmp("add", cmd) && p1 && p2) {
@ -260,6 +267,7 @@ int cmd__hashmap(int argc, const char **argv)
} }
} }
string_list_clear(&parts, 0);
strbuf_release(&line); strbuf_release(&line);
hashmap_clear_and_free(&map, struct test_entry, ent); hashmap_clear_and_free(&map, struct test_entry, ent);
return 0; return 0;

View File

@ -1,5 +1,6 @@
#include "test-tool.h" #include "test-tool.h"
#include "json-writer.h" #include "json-writer.h"
#include "string-list.h"
static const char *expect_obj1 = "{\"a\":\"abc\",\"b\":42,\"c\":true}"; static const char *expect_obj1 = "{\"a\":\"abc\",\"b\":42,\"c\":true}";
static const char *expect_obj2 = "{\"a\":-1,\"b\":2147483647,\"c\":0}"; static const char *expect_obj2 = "{\"a\":-1,\"b\":2147483647,\"c\":0}";
@ -394,35 +395,41 @@ static int unit_tests(void)
return 0; return 0;
} }
static void get_s(int line_nr, char **s_in) struct line {
struct string_list *parts;
size_t consumed_nr;
int nr;
};
static void get_s(struct line *line, char **s_in)
{ {
*s_in = strtok(NULL, " "); if (line->consumed_nr > line->parts->nr)
if (!*s_in) die("line[%d]: expected: <s>", line->nr);
die("line[%d]: expected: <s>", line_nr); *s_in = line->parts->items[line->consumed_nr++].string;
} }
static void get_i(int line_nr, intmax_t *s_in) static void get_i(struct line *line, intmax_t *s_in)
{ {
char *s; char *s;
char *endptr; char *endptr;
get_s(line_nr, &s); get_s(line, &s);
*s_in = strtol(s, &endptr, 10); *s_in = strtol(s, &endptr, 10);
if (*endptr || errno == ERANGE) if (*endptr || errno == ERANGE)
die("line[%d]: invalid integer value", line_nr); die("line[%d]: invalid integer value", line->nr);
} }
static void get_d(int line_nr, double *s_in) static void get_d(struct line *line, double *s_in)
{ {
char *s; char *s;
char *endptr; char *endptr;
get_s(line_nr, &s); get_s(line, &s);
*s_in = strtod(s, &endptr); *s_in = strtod(s, &endptr);
if (*endptr || errno == ERANGE) if (*endptr || errno == ERANGE)
die("line[%d]: invalid float value", line_nr); die("line[%d]: invalid float value", line->nr);
} }
static int pretty; static int pretty;
@ -453,6 +460,7 @@ static char *get_trimmed_line(char *buf, int buf_size)
static int scripted(void) static int scripted(void)
{ {
struct string_list parts = STRING_LIST_INIT_NODUP;
struct json_writer jw = JSON_WRITER_INIT; struct json_writer jw = JSON_WRITER_INIT;
char buf[MAX_LINE_LENGTH]; char buf[MAX_LINE_LENGTH];
char *line; char *line;
@ -470,66 +478,77 @@ static int scripted(void)
die("expected first line to be 'object' or 'array'"); die("expected first line to be 'object' or 'array'");
while ((line = get_trimmed_line(buf, MAX_LINE_LENGTH)) != NULL) { while ((line = get_trimmed_line(buf, MAX_LINE_LENGTH)) != NULL) {
struct line state = { 0 };
char *verb; char *verb;
char *key; char *key;
char *s_value; char *s_value;
intmax_t i_value; intmax_t i_value;
double d_value; double d_value;
line_nr++; state.parts = &parts;
state.nr = ++line_nr;
verb = strtok(line, " "); /* break line into command and zero or more tokens */
string_list_setlen(&parts, 0);
string_list_split_in_place(&parts, line, " ", -1);
string_list_remove_empty_items(&parts, 0);
/* ignore empty lines */
if (!parts.nr || !*parts.items[0].string)
continue;
verb = parts.items[state.consumed_nr++].string;
if (!strcmp(verb, "end")) { if (!strcmp(verb, "end")) {
jw_end(&jw); jw_end(&jw);
} }
else if (!strcmp(verb, "object-string")) { else if (!strcmp(verb, "object-string")) {
get_s(line_nr, &key); get_s(&state, &key);
get_s(line_nr, &s_value); get_s(&state, &s_value);
jw_object_string(&jw, key, s_value); jw_object_string(&jw, key, s_value);
} }
else if (!strcmp(verb, "object-int")) { else if (!strcmp(verb, "object-int")) {
get_s(line_nr, &key); get_s(&state, &key);
get_i(line_nr, &i_value); get_i(&state, &i_value);
jw_object_intmax(&jw, key, i_value); jw_object_intmax(&jw, key, i_value);
} }
else if (!strcmp(verb, "object-double")) { else if (!strcmp(verb, "object-double")) {
get_s(line_nr, &key); get_s(&state, &key);
get_i(line_nr, &i_value); get_i(&state, &i_value);
get_d(line_nr, &d_value); get_d(&state, &d_value);
jw_object_double(&jw, key, i_value, d_value); jw_object_double(&jw, key, i_value, d_value);
} }
else if (!strcmp(verb, "object-true")) { else if (!strcmp(verb, "object-true")) {
get_s(line_nr, &key); get_s(&state, &key);
jw_object_true(&jw, key); jw_object_true(&jw, key);
} }
else if (!strcmp(verb, "object-false")) { else if (!strcmp(verb, "object-false")) {
get_s(line_nr, &key); get_s(&state, &key);
jw_object_false(&jw, key); jw_object_false(&jw, key);
} }
else if (!strcmp(verb, "object-null")) { else if (!strcmp(verb, "object-null")) {
get_s(line_nr, &key); get_s(&state, &key);
jw_object_null(&jw, key); jw_object_null(&jw, key);
} }
else if (!strcmp(verb, "object-object")) { else if (!strcmp(verb, "object-object")) {
get_s(line_nr, &key); get_s(&state, &key);
jw_object_inline_begin_object(&jw, key); jw_object_inline_begin_object(&jw, key);
} }
else if (!strcmp(verb, "object-array")) { else if (!strcmp(verb, "object-array")) {
get_s(line_nr, &key); get_s(&state, &key);
jw_object_inline_begin_array(&jw, key); jw_object_inline_begin_array(&jw, key);
} }
else if (!strcmp(verb, "array-string")) { else if (!strcmp(verb, "array-string")) {
get_s(line_nr, &s_value); get_s(&state, &s_value);
jw_array_string(&jw, s_value); jw_array_string(&jw, s_value);
} }
else if (!strcmp(verb, "array-int")) { else if (!strcmp(verb, "array-int")) {
get_i(line_nr, &i_value); get_i(&state, &i_value);
jw_array_intmax(&jw, i_value); jw_array_intmax(&jw, i_value);
} }
else if (!strcmp(verb, "array-double")) { else if (!strcmp(verb, "array-double")) {
get_i(line_nr, &i_value); get_i(&state, &i_value);
get_d(line_nr, &d_value); get_d(&state, &d_value);
jw_array_double(&jw, i_value, d_value); jw_array_double(&jw, i_value, d_value);
} }
else if (!strcmp(verb, "array-true")) else if (!strcmp(verb, "array-true"))
@ -552,6 +571,7 @@ static int scripted(void)
printf("%s\n", jw.json.buf); printf("%s\n", jw.json.buf);
jw_release(&jw); jw_release(&jw);
string_list_clear(&parts, 0);
return 0; return 0;
} }

View File

@ -4,6 +4,7 @@
#include "oidmap.h" #include "oidmap.h"
#include "setup.h" #include "setup.h"
#include "strbuf.h" #include "strbuf.h"
#include "string-list.h"
/* key is an oid and value is a name (could be a refname for example) */ /* key is an oid and value is a name (could be a refname for example) */
struct test_entry { struct test_entry {
@ -25,6 +26,7 @@ struct test_entry {
*/ */
int cmd__oidmap(int argc UNUSED, const char **argv UNUSED) int cmd__oidmap(int argc UNUSED, const char **argv UNUSED)
{ {
struct string_list parts = STRING_LIST_INIT_NODUP;
struct strbuf line = STRBUF_INIT; struct strbuf line = STRBUF_INIT;
struct oidmap map = OIDMAP_INIT; struct oidmap map = OIDMAP_INIT;
@ -35,19 +37,24 @@ int cmd__oidmap(int argc UNUSED, const char **argv UNUSED)
/* process commands from stdin */ /* process commands from stdin */
while (strbuf_getline(&line, stdin) != EOF) { while (strbuf_getline(&line, stdin) != EOF) {
char *cmd, *p1 = NULL, *p2 = NULL; char *cmd, *p1, *p2;
struct test_entry *entry; struct test_entry *entry;
struct object_id oid; struct object_id oid;
/* break line into command and up to two parameters */ /* break line into command and up to two parameters */
cmd = strtok(line.buf, DELIM); string_list_setlen(&parts, 0);
string_list_split_in_place(&parts, line.buf, DELIM, 2);
string_list_remove_empty_items(&parts, 0);
/* ignore empty lines */ /* ignore empty lines */
if (!cmd || *cmd == '#') if (!parts.nr)
continue;
if (!*parts.items[0].string || *parts.items[0].string == '#')
continue; continue;
p1 = strtok(NULL, DELIM); cmd = parts.items[0].string;
if (p1) p1 = parts.nr >= 1 ? parts.items[1].string : NULL;
p2 = strtok(NULL, DELIM); p2 = parts.nr >= 2 ? parts.items[2].string : NULL;
if (!strcmp("put", cmd) && p1 && p2) { if (!strcmp("put", cmd) && p1 && p2) {
@ -108,6 +115,7 @@ int cmd__oidmap(int argc UNUSED, const char **argv UNUSED)
} }
} }
string_list_clear(&parts, 0);
strbuf_release(&line); strbuf_release(&line);
oidmap_free(&map, 1); oidmap_free(&map, 1);
return 0; return 0;

View File

@ -62,7 +62,7 @@ int cmd__string_list(int argc, const char **argv)
struct string_list list = STRING_LIST_INIT_NODUP; struct string_list list = STRING_LIST_INIT_NODUP;
int i; int i;
char *s = xstrdup(argv[2]); char *s = xstrdup(argv[2]);
int delim = *argv[3]; const char *delim = argv[3];
int maxsplit = atoi(argv[4]); int maxsplit = atoi(argv[4]);
i = string_list_split_in_place(&list, s, delim, maxsplit); i = string_list_split_in_place(&list, s, delim, maxsplit);
@ -111,7 +111,7 @@ int cmd__string_list(int argc, const char **argv)
*/ */
if (sb.len && sb.buf[sb.len - 1] == '\n') if (sb.len && sb.buf[sb.len - 1] == '\n')
strbuf_setlen(&sb, sb.len - 1); strbuf_setlen(&sb, sb.len - 1);
string_list_split_in_place(&list, sb.buf, '\n', -1); string_list_split_in_place(&list, sb.buf, "\n", -1);
string_list_sort(&list); string_list_sort(&list);

View File

@ -18,6 +18,14 @@ test_split () {
" "
} }
test_split_in_place() {
cat >expected &&
test_expect_success "split (in place) $1 at $2, max $3" "
test-tool string-list split_in_place '$1' '$2' '$3' >actual &&
test_cmp expected actual
"
}
test_split "foo:bar:baz" ":" "-1" <<EOF test_split "foo:bar:baz" ":" "-1" <<EOF
3 3
[0]: "foo" [0]: "foo"
@ -61,6 +69,49 @@ test_split ":" ":" "-1" <<EOF
[1]: "" [1]: ""
EOF EOF
test_split_in_place "foo:;:bar:;:baz:;:" ":;" "-1" <<EOF
10
[0]: "foo"
[1]: ""
[2]: ""
[3]: "bar"
[4]: ""
[5]: ""
[6]: "baz"
[7]: ""
[8]: ""
[9]: ""
EOF
test_split_in_place "foo:;:bar:;:baz" ":;" "0" <<EOF
1
[0]: "foo:;:bar:;:baz"
EOF
test_split_in_place "foo:;:bar:;:baz" ":;" "1" <<EOF
2
[0]: "foo"
[1]: ";:bar:;:baz"
EOF
test_split_in_place "foo:;:bar:;:baz" ":;" "2" <<EOF
3
[0]: "foo"
[1]: ""
[2]: ":bar:;:baz"
EOF
test_split_in_place "foo:;:bar:;:" ":;" "-1" <<EOF
7
[0]: "foo"
[1]: ""
[2]: ""
[3]: "bar"
[4]: ""
[5]: ""
[6]: ""
EOF
test_expect_success "test filter_string_list" ' test_expect_success "test filter_string_list" '
test "x-" = "x$(test-tool string-list filter - y)" && test "x-" = "x$(test-tool string-list filter - y)" &&
test "x-" = "x$(test-tool string-list filter no y)" && test "x-" = "x$(test-tool string-list filter no y)" &&