Можно написать юнит-тесты типа таких:
void Test1()
{
char *a = "a";
char *b = "";
char *q;
_lfortran_strcat(&a, &b, &q);
int ok = strcmp(q, "a") == 0;
printf("%s+%s=%s %s\n", a, b, q, ok ? "ok" : "err");
free(q);
}
void Test2()
{
char *a = "12";
char *b = "345";
char *q;
_lfortran_strcat(&a, &b, &q);
int ok = strcmp(q, "12345") == 0;
printf("%s+%s=%s %s\n", a, b, q, ok ? "ok" : "err");
free(q);
}
И ничего не заметить. Тесты проходят успешно:
a+=a ok
12+345=12345 ok
Тесты на коротких строках не выявят проблему. В голову может и не прийти мысль попробовать работать с длинными строками. Зачем? На первый взгляд такие тесты ничего не дают. Скорее всего, будут созданы тесты на краевые случаи (пустые строки), но они нерелевантные для поиска обсуждаемого бага.
При этом даже с длинными строками, вероятность заскочить в соседний блок всего 1 к N (где N, например 16). Можно объединить две огромные строки из 111111 символов и всё будет хорошо, ведь конец результирующей строки (222222 символов) не лежит на границе 16-байтного блока.
Ещё юнит-тесты
Только не подумайте, что я критикую юнит-тесты. Это замечательная штука! Однако бывают ошибки, которым легко от этих юнит-тестов спрятаться. И перед нами как раз такой случай.
Дело в том, что легко не заметить, даже когда будет пересекаться тот невидимый рубеж выделенного блока памяти. Следующий тест создаёт не такие уж короткие строки длинной 37 символов. Как думаете, такой тест приведёт к падению программы или ещё чему-то?
void Test3()
{
char *a = "123";
for (unsigned i = 1; i != 35; ++i)
{
char *b = (char *)malloc(i + 1);
memset(b, 'a', i);
b[i] = '\0';
char *q;
_lfortran_strcat(&a, &b, &q);
int ok = strlen(q) == 3 + i;
printf("%u %s %s\n", i, q, ok ? "ok" : "err");
free(b);
free(q);
}
}
Приведёт или нет — неизвестно, ведь тут неопределённое поведение. Но на практике я собираю его gcc с ключом
-O2 и не наблюдаю какого-то проявления ошибки, хотя, по идее, блоки памяти уже испорчены. Но по тесту всё ещё кажется, что всё нормально:1 123a ok
2 123aa ok
3 123aaa ok
4 123aaaa ok
5 123aaaaa ok
6 123aaaaaa ok
7 123aaaaaaa ok
8 123aaaaaaaa ok
9 123aaaaaaaaa ok
10 123aaaaaaaaaa ok
11 123aaaaaaaaaaa ok
12 123aaaaaaaaaaaa ok
13 123aaaaaaaaaaaaa ok
14 123aaaaaaaaaaaaaa ok
15 123aaaaaaaaaaaaaaa ok
16 123aaaaaaaaaaaaaaaa ok
17 123aaaaaaaaaaaaaaaaa ok
18 123aaaaaaaaaaaaaaaaaa ok
19 123aaaaaaaaaaaaaaaaaaa ok
20 123aaaaaaaaaaaaaaaaaaaa ok
21 123aaaaaaaaaaaaaaaaaaaaa ok
22 123aaaaaaaaaaaaaaaaaaaaaa ok
23 123aaaaaaaaaaaaaaaaaaaaaaa ok
24 123aaaaaaaaaaaaaaaaaaaaaaaa ok
25 123aaaaaaaaaaaaaaaaaaaaaaaaa ok
26 123aaaaaaaaaaaaaaaaaaaaaaaaaa ok
27 123aaaaaaaaaaaaaaaaaaaaaaaaaaa ok
28 123aaaaaaaaaaaaaaaaaaaaaaaaaaaa ok
29 123aaaaaaaaaaaaaaaaaaaaaaaaaaaaa ok
30 123aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa ok
31 123aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa ok
32 123aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa ok
33 123aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa ok
34 123aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa ok
Падение случится при константе 38 в цикле
for (unsigned i = 1; i != 38; ++i):free(): invalid pointer
Program terminated with signal: SIGSEGV
Не надо искать какой-то особый смысл в числе 38, просто так получилось. Интересен момент, как долго ошибка пряталась от юнит-тестов.