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

Многие наши читатели слышали, что PVS-Studio поддерживает классификацию своих предупреждений по стандарту MISRA. На данный момент PVS-Studio покрывает более 100 правил MISRA C: 2012 и MISRA C++: 2008.

Цель этой статьи — убить трех зайцев одним выстрелом:

  • Расскажите, что такое MISRA для тех, кто еще не знаком с этим стандартом;
  • Напомните миру разработчиков встраиваемых систем, что мы можем сделать;
  • Помогите новым сотрудникам нашей компании, которые в будущем также будут разрабатывать наш анализатор MISRA, полностью ознакомиться с ним.

Я надеюсь, что смогу сделать его интересным. Итак, приступим!

История МИСРА

История MISRA началась очень давно. Тогда, в начале 1990-х годов, государственная программа Великобритании «Безопасные ИТ» обеспечивала финансирование различных проектов, так или иначе связанных с безопасностью электронных систем. Сам проект MISRA (Ассоциация надежности программного обеспечения автомобильной промышленности) был основан для создания руководства по разработке программного обеспечения микроконтроллеров для наземной техники — в основном для автомобилей.

Получив финансирование от государства, команда MISRA взялась за работу и к ноябрю 1994 года выпустила свое первое руководство: Руководство по разработке программного обеспечения для транспортных средств. Данное руководство пока не привязано к конкретному языку, но должен признать, что работа проделана впечатляющая и коснулась, наверное, всех мыслимых аспектов разработки встраиваемого ПО. Кстати, недавно разработчики этого руководства отметили 25-летие столь важной для них даты.

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

  • Бентли Мотор Карс
  • Форд Мотор Компани
  • Ягуар Ленд Ровер
  • Дизельные системы Делфи
  • ХОРИБА МИРА
  • Протеан Электрик
  • Вистеон Инженерные услуги
  • Университет Лидса
  • Рикардо Великобритания
  • ЗФ ТРВ

Очень сильные игроки рынка, не так ли? Неудивительно, что их первый стандарт, связанный с языком, MISRA C, стал обычным явлением среди разработчиков критически важных встраиваемых систем. Чуть позже появилась MISRA C++. Постепенно версии стандартов обновлялись и уточнялись, чтобы охватить новые возможности языков. На момент написания этой статьи текущими версиями являются MISRA C: 2008 и MISRA C++: 2012.

Основная концепция и примеры правил

Наиболее отличительными чертами MISRA являются невероятное внимание к деталям и крайняя дотошность в обеспечении безопасности и защиты. Мало того, что авторы собрали все недостатки C и C++ в одном месте (как, например, авторы CERT), так еще и тщательно проработали международные стандарты этих языков и выписали все способы сделать ошибку. После этого они добавили правила читаемости кода, чтобы застраховать чистый код от новой ошибки.

Чтобы понять шкалу серьезности, давайте рассмотрим несколько правил, взятых из стандарта.

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

  • Не используйте значение неинициализированной переменной
  • Не используйте указатель на FILE после закрытия потока
  • Все непустые функции должны возвращать значение
  • Счетчики циклов не должны иметь тип с плавающей запятой.
  • и другие.

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

  • Не используйте goto и longjmp
  • Каждый переключатель должен заканчиваться меткой по умолчанию
  • Не пишите недостижимый код
  • Не используйте вариативные функции
  • Не используйте адресную арифметику (кроме [] и ++)

Такие правила тоже неплохи, и в сочетании с предыдущими они уже дают ощутимый прирост безопасности, но достаточно ли этого для высоконадежных встраиваемых систем? Они используются не только в автомобилестроении, но и в авиационной, аэрокосмической, военной и медицинской промышленности.

Мы не хотим, чтобы какой-нибудь рентген-аппарат облучал больных дозой 20 000 рад из-за ошибки в программе, поэтому обычных бытовых правил недостаточно. Когда на кону человеческие жизни и большие деньги, без педантизма не обойтись. Вот где вступают в игру остальные правила MISRA:

  • Суффикс «L» в литерале всегда должен быть заглавным (строчную букву «l» можно спутать с 1)
  • Не используйте оператор «запятая» (это увеличивает вероятность ошибки)
  • Не используйте рекурсию (небольшой стек микроконтроллера может легко переполниться)
  • Тела операторов if, else, for, while, do, переключатель должен быть заключен в фигурные скобки (потенциально можно ошибиться, когда код неправильно выровнен)
  • Не использовать динамическую память (потому что есть шанс не освободить ее из кучи, особенно в микроконтроллерах)
  • … и многие, многие из этих правил.

Часто бывает так, что у людей, которые впервые сталкиваются с MISRA, создается впечатление, что цель стандарта — «запретить то и запретить то». На самом деле это так, но только до некоторой степени.

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

В MISRA C правила делятся на три основные категории: обязательные, обязательные и рекомендательные. Обязательными являются правила, которые нельзя нарушать ни под каким предлогом. Например, в этом разделе есть правило: «не использовать значение неинициированной переменной». Обязательные правила менее строгие: они допускают возможность отклонения, но только в том случае, если эти отклонения тщательно задокументированы и подтверждены в письменной форме. Остальные правила относятся к категории рекомендательных и не являются обязательными.

MISRA C++ имеет некоторые отличия: отсутствует категория Mandatory, а большинство правил относятся к категории Required. Поэтому, по сути, вы имеете право нарушать любое правило — только не забывайте комментировать все отклонения. Также есть категория Document — это обязательные правила (отклонения не допускаются), связанные с общими практиками, такими как «Каждое использование ассемблера должно быть задокументировано» или «Включенная библиотека должна соответствовать MISRA C++».

Другие вопросы

На самом деле MISRA — это не просто набор правил. По сути, это руководство по написанию безопасного кода для микроконтроллеров, так что в нем полно вкусностей. Давайте подробно рассмотрим их.

Во-первых, стандарт содержит достаточно подробное описание предыстории: зачем создавался стандарт, почему был выбран C или C++, плюсы и минусы этих языков.

Все мы прекрасно знаем достоинства этих языков. Как и мы знаем об их недостатках :) Высокий уровень сложности, неполная стандартная спецификация и синтаксис, позволяющий легко ошибиться и потом искать ее веками — обо всем этом нельзя не упомянуть. Например, вы можете случайно написать это:

for (int i = 0; i < n; ++i);
{
  do_something();
}

Ведь есть шанс, что человек не заметит лишнюю точку с запятой, верно? Другой вариант — написать код следующим образом:

void SpendTime(bool doWantToKillPeople)
{
  if (doWantToKillPeople = true)
  {
    StartNuclearWar();
  }
  else
  {
    PlayComputerGames();
  }
}

Хорошо, что и первый, и второй случаи легко отлавливаются правилами MISRA (1 — MISRA C: 13.4/MISRA C++: 6.2.1.; 2 — MISRA C: 13.4/MISRA C++: 6.2.1. ).

Стандарт содержит как описание проблемных вопросов, так и советы о том, что нужно знать, прежде чем браться за ту или иную задачу: как настроить процесс разработки по MISRA; как использовать статические анализаторы для проверки кода на соответствие; какие документы надо вести, как их заполнять и так далее.

В приложениях в конце также есть: краткий список и сводка правил, небольшой список уязвимостей C/C++, пример документации об отклонении от правил и несколько контрольных списков, помогающих разобраться во всей этой бюрократии.

Как видите, MISRA — это не просто набор правил, а практически целая инфраструктура для написания безопасного кода для встраиваемых систем.

Использование в ваших проектах

Представьте себе ситуацию: вы собираетесь написать программу для очень нужной и ответственной встраиваемой системы. Или у вас уже есть программа, но ее нужно «портировать» на MISRA. Так как же проверить свой код на соответствие стандарту? Вам действительно нужно делать это вручную?

Ручная проверка кода — непростая и даже потенциально невыполнимая задача. Каждый рецензент должен не только внимательно просматривать каждую строчку кода, но и знать стандарт почти наизусть. Сумасшедший!

Поэтому сами разработчики MISRA советуют использовать статический анализ для проверки вашего кода. Ведь по сути статический анализ — это автоматизированный процесс код-ревью. Вы просто запускаете анализатор в своей программе и через несколько минут получаете отчет о возможных нарушениях стандарта. Это то, что вам нужно, не так ли? Все, что вам нужно сделать, это просмотреть журнал и исправить предупреждения.

Следующий вопрос — с какого момента мы должны начать использовать MISRA? Ответ прост: чем раньше, тем лучше. В идеале — до того, как вы вообще начнете писать код, потому что MISRA предполагает, что вы следуете стандарту на протяжении всего жизненного цикла вашего кода.

Ну не всегда получается писать по MISRA с самого начала. Например, часто бывает так, что проект уже реализован частично или полностью, но позже заказчик захотел, чтобы проект соответствовал стандарту. В этом случае вам придется заняться тщательным рефакторингом существующего кода.

Вот и всплывает подводный камень. Я бы даже сказал, что появляется подводный валун. Что будет, если взять статический анализатор и проверить «обычный» проект на соответствие стандарту MISRA? Спойлер: вам может быть страшно.

Нажмите на картинку, чтобы увеличить.

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

Итак, что вы можете с этим поделать? Вам действительно нужно отложить все задачи и полностью посвятить себя исправлению старых предупреждений?

Как разработчики статического анализатора, мы знаем, что после проверки появляется очень много предупреждений. Поэтому мы разработали решение, которое поможет сразу начать пользоваться анализатором, не останавливая работу. Это решение называется «подавить базу».

Базы подавления представляют собой механизм PVS-Studio, позволяющий массово подавлять сообщения анализатора. Если вы впервые проверяете проект и получаете несколько тысяч предупреждений — вам достаточно добавить их в базу подавления, и при следующем запуске вы получите ноль предупреждений.

Таким образом, вы можете продолжать писать и изменять код как обычно, и при этом вы будете получать сообщения только об ошибках, которые только что были сделаны в проекте. Так вы получите максимальную пользу от анализатора прямо здесь и сейчас, не отвлекаясь на разгребание старых багов. Несколько кликов — и анализатор внедрен в вашу разработку! Подробную инструкцию о том, как это сделать, вы можете прочитать здесь.

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

Хорошо, теперь анализатор успешно принят, и мы готовы продолжить. Что делать дальше? Ответ очевиден — работайте с кодом! Но что нужно, чтобы заявить о соответствии стандарту? Как вы докажете, что ваш проект соответствует требованиям MISRA?

На самом деле специального «сертификата» того, что ваш код соответствует MISRA, нет. Согласно стандарту, отслеживание соответствия должно осуществляться двумя сторонами: заказчиком программного обеспечения и поставщиком программного обеспечения. Вендор разрабатывает ПО, соответствующее стандарту, и заполняет необходимые документы. Заказчик, в свою очередь, должен убедиться, что данные из этих документов соответствуют действительности.

Если вы разрабатываете ПО для себя, то ответственность за соответствие стандарту ляжет только на ваши плечи :)

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

Вам понадобятся следующие вещи, чтобы подать заявку на стандартное соответствие:

  • Сам проект, код которого соответствует обязательным и обязательным правилам
  • Руководство по реализации плана
  • Документация по всем предупреждениям компилятора и статического анализатора
  • Документация по всем отклонениям от Обязательных правил
  • Сводка по соблюдению рекомендаций

Во-первых, это план принудительного исполнения руководства. Это ваша самая важная таблица, и она содержит ссылки на все остальные документы. В его первой колонке список правил MISRA, в остальных отмечено, были ли отклонения от этих правил. Эта таблица выглядит примерно так:

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

Если одно из правил не может быть проверено статическим анализатором, вам необходимо выполнить проверку кода вручную. Эта процедура также должна быть задокументирована, после чего ссылка на эту документацию должна быть добавлена ​​в план соответствия.

Если компилятор или статический анализатор окажутся правы, или если в процессе проверки кода будут выявлены действительные нарушения правил, вы должны либо исправить их, либо задокументировать. Опять же, прикрепив ссылку на документацию в таблице.

Таким образом, план соответствия — это документ, который будет документировать любое отклонение, обнаруженное в вашем коде.

Коротко коснемся непосредственно документирования отклонений от правил. Как я уже упоминал, такая документация необходима только для Обязательных правил, потому что Обязательные правила нельзя нарушать, а Консультативные правила можно нарушать без какой-либо документации.

Если вы решите отклониться от правила, документация должна включать:

  • Номер нарушенного правила
  • Точное место отклонения
  • Действительность отклонения
  • Доказательство того, что отклонение не ставит под угрозу безопасность
  • Возможные последствия для пользователя

Как видите, такой подход к документации заставляет всерьез задуматься, а стоит ли оно того. Это сделано специально, чтобы не возникало желания нарушать Required rules :)

Теперь о подведении итогов соблюдения правил. Эту бумагу, пожалуй, будет проще всего заполнить:

Центральная колонка заполняется до того, как вы начнете работать с кодом, а правая — после того, как ваш проект будет готов.

Возникает резонный вопрос: зачем указывать категории правил, если они уже указаны в самом стандарте? Дело в том, что стандарт позволяет «продвигать» правило в более строгую категорию. Например, клиент может попросить вас классифицировать рекомендательное правило. Такое «продвижение» нужно делать перед работой с кодом, а сводка о соблюдении правил позволяет явно это отметить.

Что касается последнего столбца, то тут все достаточно просто: нужно лишь отметить, используется ли правило, и если да, то есть ли отклонения от него.

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

Итак, вы написали код, тщательно следуя правилам MISRA. Вы составили план соответствия и задокументировали все, что можно было задокументировать, и вы заполнили свое резюме соответствия. Если это действительно так, то у вас получился очень чистый, очень читаемый и очень надежный код, который вы теперь ненавидите :)

Где теперь будет жить ваша программа? В аппарате МРТ? В обычном датчике скорости или в системе управления какого-нибудь космического спутника? Да, вы прошли серьезный бюрократический путь, но это не имеет большого значения. Когда дело доходит до реальной человеческой жизни, вы всегда должны быть щепетильны.

Если вы справились и сумели дойти до победного конца, то искренне вас поздравляю: вы пишете качественный безопасный код. Спасибо!

Будущее стандартов

Напоследок хотелось бы остановиться на будущем стандартов.

Сейчас MISRA живет и развивается. Например, «MISRA C:2012 Third Edition (First Revision)» является исправленным и дополненным новыми правилами издания, анонсированными в начале 2019 года. В то же время, предстоящий выпуск «MISRA C: 2012 Amendment 2 — C11 Core», который является пересмотренным стандартом 2012 года. Этот документ будет содержать правила, которые впервые охватывают версии языка C 2011 и 2018 годов.

MISRA C++ также продолжает развиваться. Как вы знаете, последний стандарт MISRA C++ датирован 2008 годом, поэтому самой старой версией языка, который он охватывает, является C++03. По этой причине существует другой стандарт, аналогичный MISRA, и он называется AUTOSAR C++. Первоначально он задумывался как продолжение MISRA C++ и предназначался для более поздних версий языка. В отличие от своего вдохновителя, AUTOSAR C++ обновляется два раза в год и в настоящее время поддерживает C++14. Новые обновления C++17, а затем и C++20 еще впереди.

Почему я начал говорить о каком-то другом стандарте? Дело в том, что чуть меньше года назад обе организации объявили, что объединят свои стандарты в один. MISRA C++ и AUTOSAR C++ станут единым стандартом, и отныне они будут развиваться вместе. Думаю, это отличная новость для разработчиков, которые пишут для микроконтроллеров на C++, и не менее хорошая новость для разработчиков статического анализатора. Есть еще много дел! :)

Вывод

Сегодня вы, надеюсь, многое узнали о MISRA: прочитали историю ее возникновения, изучили примеры правил и концепции стандарта, рассмотрели все, что вам понадобится для использования MISRA в своих проектах, и даже заглянули в будущее. Надеюсь, теперь вы лучше понимаете, что такое МИСРА и как ее готовить!

По старой традиции оставлю здесь ссылку на наш статический анализатор PVS-Studio. Он способен находить не только отклонения от стандарта MISRA, но и огромный спектр ошибок и уязвимостей. Если вам интересно попробовать PVS-Studio самостоятельно, скачайте демо-версию и проверьте свой проект.

На этом моя статья подходит к концу. Поздравляю всех читателей с наступающими новогодними праздниками и Рождеством!