Pull to refresh
294.96
PVS-Studio
Static Code Analysis for C, C++, C# and Java

Tizen: подводим итоги

Reading time 15 min
Views 11K

PVS-Studio, Tizen, ИтогиНаша команда написала три заметки, связанные с анализом кода операционной системы Tizen. Операционная система содержит много кода и поэтому является благодатной почвой для написания различных статей. Думаю, что к Tizen мы ещё вернёмся в будущем, но сейчас нас ждут другие интересные проекты. Поэтому я подведу некоторые итоги проделанной работы и отвечу на ряд вопросов, возникших после опубликованных ранее статей.



Проделанная работа


Итак, наша команда написала 3 статьи:

  1. Андрей Карпов. 27000 ошибок в операционной системе Tizen. Фундаментальная статья, демонстрирующая всю важность использования статического анализа в больших проектах. Статический анализатор PVS-Studio отлично показал, как много различных паттернов ошибок он может обнаруживать в C/C++ коде.

  2. Андрей Карпов. Поговорим о микрооптимизациях на примере кода Tizen. На примере Tizen продемонстрировано, какие микрооптимизации кода предлагает анализатор PVS-Studio. Старый код править не стоит, но разрабатывать новый код с учетом этих рекомендаций следует однозначно.

  3. Сергей Хренов. Продолжаем изучать Tizen: C# компоненты оказались высокого качества. А вот здесь анализатор PVS-Studio не смог проявить себя. Неудача. Зато эта статья показывает, что мы честны в своих исследованиях. Удалось найти много интересных ошибок в C и C++ коде — мы написали про это. Не удалось найти ошибок в C# коде — про это мы тоже написали.

По итогам публикаций возникло два больших обсуждения: первое на Reddit, второе на Hacker News. Также появилось несколько новостных постов. Основные:


Всё это и натолкнуло меня на мысль обсудить несколько дополнительных тем и ответить на некоторые вопросы, которые поднимались в дискуссиях.

Надо всё переписать на Rust


В последнее время активизировались энтузиасты, агитирующие везде использовать Rust. Особенно бурный всплеск дискуссии на эту тему последовал после статьи "Rewrite the Linux kernel in Rust?".

Отметились эти энтузиасты и в комментариях к нашим статьям. Их предложение: чтобы не было таких ошибок, надо переписать всё на Rust.

Единорог facepalm

На самом деле, мне всё равно, будет что-то переписываться или нет. В мире написано так много кода на C и C++, что еще лет 50 анализатору PVS-Studio будет что проверять. Уж если до сих пор используются статические анализаторы для Cobol, то для C и C++ они также будут требоваться ещё многие десятилетия.

И тем не менее, я не могу обойти эту тему стороной. Вы серьезно предлагаете переписывать такие проекты на Rust? Вот взять и переписать 72 MLOC кода на Rust? Да это же сумасшествие!

Это потребует невероятного количества времени и сил. Более того, потратив много лет на разработку, вы в результате получите ровно то же, что уже и так существовало! Намного лучше было бы вложить эти человеко-годы в создание чего-то нового в уже существующем проекте.

Кто-то возразит, что после такого переписывания код станет лучше и надёжнее. Вот вообще нет такой гарантии. В крупных проектах значение языка не так велико. Вдобавок, многие библиотеки на C или C++ давно отлажены, а при переписывании придётся изобретать велосипеды, которые долгие годы будут радовать пользователей разнообразнейшими ошибками.

Я считаю, тот, кто предлагает переписать 72 MLOC кода, просто некомпетентен. Это можно простить новичку, но если это говорит человек с опытом работы, то он, видимо, тролль.

3.3% — это очень маленькая выборка и ваша оценка количества ошибок неверна


Да, такой подход может дать неточный результат. Но переживать об этом есть смысл, если мы проверили 1000, 3000 или 10000 строк кода. Переживать стоит и в том случае, если проверялся только один проект, написанный одной командой. В другом проекте плотность ошибок может сильно отличаться.

Но я напомню, что мною (при помощи анализатора PVS-Studio) было проверено 2 400 000 строк кода на C/C++. Это много! Это размер некоторых проектов.

При этом проверялся код разных проектов. Я использовал метод выборки «пальцем в небо». Очень хороший, честный способ. Вот список изученных мною проектов:
alsa-lib-1.0.28, aspell-0.60.6.1, augeas-1.3.0, bind-9.11.0, efl-1.16.0, enlightenment-0.20.0, ise-engine-anthy-1.0.9, bluetooth-frwk-0.2.157, capi-appfw-application-0.5.5, capi-base-utils-3.0.0, capi-content-media-content-0.3.10, capi-maps-service-0.6.12, capi-media-audio-io-0.3.70, capi-media-codec-0.5.3, capi-media-image-util-0.1.15, capi-media-player-0.3.58, capi-media-screen-mirroring-0.1.78, capi-media-streamrecorder-0.0.10, capi-media-vision-0.3.24, capi-network-bluetooth-0.3.4, capi-network-http-0.0.23, cynara-0.14.10, e-mod-tizen-devicemgr-0.1.69, ise-engine-default-1.0.7, ise-engine-sunpinyin-1.0.10, ise-engine-tables-1.0.10, isf-3.0.186, org.tizen.app-selector-0.1.61, org.tizen.apps-0.3.1, org.tizen.bluetooth-0.1.2, org.tizen.browser-3.2.0, org.tizen.browser-profile_common-1.6.4, org.tizen.classic-watch-0.0.1, org.tizen.d2d-conv-setting-profile_mobile-1.0, org.tizen.d2d-conv-setting-profile_wearable-1.0, org.tizen.download-manager-0.3.21, org.tizen.download-manager-0.3.22, org.tizen.dpm-toolkit-0.1, org.tizen.elm-demo-tizen-common-0.1, org.tizen.indicator-0.2.53, org.tizen.inputdelegator-0.1.170518, org.tizen.menu-screen-1.2.5, org.tizen.myplace-1.0.1, org.tizen.privacy-setting-profile_mobile-1.0.0, org.tizen.privacy-setting-profile_wearable-1.0.0, org.tizen.quickpanel-0.8.0, org.tizen.screen-reader-0.0.8, org.tizen.service-plugin-sample-0.1.6, org.tizen.setting-1.0.1, org.tizen.settings-0.2, org.tizen.settings-adid-0.0.1, org.tizen.telephony-syspopup-0.1.6, org.tizen.voice-control-panel-0.1.1, org.tizen.voice-setting-0.0.1, org.tizen.volume-0.1.149, org.tizen.w-home-0.1.0, org.tizen.w-wifi-1.0.229, org.tizen.watch-setting-0.0.1, security-manager-1.2.17.
Вряд ли мне «повезло» взять столько проектов, относящихся к одной команде. Очевидно, что здесь трудились разные команды специалистов.

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

Всё не так плохо, как вы утверждаете


После публикации моей статьи «27000 errors in the Tizen operating system» в интернете появилось несколько неграмотных новостей, где люди написали про большое количество уязвимостей, найденных в Tizen. Например, можно было встретить вот такие неграмотные заголовки «В коде операционной системы Tizen зафиксировано 27000 уязвимостей». Это, естественно, не соответствует действительности. Давайте я поясню почему.

Сразу скажу, что я писал не про уязвимости, а про ошибки. И ещё, в статье я нигде не говорил, что Tizen является некачественным кодом. Да, я говорю, что анализатор PVS-Studio выявляет много ошибок, однако в любом большом проекте ошибок будет много. Поэтому общее количество ошибок ещё не говорит о качестве кода.

Давайте теперь немного подробнее поговорим про уязвимости. Среди всех ошибок, которые встречаются в программах, выделяют security weaknesses. Их особенность в том, что возможно стечение обстоятельств, когда эта ошибка может использоваться злоумышленником. Эти типы ошибок описаны в CWE. CWE is a community-developed list of common software security weaknesses.

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

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

27000 ошибок говорит о хорошем или плохом качестве кода? Невозможно сказать. Однако это не является страшным числом, как может показаться на первый взгляд. Следует учитывать, что общий объём кода составляет 72 500 000 строк на языке C, C++ (без учёта комментариев). Получается, что анализатор PVS-Studio выявляет приблизительно 0,37 ошибки на 1000 строк кода. Или другими словами около 1 ошибки на 3000 строк кода.

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

Итак, PVS-Studio выявляет приблизительно 0,37 ошибок на 1000 строк кода. Это много или мало? Скорее, средне. Бывает лучше и хуже. Вот некоторые примеры:


Подведём итоги. На самом деле никакой сенсации нет. Шокирует число в 27000 ошибок, но такая внушительная цифра связана с большим размером проекта Tizen. Если взять другой большой проект, там тоже будет много ошибок.

Целью моей статьи было показать, что инструмент PVS-Studio может быть полезен проекту Tizen. И кажется, мне это удалось. Однако я вовсе не ожидал той бурной реакции и обсуждения, которые возникли вокруг этой статьи. Мы регулярно пишем подобные заметки. С ними можно ознакомиться здесь.

В статье не указан процент ложных срабатываний


Начну издалека. К сожалению, многие читатели знакомятся со статьями очень невнимательно. В результате они довольно часто неправильно воспринимают числа, которые в них указываются. Я уже хорошо знаком с этим эффектом и стараюсь учитывать это в статьях. Например, в статье про «27000 ошибок» я специально дважды написал, что я нашел 900 ошибок, изучив 3.3% кода. При этом подчеркивал, что речь идёт именно об ошибках, а не о количестве выданных анализатором предупреждений.

И хотя я подстраховался, всё равно появился вот этот комментарий:

900 варнингов в аналоге Lint'а не означает 900 багов. Я бы даже сказал, что эти показатели вообще не связаны никак. Наверняка, там обнаружены ошибки в форматировании кода, областях видимости переменных и т.д. В топку таких аналитегов.

Человек не читал статью, но увидел число 900 и спешит поделиться своим мнением с другими.

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

Всё дело в том, что анализатор требует настройки. Причем большинство ложных срабатываний относится к небольшому количеству макросов. Я уже не раз демонстрировал в некоторых своих статьях, как подавление предупреждений для нескольких макросов резко уменьшает количество ложных срабатываний.

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

Возникает логичный вопрос: Тогда почему бы вам не настроить статический анализатор и не показать сразу хороший результат?

Отвечаю. Это займёт время, а меня ещё ждут такие интересные проекты, как iOS или Android. Однако это не главная причина, почему я не хочу этим заниматься. Дело в том, что непонятно где остановиться. Я знаю, что, приложив усилия, мы сможем свести количество ложных срабатываний до нуля, ну или почти до нуля. Например, мы сводили до нуля количество ложных срабатываний, когда работали над проектом Unreal Engine (см. статьи 1, 2).

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

Так как же программисту понять, хорошо работает наш анализатор или плохо? Очень просто! Нужно его скачать и попробовать проверить рабочий проект. Cразу станет понятно, нравится он или нет. И будет сразу видно, как много ложных срабатываний и какого они типа. После чего, возможно, ваша компания с удовольствием пополнит список наших клиентов.

Только прошу не совершать ошибку и не пробовать анализатор на маленьких проектах или вообще тестовых примерах. Причины:


Update. Вношу это примечание уже после написания статьи. Хорошо, читатели победили. Я сдаюсь и привожу число. Я провёл исследование EFL Core Libraries и вычислил, что статический анализатор PVS-Studio будет выдавать около 10-15% ложных срабатываний. Вот статья про это: Характеристики анализатора PVS-Studio на примере EFL Core Libraries.

Достаточно -Wall -Wextra -Werror


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

Дополнительные инструменты нужны. Статические анализаторы — это специализированные инструменты, которые всегда опережают компиляторы своими диагностическими возможностями. За это они и получают деньги от своих клиентов.

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


При этом не стоит забывать, что статический анализ — это не только предупреждения, это ещё и инфраструктура. Вот некоторые возможности PVS-Studio:

  • Удобная и простая интеграция с Visual Studio 2010-2017.
  • Интеграция с SonarQube.
  • Утилита BlameNotifier. Инструмент позволяет рассылать письма разработчикам об ошибках, которые PVS-Studio нашел во время ночного прогона.
  • Mass Suppression — позволяет подавить все старые сообщения, чтобы анализатор выдавал 0 срабатываний. К подавленным сообщениям всегда можно вернуться позже. Возможность безболезненно внедрить PVS-Studio в существующий процесс разработки и сфокусироваться на ошибках только в новом коде.
  • Сохранение и загрузка результатов анализа: можно ночью проверить код, сохранить результаты, а утром загрузить их и смотреть.
  • Поддержка IncrediBuild.
  • Mark as False Alarm — разметка в коде, чтобы не ругаться конкретной диагностикой в конкретном фрагменте файла.
  • Интерактивная фильтрация результатов анализа (лога) в окне PVS-Studio: по коду диагностики, по имени файла, по включению слова в текст диагностики.
  • Статистика ошибок в Excel — можно посмотреть темпы правки ошибок, количество ошибок во времени и т.п.
  • Автоматическая проверка на наличие новых версий PVS-Studio (как при работе в IDE, так и при ночных сборках).
  • Использование относительных путей в файлах отчета для возможности переноса отчета на другую машину.
  • CLMonitoring — проверка проектов, у которых нет файлов Visual Studio (.sln/.vcxproj); если вдруг вам не хватит функциональности CLMonitoring, то вы можете интегрировать PVS-Studio в любую Makefile-based систему сборки вручную.
  • pvs-studio-analyzer — утилита аналогичная CLMonitoring, но работающая под Linux.
  • Возможность исключить из анализа файлы по имени, папке или маске.

Подробнее про всё это можно узнать в документации.

Нет цены


Да, на сайте у нас нет цены. Это стандартная практика компаний, продающих решения в сфере статического анализа кода.

Мы позиционируем PVS-Studio как B2B решение. При продаже компаниям необходимо обсудить множество моментов, которые влияют на цену лицензии. Вывешивать какую-то конкретную цену на сайт не имеет смысла и продуктивнее сразу приступить к обсуждению.

Почему мы не работаем с индивидуальными разработчиками? Мы пробовали, но у нас не получилось.

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


Представителей компаний я приглашаю пообщаться по почте.

Не все участки, которые вы упоминаете в статье, являются настоящими ошибками


Да, возможно что-то окажется не ошибкой при более тщательном изучении кода. С другой стороны, при тщательном анализе может выясниться, что я, наоборот, пропускал некоторые ошибки. Например, я поленился изучать предупреждения V730 — Not all members of a class are initialized inside the constructor. Очень трудоёмко пытаться понять в чужом коде, является ли ошибкой, что какой-то член класса остался неинициализированным. Однако, если этим заняться, наверняка найдутся настоящие ошибки.

Давайте разберем один из таких случаев. Код относится к проекту org.tizen.browser-profile_common-1.6.4.

Для начала рассмотрим определение класса BookmarkItem.

class BookmarkItem
{
public:
    BookmarkItem();
    BookmarkItem(
        const std::string& url,
        const std::string& title,
        const std::string& note,
        unsigned int dir = 0,
        unsigned int id = 0
        );
    virtual ~BookmarkItem();

    void setAddress(const std::string & url) { m_url = url; };
    std::string getAddress() const { return m_url; };

    void setTitle(const std::string & title) { m_title = title; };
    std::string getTitle() const { return m_title; };

    void setNote(const std::string& note){m_note = note;};
    std::string getNote() const { return m_note;};

    void setId(int id) { m_saved_id = id; };
    unsigned int getId() const { return m_saved_id; };

    ....
    ....

    bool is_folder(void) const { return m_is_folder; }
    bool is_editable(void) const { return m_is_editable; }

    void set_folder_flag(bool flag) { m_is_folder = flag; }
    void set_editable_flag(bool flag) { m_is_editable = flag; }

private:
    unsigned int m_saved_id;
    std::string m_url;
    std::string m_title;
    std::string m_note;
    std::shared_ptr<tizen_browser::tools::BrowserImage> m_thumbnail;
    std::shared_ptr<tizen_browser::tools::BrowserImage> m_favicon;
    unsigned int m_directory;
    std::vector<unsigned int> m_tags;
    bool m_is_folder;
    bool m_is_editable;
};

Для нас интерес представляют члены m_is_folder и m_is_editable. Обратите внимание, что они находятся в конце описания класса. Я готов поставить $10 на то, что изначально их не было в первой версии класса и появились они уже позже, в процессе развития проекта. Так вот, когда добавляли эти члены, был модифицирован только один конструктор.

В результате мы имеем вот такие два конструктора:

BookmarkItem::BookmarkItem()
: m_saved_id(0)
, m_url()
, m_title()
, m_note()
, m_thumbnail(std::make_shared<.....>())
, m_favicon(std::make_shared<.....>())
, m_directory(0)
, m_is_folder(false)
, m_is_editable(true)
{
}

BookmarkItem::BookmarkItem(
                const std::string& url,
                const std::string& title,
                const std::string& note,
                unsigned int dir,
                unsigned int id
                        )
: m_saved_id(id)
, m_url(url)
, m_title(title)
, m_note(note)
, m_directory(dir)
{
}

Один конструктор инициализирует члены m_is_folder и m_is_editable, а другой — нет. У меня нет абсолютной уверенности, но скорее всего это ошибка.

Анализатор PVS-Studio выдаёт для второго конструктора следующее предупреждение: V730. Not all members of a class are initialized inside the constructor. Consider inspecting: m_is_folder, m_is_editable. BookmarkItem.cpp 268

Кстати, анализатор PVS-Studio умеет искать 64-битные ошибки. Tizen пока 32-битный, так что для него они пока не актуальны, но я хочу посвятить пару слов этой теме.

Я предвижу 64-битные ошибки

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

Рассмотрим простой пример для пояснения. Нужно создать массив указателей и для этого написан вот такой ошибочный код:

int **PtrArray = (int **)malloc(Count * size_of(int));

Память выделяется для массива int, а не для массива указателей. Правильный код должен был быть таким:

int **PtrArray = (int **)malloc(Count * size_of(int *));

Ошибка в 32-битной программе не проявляет себя. Размер указателя и типа int совпадают, поэтому выделяется буфер правильного размера. Всё корректно работает и проблемы начнутся только когда мы начнем собирать 64-битную версию программы.

Примечание. Конечно, в некоторых 64-битных системах размер указателя также может совпадать с размером типа int. А может быть так, что размеры будут отличаться и в 32-битных системах. Это экзотика, про которую нет смысла говорить. Рассмотренный пример будет корректно работать во всех распространённых 32-битных системах и давать сбой в 64-битных.

На нашем сайте можно найти много интересных материалов, посвященных 64-битным ошибкам и способам борьбы с ними:

  1. Коллекция примеров 64-битных ошибок в реальных программах
  2. C++11 и 64-битные ошибки
  3. Undefined behavior ближе, чем вы думаете
  4. Уроки разработки 64-битных приложений на языке Си/Си++

Вернёмся к проекту Tizen и возьмём для примера проект capi-media-vision-0.3.24. Здесь можно наблюдать интересную разновидность 64-битных ошибок. Анализатор PVS-Studio выдаёт для него 11 предупреждений с кодом V204:

  1. V204 Explicit conversion from 32-bit integer type to pointer type. mv_testsuite_common.c 94
  2. V204 Explicit conversion from 32-bit integer type to pointer type. mv_video_helper.c 103
  3. V204 Explicit conversion from 32-bit integer type to pointer type. mv_video_helper.c 345
  4. V204 Explicit conversion from 32-bit integer type to pointer type. mv_mask_buffer.c 39
  5. V204 Explicit conversion from 32-bit integer type to pointer type. mv_surveillance.c 52
  6. V204 Explicit conversion from 32-bit integer type to pointer type. mv_surveillance.c 134
  7. V204 Explicit conversion from 32-bit integer type to pointer type. mv_surveillance.c 172
  8. V204 Explicit conversion from 32-bit integer type to pointer type. surveillance_test_suite.c 452
  9. V204 Explicit conversion from 32-bit integer type to pointer type: (unsigned char *) malloc(buf_size) surveillance_test_suite.c 668
  10. V204 Explicit conversion from 32-bit integer type to pointer type: (unsigned char *) malloc(buf_size) surveillance_test_suite.c 998
  11. V204 Explicit conversion from 32-bit integer type to pointer type: (unsigned char *) malloc(buf_size) surveillance_test_suite.c 1109

Эти предупреждения выдаются на первый взгляд на совершенно безобидный код вот такого вида:

*string = (char*)malloc(real_string_len * sizeof(char));

В чем же причина? Дело в том, что нигде не подключен заголовочный файл, в котором объявлен тип функции malloc. В этом можно убедиться, выполнив препроцессирование C-файлов и посмотрев содержимое i-файлов. Использование функции malloc есть, а её объявления нет.

Так как эта программа на языке Си, то она компилируется, несмотря на отсутствие объявления. Раз функция не объявлена, то считается, что она принимает и возвращает аргументы типа int.

Т.е. компилятор считает, что функция объявлена так:

int malloc(int x);

Благодаря этому 32-битная программа отлично компилируется и работает. Указатель помещается в тип int и всё хорошо.

Эта программа будет компилироваться и в 64-битном режиме. Она даже почти всегда работает. Важно вот это самое «почти всегда».

Всё будет хорошо, пока память выделяется в младших адресах адресного пространства. Однако в процессе работы память в младшей части адресного пространства может оказаться занятой или фрагментированной. Тогда менеджер памяти вернёт память, выделенную за пределами младших адресов. Произойдёт сбой из-за обрезания старших бит в указателе. Подробнее, как и что происходит, я описывал здесь: "Красивая 64-битная ошибка на языке Си".

В итоге мы видим 11 дефектов, которые могут приводить к трудновоспроизводимым сбоям программы. Очень неприятные ошибки.

К сожалению, диагностики PVS-Studio для выявления 64-битных ошибок генерируют много шума (ложных срабатываний) и с этим ничего нельзя сделать. Такова их природа. Анализатор часто не знает, каков диапазон тех или иных значений, и не может понять, что код будет работать правильно. Но если хочется сделать надёжное и быстрое 64-битное приложение, следует поработать со всеми этими предупреждениями. Кстати, мы можем взять на себя эту кропотливую работу и выполнить на заказ портирование приложения на 64-битную систему. У нас есть опыт по этому направлению (см. "Как перенести проект размером в 9 млн строк кода на 64-битную платформу").

Так что если разработчики Tizen захотят сделать систему 64-битной, то знайте, наша команда готова помочь в этом.

Заключение


Спасибо всем за внимание. Тем, кто заинтересовался анализатором PVS-Studio и хочет узнать больше о его возможностях, предлагаю посмотреть большую презентацию (47 минут): PVS-Studio static code analyzer for C, C++ and C#.

Приглашаю подписываться, чтобы быть в курсе новых публикаций:




Если хотите поделиться этой статьей с англоязычной аудиторией, то прошу использовать ссылку на перевод: Andrey Karpov. Tizen: Summing Up

Прочитали статью и есть вопрос?
Часто к нашим статьям задают одни и те же вопросы. Ответы на них мы собрали здесь: Ответы на вопросы читателей статей про PVS-Studio, версия 2015. Пожалуйста, ознакомьтесь со списком.
Tags:
Hubs:
+30
Comments 5
Comments Comments 5

Articles

Information

Website
pvs-studio.com
Registered
Founded
2008
Employees
31–50 employees