Как можно обнаружить неуловимые проблемы с 64-битной переносимостью? - PullRequest
4 голосов
/ 14 июня 2011

Я нашел фрагмент, похожий на этот, в каком-то (C ++) коде, который я готовлю для 64-битного порта.

int n;
size_t pos, npos;

/* ... initialization ... */

while((pos = find(ch, start)) != npos)
{
    /* ... advance start position ... */

    n++; // this will overflow if the loop iterates too many times
}

Хотя я серьезно сомневаюсь, что это может вызвать проблемы даже в приложениях, интенсивно использующих память, стоит взглянуть с теоретической точки зрения, поскольку подобные ошибки могут привести к тому, что вызовет проблемы. (Измените n на short в приведенном выше примере, и даже небольшие файлы могут переполнить счетчик.)

Инструменты статического анализа полезны, но они не могут напрямую обнаруживать подобные ошибки. (Во всяком случае, пока.) Счетчик n вообще не участвует в выражении while, так что это не так просто, как в других циклах (где ошибки при типизации отдают ошибку). Любой инструмент должен был бы определить, что цикл будет выполняться более 2 31 раз, но это означает, что он должен быть в состоянии оценить, сколько раз выражение (pos = find(ch, start)) != npos будет оценивать как истинное - не маленький подвиг! Даже если инструмент может определить, что цикл может выполнить более 2 31 раз (скажем, потому что он распознает, что функция find работает со строкой), как он мог узнать что цикл не будет выполняться более 2 64 раз, переполняя тоже значение size_t?

Кажется очевидным, что для окончательного выявления и исправления такого рода ошибки требуется человеческий глаз, но существуют ли шаблоны, которые выдают такую ​​ошибку, чтобы ее можно было проверить вручную? Какие подобные ошибки существуют, за которыми я должен следить?

РЕДАКТИРОВАТЬ 1: Поскольку типы short, int и long по своей сути проблематичны, такого рода ошибки можно обнаружить, изучив каждый экземпляр этих типов. Однако, учитывая их повсеместное распространение в унаследованном коде C ++, я не уверен, что это практично для большого программного обеспечения. Что еще выдает эту ошибку? Может ли каждый цикл while выдавать какую-то ошибку, подобную этой? (for циклы определенно не застрахованы от этого!) Насколько ужасна такая ошибка, если мы не имеем дело с 16-битными типами, такими как short?

EDIT 2: Вот еще один пример, показывающий, как эта ошибка появляется в цикле for.

int i = 0;
for (iter = c.begin(); iter != c.end(); iter++, i++)
{
    /* ... */
}

По сути, это та же проблема: циклы рассчитывают на некоторую переменную, которая никогда напрямую не взаимодействует с более широким типом. Переменная может все еще переполняться, но ни один компилятор или инструмент не обнаружит ошибку приведения. (Строго говоря, нет.)

РЕДАКТИРОВАТЬ 3: Код, с которым я работаю, очень большой. (10-15 миллионов строк кода только для C ++.) Все это невозможно проверить, поэтому я особенно заинтересован в способах автоматического выявления такого рода проблем (даже если это приводит к высокой вероятности ложных срабатываний).

Ответы [ 3 ]

1 голос
/ 23 июня 2011

Понятно, что вам нужен интеллектуальный анализатор «диапазона», чтобы определить, какой диапазон значений рассчитывается в зависимости от типа, в котором эти значения хранятся.(Ваше основное возражение против того, чтобы этот интеллектуальный анализатор дальности был человеком).Вам могут потребоваться некоторые дополнительные аннотации кода (правильно расположенные вручную определения типов или утверждения, которые предоставляют явные ограничения диапазона), чтобы включить хороший анализ и обрабатывать в противном случае произвольно большие пользовательские данные.

Вам потребуются специальные проверки дляобработать место, где C / C ++ говорит, что арифметика допустима , но глупа (например, предположение, что вы не хотите переполнения [twos дополнение)].Для вашего примера n ++ (эквивалентного n_after = n_before + 1) n_before может быть 2 ^ 31-1 (из-за ваших наблюдений о строках), поэтому n_before + 1 может быть 2 ^ 32, что является переполнением.(Я думаю, что стандартная семантика C / C ++ говорит, что переполнение до -0 без жалоб нормально).

Наш инструментарий реинжиниринга программного обеспечения DMS фактически имеет встроенный механизм анализа диапазона ... но в настоящее время он не подключен к интерфейсу DMS C ++;мы можем торговать только так быстро: - {[Мы использовали его в программах на языке COBOL для различных задач, связанных с диапазонами].

В отсутствие такого анализа диапазона вы, вероятно, могли бы обнаружить существующие циклы с такими зависимыми потоками;значение n явно зависит от количества циклов.Я подозреваю, что это даст вам каждый цикл в программе, у которого есть побочный эффект, который может не сильно помочь.

Другой автор предлагает как-то пересекать все объявления типа int, используя специфичные для приложения типы (например, *linecount_appt *), а затем напечатайте те, которые имеют значение для работы вашего приложения.Чтобы сделать это, я бы подумал, что вам придется классифицировать каждое int-подобное объявление по категориям (например, «все эти объявления * linecount_appt *»).Выполнение этого вручную 10M SLOC кажется довольно трудным и очень подверженным ошибкам.Поиск всех объявлений, которые получают (путем присвоения) значения из «одинаковых» источников значений, может быть способом получить подсказки о том, где находятся такие типы приложений.Вы хотели бы иметь возможность механически находить такие группы объявлений, а затем иметь какой-либо инструмент, автоматически заменяющий фактические объявления назначенным типом приложения (например, * linecount_appt *).Это, вероятно, несколько проще, чем точный анализ диапазона.

1 голос
/ 14 июня 2011

Кодовые отзывы.Поищите несколько умных людей, которые смотрят на код.

Использование short, int или long является предупреждающим знаком, поскольку диапазон этих типов не определен в стандарте.Большая часть использования должна быть изменена на новые int_fastN_t типы в <stdint.h>, использование, связанное с сериализацией, на intN_t.Ну, на самом деле эти <stdint.h> типы должны использоваться для typedef новых специфичных для приложения типов.

Этот пример действительно должен быть:

typedef int_fast32_t linecount_appt;
linecount_appt n;

Это выражает предположение о том, что linecountумещается в 32 бита, а также позволяет легко исправить код в случае изменения проектных требований.

0 голосов
/ 15 июня 2011

Существуют инструменты, которые помогают найти такие проблемы.Я не буду давать здесь никаких ссылок, потому что те, о которых я знаю, являются коммерческими, но их должно быть довольно легко найти.

...