За окном январь 2025 года, а это значит, пора подвести итоги за 2024 год! В этой статье вы узнаете, чем занималась команда PVS-Studio в прошлом году: новые плагины, интеграции, фичи и многое другое!

Если вы наткнулись на статью случайно и не знаете, что такое PVS-Studio
PVS-Studio — это статический анализатор, выявляющий ошибки и потенциальные уязвимости в коде программ на C, C++, C# и Java. Инструмент помогает контролировать качество кода и обеспечить безопасность проекта.
Больше информации — на странице продукта.
Общие изменения
Плагины для IDE
Поддержка анализа Java проектов в Visual Studio Code
В 2023 году появился плагин PVS-Studio для Visual Studio Code. Он прошёл довольно большой путь от простого средства просмотра отчётов до появления возможности проведения анализа C++ и C# проектов. Но на этом его путь не заканчивается!
В 2024 году наша команда поддержала анализ Java проектов в плагине PVS-Studio для Visual Studio Code! Теперь функционал плагина для Java включает:
-
запуск анализа и просмотр отчёта;
-
подавление предупреждений с помощью suppress-файлов;
-
инкрементальный анализ;
-
и другие функции, описанные в соответствующем разделе документации.

Улучшение разметки ложноположительных срабатываний и другие исправления для Visual Studio
В плагине PVS-Studio для Visual Studio в 2024 году появился режим, который при разметке ложноположительных срабатываний учитывает хэш исходной строки кода, на которой произошло срабатывание.
Хэши использовались и ранее, например, для навигации и работы suppress-файлов. Новый же вариант их использования позволяет понять, менялась ли строка с того момента, как срабатывание на ней было размечено как ложноположительное. Если строка изменялась, отметка ложноположительного срабатывания перестанет подавлять предупреждение.
std::string(4, std::string().at(0)); //-V530 //-VH"920700501"
Примечание. О новой функции разметки ложноположительных срабатываний можно прочитать в соответствующем разделе документации по ссылке.
Помимо этого, в версии 7.33 мы исправили проблемы с использованием плагина PVS-Studio с Visual Studio 2022 версий 17.12 и выше. Об этой проблеме, кстати, нам сообщил пользователь. Наша поддержка всегда готова помочь в решении различных проблем, поэтому не стесняйтесь сообщать нам, если вдруг что-то сломалось или появились идеи по улучшению анализатора!
Новые требования для IntelliJ IDEA, CLion и Rider
Для сохранения возможности поддерживать плагины и обеспечивать их стабильную работу в 2024 году минимальные поддерживаемые версии IDE от JetBrains для наших плагинов были подняты до версии 2022.2.
Помимо этого, в плагине PVS-Studio для CLion была добавлена поддержка файлов конфигурации диагностических правил .pvsconfig.
Примечание. Подробнее о работе файлов
.pvsconfigможно прочитать в нашей документации по ссылке.
Доработка плагина для Qt Creator
Плагин PVS-Studio для Qt Creator, как и плагин для Visual Studio Code, появился в позапрошлом (2023) году. В 2024 году мы существенно расширили количество поддерживаемых плагином версий этой IDE:
-
в начале года появилась поддержка плагина PVS-Studio для Qt Creator 12 на операционных системах семейства macOS;
-
позже для плагина были поддержаны версии 13 и 14;
-
также плагин PVS-Studio для Qt Creator был портирован на комплект разработчика для операционной системы «Нейтрино». Поддержана работа с Qt Creator 6.0.2 (Qt 5.15).
Кроме добавления новых версий, мы также начали убирать из поддержки устаревшие. Первой из них оказалась Qt Creator 8.x, вышедшая в июле 2022 года. Для этой IDE мы решили поддерживать все официальные версии не старше двух лет.
Примечание. О плагине PVS-Studio для Qt Creator можно прочитать в нашей документации по ссылке.
Интеграции
«Удвоение» плагина для SonarQube
Плагин PVS-Studio для SonarQube в 2024 году разделился на два плагина.
API SonarQube изменился, из-за чего в списке срабатываний неправильно отображалась достоверность срабатываний. Для того, чтобы исправить это недоразумение, пришлось разделить плагин на два. Об этих изменениях можно прочитать в нашей статье по ссылке.
Примечание. О том, как использовать плагин PVS-Studio для SonarQube, можно прочитать в соответствующем разделе нашей документации.
Новая интеграция — CodeChecker
В 2024 году по запросу наших пользователей мы добавили поддержку отчётов PVS-Studio в веб-интерфейс для агрегации и просмотра отчётов анализаторов с открытым исходным кодом CodeChecker.
Примечание. Подробнее об использовании PVS-Studio в CodeChecker можно прочитать в соответствующем разделе документации по ссылке.
Новый функционал интеграции в Unreal Engine
В прошлом году для интеграции PVS-Studio в Unreal Engine мы добавили поддержку работы с системой распределённой сборки SN-DBS. Изменения появились с версии Unreal Engine 5.5.
Примечание. Подробнее об интеграции PVS-Studio с SN-DBS написано в соответствующем разделе документации.
Помимо этого, в работе анализатора PVS-Studio с Unreal Engine произошло ещё множество разных изменений, о которых мы писали в недавней статье, которую можно прочитать по ссылке.
Другие изменения
Обновления в документации
На протяжении года мы продолжали совершенствовать нашу документацию. Помимо новых разделов, связанных с новыми функциями и диагностиками, также обновлялись и уже существующие.
Например, мы обновили и актуализировали раздел документации, посвящённый использованию анализатора в облачной CI-системе CircleCI, а в разделе документации об анализе C и C++ проектов на основе JSON Compilation Database появились пункты о работе со сборочными системами Bazel и SCons.
Анализ модифицированных файлов
Новый режим анализа модифицированных файлов вычисляет хэш файлов и записывает их в кэш зависимостей. При следующем запуске анализ будет проводиться только для тех файлов, хэш которых изменился за время с прошлого анализа.
Сейчас анализ модифицированных файлов доступен на Windows для MSBuild С, С++ и С# проектов с помощь утилиты PVS-Studio_Cmd и на Linux/macOS для С# проектов с помощью утилиты pvs-studio-dotnet.
Примечание. Подробнее о новом режиме анализа модифицированных файлов можно прочитать в нашей документации по ссылке.
PVS-Studio теперь на ARM!
Статический анализатор PVS-Studio работает на macOS с 2019 года, однако только на процессорах с архитектурой x86.
Но не после версии 7.34! В последнем релизе 2024 года появилась полноценная поддержка PVS-Studio на macOS для процессоров с архитектурой ARM.
Примечание. Скачать PVS-Studio можно на странице загрузок, а прочитать о том, как его установить, можно в соответствующем разделе документации по ссылке.
Используем анализатор по ГОСТ-Р 71207-2024
В апреле 2024 вышел ГОСТ-Р 71207-2024, посвящённый использованию статических анализаторов в процессе разработки безопасного программного обеспечения. Мы довольно много говорили об этой теме и даже выпустили целую серию вебинаров об этом стандарте.
В плагинах PVS-Studio для Visual Studio, Visual Studio Code, а также в утилите PlogConverter появилась возможность фильтровать ошибки по новым значениям параметра SAST ID, соответствующим классификации критических ошибок по ГОСТ-Р 71207-2024
C++
Улучшения и новый функционал
Расширение системы анализа отдельных файлов на языке C и C++
В 2024 году мы расширили систему анализа отдельных файлов в утилите pvs-studio-analyzer с помощью флага --source-files. Теперь использование утилиты в условиях отличия кэша зависимостей компиляций для C и C++ файлов от структуры проекта стало более удобным.
Примечание. Подробнее о кэше зависимостей компиляции для C и C++ проектов можно прочитать в соответствующем разделе документации по ссылке.
Поддержка GNU RISC-V GCC Toolchain
А ещё мы добавили поддержку GNU RISC-V GCC Toolchain для платформы RISC-V для C и C++ анализатора PVS-Studio.
Сокращение объёма памяти при анализе инстанцирований шаблонов в C++
Мы постарались сократить объем памяти, необходимый для анализа инстанцирований шаблонов в C++. Благодаря усилиям нашей команды удалось значительно снизить среднее потребление ресурсов анализатором. На одном из проектов, который мы используем при тестировании продукта, потребление памяти снизилось на более чем 90%:

Попробуйте обновлённый анализатор PVS-Studio по ссылке!
Механизм пользовательских аннотаций
Ранее в анализаторе PVS-Studio существовали различные механизмы, которые помогали адаптировать анализатор под кодовую базу. Они позволяли настраивать диагностические правила под потребности пользователя.
Примечание. Подробнее о дополнительной настройке диагностических правил C и C++ анализатора можно прочитать в соответствующем разделе документации по ссылке.
Однако в этом году мы пошли дальше и реализовали механизм пользовательских аннотаций в файлах формата JSON. Этот формат отличается большей гибкостью, возможностью расширения и лучшей читаемостью по сравнению с предыдущим. Кроме того, использование этого метода для разметки пользовательских функций и типов позволяет аннотировать даже third-party код. Уже сейчас новая система пользовательских аннотаций позволяет, например, задать:
-
для типов:
-
сходство со некоторыми классами из стандартной библиотеки:
std::unique_ptr,std::string,std::vectorи т.д.; -
семантику:
cheap-to-copy,copy-on-writeи т.д.
-
-
для функций:
-
свойства функции: не возвращает управление (
noreturn), объявлена как устаревшая и т.д.; -
свойства каждого из параметров функции:
nullable-объект должен быть валидным, параметр должен отличаться от другого параметра и т.д.; -
ограничения на параметры: можно запретить или разрешить передачу определённых целочисленных значений;
-
свойства возвращаемых значений: помеченные данные (для анализа помеченных данных, taint-анализ),
nullable-объект всегда валидный и т.д.
-
Чем могут помочь анализатору написанные вами аннотации? Ответ на этот вопрос можно найти в статье «Пользовательские аннотации кода для PVS-Studio«.
Примечание. Подробнее о работе механизма пользовательских аннотаций можно прочитать в соответствующем разделе документации по ссылке.
Улучшения в работе анализатора
В этом году было проведено множество изменений, касающихся разбора кода на C++. Мы достигли следующих улучшений:
-
оптимизация разбора стандартной библиотеки libc++: улучшен разбор шаблонов, добавлена поддержка nested inline namespace из C++20;
-
разбор шестнадцатеричных вещественных литералов из C++17;
-
улучшение разбора большой вложенности циклов.
И внедрили новые возможности:
-
разбор флагов стандартов C20, C++23, C++26 и их GNU-аналоги для языков C и C++;
-
разбор умных указателей из Boost:
boost::unique_ptr,boost::shared_ptr; -
разбор множественных деклараторов в using-declaration.
Новые диагностические правила
Диагностики общего назначения
-
V839. Function returns a constant value. This may interfere with move semantics.
-
V1104 . Priority of the ‘M’ operator is higher than that of the ‘N’ operator. Possible missing parentheses.
-
V1105. Suspicious string modification using the ‘operator+=’. The right operand is implicitly converted to a character type.
-
V1106. Qt. Class inherited from ‘QObject’ should contain at least one constructor that takes a pointer to ‘QObject’.
-
V1107. Function was declared as accepting unspecified number of parameters. Consider explicitly specifying the function parameters list.
-
V1108. Constraint specified in a custom function annotation on the parameter is violated.
-
V1109 . Function is deprecated. Consider switching to an equivalent newer function.
-
V1110. Constructor of a class inherited from ‘QObject’ does not use a pointer to a parent object.
-
V1111. The index was used without check after it was checked in previous lines.
-
V1112. Comparing expressions with different signedness can lead to unexpected results.
-
V1113. Potential resource leak. Calling the ‘memset’ function will change the pointer itself, not the allocated resource. Check the first and third arguments.
-
V1114. Suspicious use of type conversion operator when working with COM interfaces. Consider using the ‘QueryInterface’ member function.
-
V1115. Function annotated with the ‘pure’ attribute has side effects.
-
V1116. Creating an exception object without an explanatory message may result in insufficient logging.
-
V1117. The declared function type is cv-qualified. The behavior when using this type is undefined.
По запросам пользователей
-
V2021. Using assertions may cause the abnormal program termination in undesirable contexts.
-
V2022. Implicit type conversion from integer type to enum type.
Cтандарт MISRA
-
V2625. MISRA. Identifiers that define objects or functions with external linkage shall be unique.
Из них следующие диагностики классифицируются согласно ГОСТ Р 71207–2024 как критические:
-
V1106. Qt. Class inherited from ‘QObject’ should contain at least one constructor that takes a pointer to ‘QObject’.
-
V1109 . Function is deprecated. Consider switching to an equivalent newer function.
-
V1110. Constructor of a class inherited from ‘QObject’ does not use a pointer to a parent object.
-
V1111. The index was used without check after it was checked in previous lines.
-
V1112. Comparing expressions with different signedness can lead to unexpected results.
Выпущенные статьи
За год мы успели выпустить 45 статей, посвящённых языку C++. Как оказалось, наиболее популярные из них — про массивы:
В течение года мы работали над особо крупным проектом, который приобретает популярность. И уже сейчас мы с радостью представляем полную версию электронной книги, посвящённой неопределённому поведению в C++. Книга, созданная в сотрудничестве специалиста по безопасности программного обеспечения Дмитрия Свиридкина и сооснователя PVS-Studio Андрея Карпова, станет ценным ресурсом для программистов.
«Путеводитель C++ программиста по неопределённому поведению» состоит из 12 частей, в каждой из которых собраны самые загадочные и экзотические аспекты неопределённого поведения. Авторы делятся практическими примерами и советами, позволяющими избежать распространённых ошибок при разработке. Книга будет не только полезной, но и увлекательной для всех, кто работает с языком C++.
C#
Улучшения и новый функционал
Поддержка новой версии .NET
В 2024 году команда C# анализатора поддержала анализ проектов под .NET 9. Теперь он проводится с учётом всех нововведений.
Примечание. Если вам интересно узнать, что нового появилось в .NET 9, то можете ознакомиться с нашей обзорной статьёй нововведений.
Поддержка новых версий .NET и C# в PVS-Studio после их выпуска появляется достаточно оперативно. Чтобы не пропустить выход новой версии анализатора, приглашаем подписаться на рассылку пресс-релизов.
Пользовательские аннотации для C# анализатора
Как и в C++, в C# анализатор мы добавили возможность проставления пользовательских аннотаций в формате JSON.
Пользовательские аннотации — способ разметки типов и функций в формате JSON с целью дать анализатору дополнительную информацию. Благодаря этой информации анализатор может находить больше ошибок в коде. Такие аннотации необходимо поместить в специальный файл формата JSON.
Сейчас в C# части анализатора PVS-Studio пользовательские аннотации реализованы только для taint-анализа. Подобный способ разметки требуется для ГОСТ Р 71207–2024.
Теперь PVS-Studio предоставляет пользователям возможность размечать источники (процедуры-источники) и приёмники (процедуры-стоки) чувствительных данных. Таким образом, анализатор сможет точнее искать потенциальные уязвимости для конкретного проекта.
Стоит отметить, что в будущем мы планируем расширять возможности пользовательских C# аннотаций (не только для taint-анализа).
Примечание. Более детально о возможностях пользовательских C# аннотаций можно почитать в документации.
Настоящее и будущее Unity
В 2024 году особое внимание команды C# анализатора было направлено на Unity-специфичные диагностики.
Примечание. Если вам интересно, как статический анализ может помочь в поиске ошибок и оптимизации Unity-проектов, то приглашаем ознакомиться с вот этой статьёй.
Общий список новых Unity-специфичных диагностических правил:
-
V3205. Unity Engine. Improper creation of ‘MonoBehaviour’ or ‘ScriptableObject’ object using the ‘new’ operator. Use the special object creation method instead.
-
V3206. Unity Engine. A direct call to the coroutine-like method will not start it. Use the ‘StartCoroutine’ method instead.
-
V3207. The ‘not A or B’ logical pattern may not work as expected. The ‘not’ pattern is matched only to the first expression from the ‘or’ pattern.
-
V3208. Unity Engine. Using ‘WeakReference’ with ‘UnityEngine.Object’ is not supported. GC will not reclaim the object’s memory because it is linked to a native object.
-
V3209. Unity Engine. Using await on ‘Awaitable’ object more than once can lead to exception or deadlock, as such objects are returned to the pool after being awaited.
-
V3210. Unity Engine. Unity does not allow removing the ‘Transform’ component using ‘Destroy’ or ‘DestroyImmediate’ methods. The method call will be ignored.
-
V4006. Unity Engine. Multiple operations between complex and numeric values. Prioritizing operations between numeric values can optimize execution time.
-
V4007. Unity Engine. Avoid creating and destroying UnityEngine objects in performance-sensitive context. Consider activating and deactivating them instead.
В рамках исследования GameDev направления мы не забываем изучать новые версии Unity. Если вам интересно узнать, что нового в Unity 6, то приглашаем изучить нашу обзорную статью.
В 2025 году мы планируем добавить ещё больше диагностик, посвящённых оптимизации и ориентированных на Unity. Если у вас есть мысли по поводу того, какие правила были бы полезны при разработке под этот игровой движок, пожалуйста, напишите нам.
Различные улучшения
-
реализовали отслеживание изменения возвращаемого значения метода между вызовами;
-
доработали data flow для анализа псевдонимов;
-
улучшили связь логической и ссылочной переменных;
-
оптимизировали потребление памяти и время открытия больших отчётов (более 1 Гб) в формате
.plogи.jsonпри работе из Visual Studio.
Новые диагностические правила
Диагностики общего назначения
-
V3194. Calling ‘OfType’ for collection will return an empty collection. It is not possible to cast collection elements to the type parameter.
-
V3195. Collection initializer implicitly calls ‘Add’ method. Using it on member with default value of null will result in null dereference exception.
-
V3196. Parameter is not utilized inside the method body, but an identifier with a similar name is used inside the same method.
-
V3197. The compared value inside the ‘Object.Equals’ override is converted to a different type that does not contain the override.
-
V3198. The variable is assigned the same value that it already holds.
-
V3199. The index from end operator is used with the value that is less than or equal to zero. Collection index will be out of bounds.
-
V3200. Possible overflow. The expression will be evaluated before casting. Consider casting one of the operands instead.
-
V3201. Return value is not always used. Consider inspecting the ‘foo’ method.
-
V3202. Unreachable code detected. The ‘case’ value is out of the range of the match expression.
-
V3203. Method parameter is not used.
-
V3204. The expression is always false due to implicit type conversion. Overflow check is incorrect.
-
V3205. Unity Engine. Improper creation of ‘MonoBehaviour’ or ‘ScriptableObject’ object using the ‘new’ operator. Use the special object creation method instead.
-
V3206. Unity Engine. A direct call to the coroutine-like method will not start it. Use the ‘StartCoroutine’ method instead.
-
V3207. The ‘not A or B’ logical pattern may not work as expected. The ‘not’ pattern is matched only to the first expression from the ‘or’ pattern.
-
V3208. Unity Engine. Using ‘WeakReference’ with ‘UnityEngine.Object’ is not supported. GC will not reclaim the object’s memory because it is linked to a native object.
-
V3209. Unity Engine. Using await on ‘Awaitable’ object more than once can lead to exception or deadlock, as such objects are returned to the pool after being awaited.
-
V3210. Unity Engine. Unity does not allow removing the ‘Transform’ component using ‘Destroy’ or ‘DestroyImmediate’ methods. The method call will be ignored.
Микрооптимизации
-
V4006. Unity Engine. Multiple operations between complex and numeric values. Prioritizing operations between numeric values can optimize execution time.
-
V4007. Unity Engine. Avoid creating and destroying UnityEngine objects in performance-sensitive context. Consider activating and deactivating them instead.
Из них следующие диагностики классифицируются согласно ГОСТ Р 71207–2024 как критические:
-
V3200. Possible overflow. The expression will be evaluated before casting. Consider casting one of the operands instead.
-
V3204. The expression is always false due to implicit type conversion. Overflow check is incorrect.
Выпущенные статьи
За год C# команда выпустила более 20 статей, посвящённых проверке проектов и различных особенностей C#. Вот самые популярные из них:
-
Исходный код на прощание: разбор ошибок в проектах закрывшейся инди-студии
-
Анализ кода WolvenKit: что нужно знать перед созданием модов для Cyberpunk 2077
-
Как одна строка может положить приложение? Поиск проблем и уязвимостей в ScreenToGif
А ещё у нас появилась новая постоянная рубрика — .NET Digest. В нём мы освещаем самые интересные новости и события в мире .NET! С последней частью можно ознакомиться тут.
Java
Улучшения и новый функционал
Taint-анализ
Декабрьский релиз привнёс в Java анализатор механизм, позволяющий разрабатывать taint-диагностики. Это позволило нам начать значительно расширяться как SAST-решению.
Taint-анализ — это технология, позволяющая отслеживать распространение по программе данных, пришедших из внешнего контекста. Попадание таких данных в определённые ключевые точки способно привести к различным уязвимостям.
Первой taint-диагностикой в Java анализаторе стала V5309, в рамках которой происходит поиск потенциальных SQL-инъекций.
Если вам хочется немного углубиться в теорию о том, что из себя представляет анализ заражённых данных и как подобные механизмы реализуются, рекомендую прочитать нашу статью об этом.
Установка таймаута для анализа в IDEA
Мы добавили возможность устанавливать таймаут для анализа в нашем плагине для IDEA. Теперь вы можете устанавливать в плагине временной лимит, по прошествии которого анализ будет завершаться автоматически.
И это не всё. Уже в следующем релизе запланировано большое количество изменений и доработок в нашем IDEA плагине. Следите за обновлениями 🙂
Новые диагностические правила
Диагностики общего назначения
-
V6108. Do not use real-type variables in ‘for’ loop counters;
-
V6109. Potentially predictable seed is used in pseudo-random number generator;
-
V6110. Using an environment variable could be unsafe or unreliable. Consider using trusted system property instead;
-
V6111. Potentially negative value is used as the size of an array;
-
V6112. Calling the ‘getClass’ method repeatedly or on the value of the ‘.class’ literal will always return the instance of the ‘Class<Class>’ type;
-
V6113. Suspicious division. Absolute value of the left operand is less than the value of the right operand;
-
V6114. The ‘A’ class containing Closeable members does not release the resources that the field is holding;
-
V6115. Not all Closeable members are released inside the ‘close’ method;
-
V6116. The class does not implement the Closeable interface, but it contains the ‘close’ method that releases resources;
-
V6117. Possible overflow. The expression will be evaluated before casting. Consider casting one of the operands instead;
-
V6118. The original exception object was swallowed. Cause of original exception could be lost;
-
V6119. The result of ‘&’ operator is always ‘0’;
-
V6120. The result of the ‘&’ operator is ‘0’ because one of the operands is ‘0’;
-
V6121. Return value is not always used. Consider inspecting the ‘foo’ method;
-
V6122. The ‘Y’ (week year) pattern is used for date formatting. Check whether the ‘y’ (year) pattern was intended instead;
-
V6123. Modified value of the operand is not used after the increment/decrement operation;
-
V6124. Converting an integer literal to the type with a smaller value range will result in overflow;
-
V6125. Calling the ‘wait’, ‘notify’, and ‘notifyAll’ methods outside of synchronized context will lead to ‘IllegalMonitorStateException’;
-
V5309. OWASP. Possible SQL injection. Potentially tainted data is used to create SQL command;
Из них следующие диагностики классифицируются согласно ГОСТ Р 71207–2024 как критические:
-
V6109. Ошибки некорректного использования системных процедур и интерфейсов, связанных с обеспечением информационной безопасности;
-
V6117. Ошибки целочисленного переполнения и некорректного совместного использования знаковых и беззнаковых чисел;
-
V6124. Ошибки целочисленного переполнения и некорректного совместного использования знаковых и беззнаковых чисел;
-
V6125. Ошибки при работе с многопоточными примитивами.
Выпущенные статьи
Также за 2024 год у нас вышли статьи, в которых мы описывали процесс создания диагностик. Рекомендую ознакомиться:
Заключение
Мы постарались рассказать обо всех важных нововведениях в анализаторе за 2024 год, но даже так мы охватили лишь часть. Полный список изменений можно найти на странице истории релизов.
А если вам интересно своевременно узнавать об обновлениях в анализаторе, подписывайтесь на наши рассылки пресс-релизов (а ещё там появилась рассылка с IT-ивентами для разработчиков и дайджест статей).
А какие изменения вы бы хотели увидеть в PVS-Studio в 2025 году? Делитесь с нами своими идеями в комментариях!
Не забывайте обновляться до последней версии PVS-Studio! Все эти улучшения будут доступны в ней! Самую актуальную версию вы всегда можете найти здесь.
Если хотите поделиться этой статьей с англоязычной аудиторией, то прошу использовать ссылку на перевод: Valerii Filatov, Aleksandra Uvarova, Gleb Aslamov, Vladislav Bogdanov. PVS-Studio in 2024.
ссылка на оригинал статьи https://habr.com/ru/articles/876410/
Добавить комментарий