Пару недель назад Линус Торвальдс активировал флаг -Werror для всех сборок ядра. Мнения сообщества касательно этого решения разделились. Против даже высказался разработчик фронтенда Clang и фреймворка LLVM. Обсуждаем ситуацию.

Unsplash / Markus Spiske
Unsplash / Markus Spiske

Warning’и не нужны

Год назад Линус Торвальдс в рассылке LKML рассказал, что перешел на gcc-10, и новые предупреждения компилятора (описания которых порой занимают целые абзацы) мешают работать над ядром — среди них сложно искать критические ошибки. Так, создатель Linux решил отключить warning’и, не представляющие интереса на конкретном этапе работы — например, restrict, stringop-overflow, array-bounds и zero-length-bounds. Но в начале сентября он предпринял довольно радикальный шаг в другом направлении и изменил параметры компиляции кода ядра Linux для мейнтейнеров. Торвальдс активировал флаг -Werror в Makefile, благодаря которому компилятор начал интерпретировать любое предупреждение как ошибку. Таким образом он хотел улучшить качество получаемых pull-запросов.

Справедливости ради стоит заметить, что соответствующая опция также появилась в Kconfig (то есть -Werror можно было отключить), но инициатива все равно разделила ИТ-сообщество на два лагеря. Одни посчитали, что нововведение серьезно повысит качество кодовой базы. Другие заметили, что оно не принесет ничего, кроме «головной боли», а ситуацию с большим количеством предупреждений нужно решать на стороне компиляторов — например, добавлением нескольких категорий фильтрации ошибок. Далее мы рассмотрим аргументы обеих сторон.

Сомнительное решение

Один из резидентов Hacker News в тематическом треде отметил, что обработка warning’ов при разработке программного обеспечения — это хорошая практика. Более того, предупреждения, касающиеся старого кода, могут скрывать реальные проблемы и баги, требующие срочного исправления. Это — одна из причин, по которой флаг -Werror используют многие разработчики. Однако можно привести контраргумент — большинство подобных проектов менее масштабные, чем Linux. В этом случае обработка всех предупреждений может оказаться не просто сложной, но невыполнимой задачей.

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

Unsplash / Gerrie van der Walt
Unsplash / Gerrie van der Walt

Против решения Линуса Торвальдса также высказался Ник Десанье из Google, работающий над Clang и LLVM. Он даже предложил патч, возвращающий флаг в изначальное состояние. По словам инженера, коллегам стоит внимательнее тестировать код на различных конфигурациях и серьезнее относиться к предупреждениям компилятора, но -Werror — слишком кардинальный шаг. Одним из немногих кейсов, когда этот флаг способен принести пользу, является защита «чистой» кодовой базы от ошибок. Эту точку зрения поддержал коллега Ника — по его мнению, старые предупреждения в малоиспользуемых подсистемах не должны мешать разработке и тестированию целого ядра.

Найденный компромисс

В начале недели Линус Торвальдс признался, что «погорячился» с включением -Werror, и сообществу удалось прийти к компромиссу. Теперь соответствующий флаг будут использовать только для билдов COMPILE_TEST, которые часто проводят с использованием CI-систем. Соответствующий коммит Линус уже опубликовал. Можно с уверенностью сказать, что вопрос не будут поднимать в ближайшем будущем — по крайней мере, до выпуска Linux kernel 5.15, намеченного на ноябрь.

По словам разработчиков, это не самый масштабный релиз, но даже он включает несколько интересных компонентов. Например, в его состав войдет новая реализация файловой системы NTFS от Paragon Software. Старый драйвер не обновлялся уже много лет, а новый привнесет дополнительную функциональность — расширенные атрибуты файлов, режим сжатия данных, списки доступа (ACL). Также можно ожидать появления драйверов для DMA-движка PassThru от AMD.


Свежие посты из нашего блога на Хабре:


Особенности работы интернет-провайдеров — в нашем корп. блоге:


Комментарии (41)


  1. zuborg
    19.09.2021 14:09
    +20

    Имхо, было бы правильнее сделать так, чтобы -Werror активировался не глобально, а для определённых подсистем, драйверов и т.д. Тогда можно было бы потихоньку подчищать актуальные подсистемы и драйвера от предупреждений, активировать для них -Werror и требовать чтобы последующие патчи для них компилировались чисто.


  1. slonopotamus
    19.09.2021 14:55
    +9

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


    1. VioletGiraffe
      19.09.2021 15:02
      +1

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


      1. Gorthauer87
        19.09.2021 15:08
        +3

        Проблема начинается для мейнтейнеров дистрибутивов и приобретает большой размах для дистров типа Gentoo


        1. delvin-fil
          19.09.2021 22:23

          Вот, кстати, да! GCC-10.3 ну просто половину обнов не собирает. Переключаюсь на 6.5 и работает.


        1. middle
          19.09.2021 23:18

          Этот флаг отключается при желании.


      1. slonopotamus
        19.09.2021 15:18
        +10

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


        1. sena73
          21.09.2021 21:06

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


        1. sena73
          21.09.2021 22:27

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

          Эту проблему можно легко обойти, отключая выдачу ошибок при публикации тарбола.


          1. slonopotamus
            21.09.2021 22:46

            Точно такую же проблему получит разработчик, решив прогуляться с каким-нибудь git bisect'ом в прошлое.


            1. sena73
              22.09.2021 21:28

              И эту проблему можно обойти достаточо легко, если сделать -Werror легко включаемым/отключаемым. Главное требование - новый патч не должен вызывать предупреждений для последней версии для текущего компилятора.

              Сделать это можно разными путями. Например включать -Werror при проверке патча, но в репе иметь -Werror отключенным.


      1. ncr
        19.09.2021 20:13
        +5

        Проблема в том, что пользователи компилятора не хотят пересматривать код и избавляться от предупреждений.
        Они хотят всем селом ходить в багтекер компилятора и орать «аааааа сломали мне билд».
        Поэтому разработчики компилятора, которые тоже люди и которых этот цирк совсем не радует, просто не включают новые предупреждения по умолчанию. И в -Wall -Wextra тоже не включают.

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


    1. creker
      20.09.2021 21:54
      +1

      Компиляторы действительно такое делают? Так нагло ломать обратную совместимость как-то не очень с их стороны и тут скорее проблема не Werror, а компиляторов, которые позволяют себе включать новые ворнинги по-умолчанию.

      Но в чем бы ни была проблема, все это можно было бы решить современными практиками CI. Я уж не знаю, как Линус и остальные ведут разработку и собирают ядро, но ядро для сборки должно не просто дергать какой попало компилятор, а поднимать полное рабочее окружение с нужной версией компилятора. В таком случае какую версию ядра ты ни скачай, она всегда соберется как надо. Хочется поднять версию компилятор — поднимаешь, фиксишь новые ошибки из-за новых ворнингов, комитишь новый билд скрипт. Все, проблема решена. Сейчас же получается каждый билдит тем, чем попало, что как-то не очень.


  1. realimba
    19.09.2021 15:34
    -9

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


    1. JerleShannara
      19.09.2021 17:19
      +2

      А лучше всё в духе «И слегка наговнокодил, всё, ура, проект готов» делать?


  1. Revertis
    19.09.2021 16:29
    -7

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


    1. Sulerad
      19.09.2021 21:21
      +4

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

      ИМХО, в нем просто-напросто, из-за некоторых проблем с IDE, довольно часто приходится читать вывод компилятора, а там предупреждения сильно мозолят глаза. Ну и практически нету легаси, из-за молодости языка.


      1. Revertis
        19.09.2021 22:09
        -5

        Да, вы правы, есть линтеры и т.п. Но тут дело в разной культуре разработки. В разработке на Расте, в самом сообществе, принято делать так, чтобы ворнингов не было.


  1. arheops
    19.09.2021 17:28
    +2

    Количество warning при компиляции ядра просто нереально велико, ибо кодовая база огромна и не все варнинги на самом деле правильные. В частности, есть хитрые оптимизации в нетворк-стеке, которые просто необходимы, но будут генерировать варнинги.
    Имхо для ядра количество работы по исключению warnings или по добавлению исключений — неприлично велико


    1. orion_tvv
      20.09.2021 22:20

      >Имхо для ядра количество работы по исключению warnings или по добавлению исключений — неприлично велико

      т.е. это ядро уже не спасти? и нужно другое?


      1. arheops
        20.09.2021 22:28

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


        1. bano-notit
          21.09.2021 19:21

          Так уже же переписывали и уже финансировали, если я правильно помню.


          1. arheops
            21.09.2021 19:46

            Полное переписывание никогда не выполнялося. Ибо оно влечет переписывание кучи драйверов и другого кода.


  1. NeoCode
    19.09.2021 17:29
    -7

    Это просто следствие морально устаревшего языка Си, используемого для ядра. Языку уже 50 лет, и понятно что когда разрабатывался дизайн языка, просто еще многого не знали, не было того огромного опыта, который появился за эти 50 лет.
    Rust пожалуй слишком строгий. Но Си — совершенно однозначно недостаточно строгий, и в нем недостаточно возможностей для того, чтобы выразить многие вещи без применения различных хаков.


    1. cher-nov
      19.09.2021 20:54
      +6

      Это просто следствие морально устаревшего русского языка, используемого в стране. Языку уже 500 лет, и понятно, что когда он только формировался, просто ещё многого не знали, не было того огромного опыта, который появился за эти 500 лет. Английский, пожалуй, слишком строгий. Но русский — совершенно однозначно недостаточно строгий, и в нём недостаточно возможностей для того, чтобы выразить многие вещи без применения различных эрративов.


      1. NeoCode
        19.09.2021 22:21

        Я между прочим не просто так написал, а как практикующий программист на С/С++. Я имею дело с задачами, в чем-то похожими на ядро, но конечно меньшего масштаба. И даже там проблемы Си уже заметны. Некоторые типы ошибок, определяемые как ошибки в С++, в Си проходят как предупреждения, а то что определяется как предупреждения в С++, в Си вообще не определяется.
        А так то понятно, что и на Ассемблере можно любую программу написать. Только трудозатраты будут огромными. И то, что мог бы взять на себя компьютер, придется делать человеку.


        1. cher-nov
          19.09.2021 22:41
          +3

          Я между прочим не просто так написал, а как практикующий программист на С/С++.

          Как будто Вы единственный практикующий программист на Си / C++ здесь.

          Некоторые типы ошибок, определяемые как ошибки в С++, в Си проходят как предупреждения, а то что определяется как предупреждения в С++, в Си вообще не определяется.

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

          И то, что мог бы взять на себя компьютер, придется делать человеку.

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

          Различные же виды автоматизации кодогенерации со стороны компилятора это понимание от программиста маскируют и скрывают. Что относится даже к таким простым и привычным с виду вещам, как шаблоны типов, инстанцируемые по прецеденту использования. Не говоря уже об различного рода управляемых (managed) типах и коде, которые неприемлемы в системном программировании (передаю пламенный коммунистический привет Rust).

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


          1. pdragon
            20.09.2021 09:10
            +1

            А что за управляемые типы? Я не си++ программист потому и спрашиваю, я знаю про управляемый код в C# это по смыслу что- то похожее?


            1. cher-nov
              20.09.2021 18:22
              +1

              А что за управляемые типы? Я не си++ программист потому и спрашиваю, я знаю про управляемый код в C# это по смыслу что- то похожее?

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

              В Delphi, например, таковыми являются структурные типы, содержащие стандартные строки и/или динамические массивы, память под которые выделяется и освобождается незаметно для программиста. В результате если определить переменную такого типа в подпрограмме, то в её начало и конец будет неявно вставлен код инициализации и очистки соответственно.

              TExample = record
                structured_fields_forever: Integer;
                some_other_plain_value_without_any_meaning: Integer;
                here_comes_an_automatic_type: String;
              end;

              Можно видеть, что семантика объявления строкового поля внешне идентична полям примитивных типов, однако факт его наличия меняет природу структуры. В C++ это называется RAII, но там сия концепция проходит красной нитью через весь язык и поэтому неожиданностей меньше, чем в Delphi, где ручное управление объектами соседствует со стандартными автоматическими типами.


          1. AnthonyMikh
            20.09.2021 12:12
            +4

            Не говоря уже об различного рода управляемых (managed) типах и коде, которые неприемлемы в системном программировании (передаю пламенный коммунистический привет Rust).

            А причём тут Rust? Он же никакого рантайма за собой не тащит.


            1. cher-nov
              20.09.2021 18:33
              +2

              А причём тут Rust? Он же никакого рантайма за собой не тащит.

              Rust тащит за собой стандартную библиотеку только в путь, за исключением no_std, но и там подразумевается libcore. Более того, стандартная библиотека Rust монолитна, поэтому нет минимальной планки совместимости с ОС, а значит каждая редакция языка и код на ней устаревают вместе с поддерживаемыми системами. Например, Rust поначалу стал несовместим с Windows XP исключительно из-за нацеленной на Windows 7 реализации threads в стандартной библиотеке, которую нельзя отсоединить и к которой в свою очередь по умолчанию привязаны все компилируемые программы для печати красивых сообщений о вылетах.

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


              1. MikailBag
                20.09.2021 21:52
                +4

                libcore

                Сама по себе библиотека проблемой не является: все лишние функции будут выкинуты при LTO. Да и содержимое libcore в основном - это либо широко используемые фичи, либо компиляторная магия. К тому же непонятно, зачем без веской причины отказываться от стандартной библиотеки языка, а потом руками ее куски переимплементировать: https://github.com/torvalds/linux/blob/e8f71f89236ef82d449991bfbc237e3cb6ea584f/include/linux/string.h#L22

                Более того, стандартная библиотека Rust монолитна

                Все эти претензии относятся к std - платформозависимой части стандартной библиотеки. Никто не предлагает тащить ее в ядро (хотя бы потому, что она не умеет работать в kernel mode), поэтому эти претензии нерелевантны.

                неявный счётчик ссылок на стеке.

                Только это не "счетчик ссылок", а флаг. В коде на C точно такой же флаг (возможно, засунутый внутрь объекта) точно также придется поддерживать (чтобы понять в конце функции, надо ли делать очистку или нет). Так что нарушений принципа zero cost пока не видно.


                1. cher-nov
                  20.09.2021 22:42
                  +1

                  Сама по себе библиотека проблемой не является: все лишние функции будут выкинуты при LTO.

                  Рассчитывать на конкретные автоматические оптимизации в системном программировании - это всегда очень плохая практика, как по мне.

                  Да и содержимое libcore в основном - это либо широко используемые фичи, либо компиляторная магия.

                  И это дополнительный аргумент не в пользу Rust, потому что язык для системного программирования в принципе не должен зависеть от какой-либо определённой стандартной библиотеки и/или её реализации.

                  К тому же непонятно, зачем без веской причины отказываться от стандартной библиотеки языка, а потом руками ее куски переимплементировать: https://github.com/torvalds/linux/blob/e8f71f89236ef82d449991bfbc237e3cb6ea584f/include/linux/string.h#L22

                  Затем, что "куски" могут существовать в качестве эффективных инструкций уровня системы команд, о чём буквально говорит указанная Вами строчка. Стандартная библиотека, включая реализации контейнеров в ней - это решения общего назначения, а универсальные решения всегда проигрывают частным в эффективности (благодаря чему, собственно, и достигается их универсальность).

                  Никто не предлагает тащить ее в ядро (хотя бы потому, что она не умеет работать в kernel mode), поэтому эти претензии нерелевантны.

                  Системное программирование - далеко не только про драйвера и прочий ring 0. Есть ещё различные утилиты для работы с системой (навроде SysInternals и NirSoft), которые общаются с ней через API, вполне себе спокойно обитая в user mode. Написание таких утилит на Rust нецелесообразно, потому что они будут постоянно лишаться поддержки старых систем даже будучи оставаясь совместимыми с ними по API. Можно разве что на одной и той же старой версии языка сидеть, не слезая.

                  Только это не "счетчик ссылок", а флаг. В коде на C точно такой же флаг (возможно, засунутый внутрь объекта) точно также придется поддерживать (чтобы понять в конце функции, надо ли делать очистку или нет).

                  В коде на Си такой флаг обычно представляет собой NULL.

                  Про "счётчик ссылок" виноват: полез за термином в это видео и подсознательно затем их перепутал.

                  Так что нарушений принципа zero cost пока не видно.

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


          1. Am0ralist
            20.09.2021 17:09
            +1

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


            1. cher-nov
              20.09.2021 18:35
              -1

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


              1. Am0ralist
                21.09.2021 11:06

                Речь в абзаце, откуда вы тиснули цитату, шла про ассемблер. Я вам даже весь кусок сего абзаца привёл. И к нему относилось, что всю магию компиляторов человеку пришлось бы писать вручную.
                Вы ж классически применили фигурное цитирование уровня:

                Было бы величайшей ошибкой думать. В.И. Ленин, ПСС, т. 42, стр. 74
                После чего смело победили соломенное чучелою


  1. DarkHost
    20.09.2021 17:42

    Короче, разработчики не хотят запариваться, и Линусу пришлось прогнуться.


  1. IGR2014
    20.09.2021 20:32

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

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


  1. quwy
    21.09.2021 02:42

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

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


  1. mbait
    21.09.2021 22:34

    Не знаю про остальных, а подгорание у мисье из гугла понять можно, ибо в корпорациях считается дурным тоном затрагивать тему чистоты и качества кода. Достаточно вглянуть на количество предупреждений при сборке Android или Chromium. А в Android ещё и в runtime куча предупреждений в логе.