Bitwarden – менеджер паролей с открытым исходным кодом. Это программное обеспечение помогает генерировать уникальные пароли и управлять ими. Получится ли у анализатора PVS-Studio отыскать ошибки в таком проекте? Менеджер паролей – решение для хранения и генерации паролей. Любой человек, использующий подобный софт, хочет быть уверен, что его данные находятся в безопасности. Качество кода такого инструмента имеет большое значение. Именно поэтому я решил взять исходный код Bitwarden от 15.03.2022 из репозитория и проверить его с помощью статического анализатора PVS-Studio. Анализатор выдал на код проекта 247 предупреждений. Среди них мне удалось найти кое-что интересное. Предупреждение PVS-Studio: V3008 The 'Amount' variable is assigned values twice successively. Perhaps this is a mistake. Check lines: 148, 142. BillingInfo.cs 148 Обратите внимание на инициализацию Amount. Данному свойству присваивается выражение inv.AmountDue / 100M. Необычно выглядит то, что буквально через пять строчек кода производится аналогичная операция, но уже с присваиванием inv.Total / 100M. Сложно сказать, какое именно значение хотел использовать разработчик. Если последнее присваивание верно, то первое является избыточным. Это не добавляет красоты коду, но всё же на логику выполнения программы не влияет. В противном же случае данный участок кода будет работать некорректно. Предупреждение PVS-Studio: V3123 Perhaps the '??' operator works in a different way than it was expected. Its priority is lower than priority of other operators in its left part. AppleIapService.cs 96 Похоже, разработчик ожидал, что в сообщение будет добавлено либо значение свойства Status, либо строка "null". После чего полученный результат будет прибавлен к строке "Failed verifying Apple IAP after too many attempts. Last attempt status: ". К сожалению, поведение программы будет иным. Для того чтобы разобраться в сути данного срабатывания, стоит вспомнить приоритеты операторов. Оператор '??' имеет более низкий приоритет по сравнению с оператором '+'. Следовательно, сначала произойдет сложение строки со значением свойства Status, а уже после сработает оператор null-coalescing. В случае если lastReceiptStatus не null и Status не null, данный метод работает корректно. Если же lastReceiptStatus или Status всё-таки null, выведется следующее сообщение: "Failed verifying Apple IAP after too many attempts. Last attempt status: ". Оно, очевидно, является некорректным. Ожидаемое сообщение выглядит следующим образом: "Failed verifying Apple IAP after too many attempts. Last attempt status: null". Чтобы исправить ошибку, нужно взять часть выражения в скобки: Здесь PVS-Studio выдаёт сразу два предупреждения: V3063 A part of conditional expression is always false if it is evaluated: globalSettings.SelfHosted. PremiumRequestModel.cs 23 V3063 A part of conditional expression is always false if it is evaluated: License != null. PremiumRequestModel.cs 23 Часть логического выражения всегда будет ложной. Чтобы в этом убедиться, следует рассмотреть возможные комбинации значений в условии: если License не равно null, то левый операнд оператора '||' – true. Правый операнд вычисляться не будет; если globalSettings.SelfHosted будет true, то левый операнд оператора '||' – true. Правый операнд вычисляться не будет; если License равно null, то правый операнд оператора '||' – false; если globalSettings.SelfHosted будет false, то правый операнд оператора '||' – false; Получается, что второй операнд оператора '||' либо вообще не проверяется, либо будет равен false. Следовательно, он не влияет на истинность всего условия. Часть условия после '||' является избыточной. Скорее всего, подобный формат записи был выбран из некоторых соображений читаемости, но получилось немного странно. Возможно, тут должно проверяться что-то другое. Предупреждение PVS-Studio: V3063 A part of conditional expression is always false if it is evaluated: sponsorship.SponsorshipLapsedDate.HasValue. OrganizationSponsorshipService.cs 308 Сообщение анализатора говорит о том, что часть логического условия всегда ложна. Обратите внимание на инициализацию sponsorship.SponsorshipLapsedDate. Разработчик присваивает данному свойству null, после чего в условии проверяет значение HasValue у него же. Странно, что проверка производится сразу после инициализации. Она могла бы иметь смысл, если бы свойство sponsorship.CloudSponsor изменяло значение sponsorship.SponsorshipLapsedDate, но это не так. sponsorship.CloudSponsor — обычное автосвойство: Возможно, проверка реализована на будущее, но сейчас она выглядит странно. Предупреждение PVS-Studio: V3095 The 'userId' object was used before it was verified against null. Check lines: 640, 683. CipherService.cs 640 Для введения в суть срабатывания стоит отметить, что переменная userId является объектом nullable-типа. Обратите внимание на данный фрагмент кода: Перед обращением к userId.Value. разработчик проверяет userId.HasValue. Скорее всего, он предполагал, что проверяемое значение может быть равно false. Перед вышеописанным обращением было еще одно: Здесь также производится обращение к userId.Value, но проверки userId.HasValue почему-то нет. Либо разработчик забыл проверить HasValue в первый раз, либо произвёл лишнюю проверку во второй. Выясним, какой из вариантов верный. Для этого рассмотрим инициализацию userId: По коду видно, что оба операнда оператора '??' могут принять значение nullable-типа, у которого свойство HasValue будет равно false. Следовательно, userId.HasValue может иметь значение false. Получается, что при первом обращении к userId.Value всё-таки стоит проверить userId.HasValue. Ведь если значение свойства HasValue равно false, обращение к Value этой же переменной приведёт к выбрасыванию исключения типа InvalidOperationException. Предупреждение PVS-Studio: V3095 The 'organization' object was used before it was verified against null. Check lines: 1085, 1086. OrganizationService.cs 1085 В условии проверяют organization на равенство null. Получается, разработчик предполагал, что эта переменная может быть равна null. Также перед условием происходит обращение к свойству Seats переменной organization без какой-либо проверки на null. Если organization – null, данное обращение приведёт к выбросу исключения типа NullReferenceException. Предупреждение PVS-Studio: V3125 The 'sub' object was used after it was verified against null. Check lines: 1554, 1549. StripePaymentService.cs 1554 Анализатор сообщает о возможном обращении по нулевой ссылке. Перед тем как передать переменную sub в конструктор SubscriptionInfo.BillingSubscription, разработчик проверяет её на null. Странно, что сразу же после этого без какой-либо проверки происходит обращение к свойству CanceledAt этой переменной. Такое обращение может привести к выбрасыванию исключения типа NullReferenceException. Предупреждение PVS-Studio: V3105 The '_billingSettings' variable was used after it was assigned through null-conditional operator. NullReferenceException is possible. FreshdeskController.cs 47 Обратите внимание на инициализацию поля _billingSettings. Можно заметить, что ему присваивается значение свойства Value, полученное с применением оператора null-conditional. Скорее всего, ожидается, что billingSettings может иметь значение null. Значит, в поле _billingSettings также может быть присвоен null. После инициализации _billingSettings происходит обращение к свойству FreshdeskApiKey: Данное обращение может привести к выбрасыванию исключения типа NullReferenceException. Предупреждение PVS-Studio: V3105 The 'bSettings' variable was used after it was assigned through null-conditional operator. NullReferenceException is possible. PayPalIpnClient.cs 22 Запись, аналогичная предыдущей, встречается в реализации метода PayPalIpnClient. Здесь переменной bSettings присваивается значение, полученное с помощью оператора null-conditional. Далее происходит обращение к свойству PayPal этой же переменной. Подобное обращение может привести к выбрасыванию исключения типа NullReferenceException. Предупреждение PVS-Studio: V3095 The 'pageOptions' object was used before it was verified against null. Check lines: 135, 137. EventRepository.cs 135 Очередная странность, связанная с отсутствием проверки на null. Обращение к переменной pageOptions производится два раза. При втором обращении используется оператор null-conditional, а вот при первом – почему-то нет. Либо разработчик выполнил излишнюю проверку на null во втором случае, либо забыл проверить pageOptions в первом. Если верно второе предположение, то возможно обращение по нулевой ссылке, что приведет к исключению типа NullReferenceException. Предупреждение PVS-Studio: V3125 The 'taxInfo' object was used after it was verified against null. Check lines: 135, 99. StripePaymentService.cs 135 И снова анализатор обнаружил место, в котором может произойти разыменование нулевой ссылки. Действительно, выглядит странно, что в условии происходит проверка переменной taxInfo на null, а вот при ряде обращений к этой же переменной проверки нет. Предупреждение PVS-Studio: V3095 The 'x.u' object was used before it was verified against null. Check lines: 24, 32. OrganizationUserUserViewQuery.cs 24 Необычно, что переменная x.u сравнивается с null, ведь перед этим к её свойствам происходило обращение (и не один раз!). Возможно, что это просто лишняя проверка. Также есть вероятность, что разработчик забывал проверять на null перед присваиванием. Предупреждение PVS-Studio: V3159 Modified value of the 'retriedCount' operand is not used after the postfix increment operation. FreshdeskController.cs 167 Обратите внимание на инкрементацию переменной retriedCount. Весьма странно, что в данном случае использована постфиксная запись. Ведь сначала возвращается текущее значение переменной, а уже потом производится увеличение этого значения. Возможно, стоит изменить постфиксную запись на префиксную: Для большей наглядности можно использовать следующую запись: Пожалуй, среди описанных дефектов нет чего-то, что представляет угрозу в плане безопасности. Большинство ошибок сводится к возможности возникновения исключений при работе с нулевыми ссылками. Тем не менее, эти места стоит поправить. Как было сказано в начале статьи, даже из относительно небольшого количества срабатываний анализатора можно выделить интересные моменты. Возможно, некоторые недочёты и не влияют на работу программы, однако их также стоит избегать. Хотя бы для того, чтобы у других разработчиков не возникало лишних вопросов. Мне кажется, весьма удобно иметь средство, позволяющее быстро найти дефекты в коде. Как видите, таким средством может стать статический анализатор :). Предлагаю вам бесплатно попробовать PVS-Studio, чтобы посмотреть, какие ошибки таятся в интересующем вас проекте. Если хотите поделиться этой статьей с англоязычной аудиторией, то прошу использовать ссылку на перевод: Nikita Panevin. Are you sure your passwords protected? The Bitwarden project check.  Программист C#