Guide qualité : assurer la qualité du code
Table des matières
Cette page donne des conseils sur la façon d’améliorer la qualité logicielle des packages ROS 2, en se concentrant sur des domaines plus spécifiques que la section Pratiques de qualité du Guide du développeur
.
Les sections ci-dessous ont pour but d’aborder le noyau ROS 2, les packages d’application et d’écosystème et les bibliothèques clientes principales, C++ et Python. Les solutions présentées sont motivées par des considérations de conception et de mise en œuvre pour améliorer les attributs de qualité tels que « Fiabilité », « Sécurité », « Maintenabilité », « Déterminisme », etc. qui se rapportent à des exigences non fonctionnelles.
Analyse de code statique dans le cadre de la construction du package ament
Contexte:
Vous avez développé votre code de production C++.
Vous avez créé un package ROS 2 avec prise en charge de la construction avec
ament
.
Problème:
L’analyse de code statique au niveau de la bibliothèque n’est pas exécutée dans le cadre de la procédure de construction du package.
L’analyse de code statique au niveau de la bibliothèque doit être exécutée manuellement.
Risque d’oublier d’exécuter une analyse de code statique au niveau de la bibliothèque avant de créer une nouvelle version de package.
Solution:
Utilisez les capacités d’intégration de
ament
pour exécuter une analyse de code statique dans le cadre de la procédure de construction du package.
Mise en œuvre:
Insérez dans les packages le fichier
CMakeLists.txt
.
...
if(BUILD_TESTING)
find_package(ament_lint_auto REQUIRED)
ament_lint_auto_find_test_dependencies()
...
endif()
...
Insérez les dépendances de test
ament_lint
dans le fichierpackage.xml
des packages.
...
<package format="2">
...
<test_depend>ament_lint_auto</test_depend>
<test_depend>ament_lint_common</test_depend>
...
</package>
Exemples:
rclcpp
:rclcpp_lifecycle
:
Contexte résultant :
Les outils d’analyse de code statique pris en charge par
ament
sont exécutés dans le cadre de la construction du package.Les outils d’analyse de code statique non pris en charge par
ament
doivent être exécutés séparément.
Analyse statique de la sécurité des threads via l’annotation de code
Contexte:
Vous développez/déboguez votre code de production C++ multithread
Vous accédez aux données de plusieurs threads dans le code C++
Problème:
Les courses aux données et les blocages peuvent entraîner des bogues critiques.
Solution:
Utilisez l’analyse statique de la sécurité des threads de Clang <https://clang.llvm.org/docs/ThreadSafetyAnalysis.html>`__ en annotant le code fileté
Contexte de mise en œuvre :
Pour activer l’analyse de la sécurité des threads, le code doit être annoté pour permettre au compilateur d’en savoir plus sur la sémantique du code. Ces annotations sont des attributs spécifiques à Clang - par ex. __attribut__(capacité()))
. Au lieu d’utiliser ces attributs directement, ROS 2 fournit des macros de préprocesseur qui sont effacées lors de l’utilisation d’autres compilateurs.
Ces macros se trouvent dans rcpputils/thread_safety_annotations.hpp
- La documentation de l’analyse de la sécurité des threads indique
L’analyse de la sécurité des threads peut être utilisée avec n’importe quelle bibliothèque de threads, mais elle nécessite que l’API de threading soit encapsulée dans des classes et des méthodes qui ont les annotations appropriées
Nous avons décidé que nous voulions que les développeurs ROS 2 puissent utiliser les primitives de threading std::
directement pour leur développement. Nous ne voulons pas fournir nos propres types enveloppés comme suggéré ci-dessus.
Il existe trois bibliothèques standard C++ à connaître * La bibliothèque standard GNU libstdc++
- par défaut sous Linux, explicitement via l’option du compilateur -stdlib=libstdc++
* La bibliothèque standard LLVM libc++
(également appelé libcxx
) - par défaut sur macOS, défini explicitement par l’option du compilateur -stdlib=libc++
* La bibliothèque standard C++ de Windows - non pertinente pour ce cas d’utilisation
libcxx
annote ses implémentations std::mutex
et std::lock_guard
pour l’analyse de la sécurité des threads. Lors de l’utilisation de GNU libstdc++
, ces annotations ne sont pas présentes, donc l’analyse de la sécurité des threads ne peut pas être utilisée sur des types std::
non encapsulés.
Par conséquent, pour utiliser Thread Safety Analysis directement avec std::
types, nous devons utiliser libcxx
Mise en œuvre:
Les suggestions de migration de code ici ne sont en aucun cas complètes - lors de l’écriture (ou de l’annotation de code fileté existant), nous vous encourageons à utiliser autant d’annotations que cela est logique pour votre cas d’utilisation. Cependant, cette étape par étape est un excellent point de départ !
Activation de l’analyse pour le package/la cible
Lorsque le compilateur C++ est Clang, activez le drapeau
-Wthread-safety
. Exemple ci-dessous pour les projets basés sur CMakeif(CMAKE_CXX_COMPILER_ID MATCHES "Clang") add_compile_options(-Wthread-safety) # for your whole package target_compile_options(${MY_TARGET} PUBLIC -Wthread-safety) # for a single library or executable endif()
Code d’annotation
Étape 1 - Annoter les membres de données
Trouvez n’importe où
std::mutex
est utilisé pour protéger certaines données de membreAjoutez l’annotation
RCPPUTILS_TSA_GUARDED_BY(mutex_name)
aux données protégées par le mutex
class Foo { public: void incr(int amount) { std::lock_guard<std::mutex> lock(mutex_); bar += amount; } void get() const { return bar; } private: mutable std::mutex mutex_; int bar RCPPUTILS_TSA_GUARDED_BY(mutex_) = 0; };
Étape 2 - Correction des avertissements
Dans l’exemple ci-dessus -
Foo::get
produira un avertissement du compilateur ! Pour y remédier, verrouillez avant de retourner la barre
void get() const { std::lock_guard<std::mutex> lock(mutex_); return bar; }
Étape 3 - (Facultatif mais recommandé) Refactoriser le code existant en modèle Private-Mutex
Un modèle recommandé dans le code C++ threadé est de toujours garder votre
mutex
en tant que membreprivate:
de la structure de données. Cela fait de la sécurité des données la préoccupation de la structure contenante, déchargeant cette responsabilité des utilisateurs de la structure et minimisant la surface du code affecté.Rendre vos serrures privées peut nécessiter de repenser les interfaces avec vos données. C’est un excellent exercice - voici quelques points à considérer
Vous souhaiterez peut-être fournir des interfaces spécialisées pour effectuer des analyses nécessitant une logique de verrouillage complexe, par ex. compter les membres dans un ensemble filtré d’une structure de carte mutex-gardée, au lieu de renvoyer réellement la structure sous-jacente aux consommateurs
Envisagez de copier pour éviter le blocage, lorsque la quantité de données est faible. Cela peut permettre à d’autres threads d’accéder aux données partagées, ce qui peut potentiellement conduire à de meilleures performances globales.
Étape 4 - (Facultatif) Activer l’analyse de capacité négative
https://clang.llvm.org/docs/ThreadSafetyAnalysis.html#negative-capabilities
L’analyse de capacité négative vous permet de spécifier « ce verrou ne doit pas être maintenu lors de l’appel de cette fonction ». Il peut révéler des cas de blocage potentiels que d’autres annotations ne peuvent pas.
Là où vous avez spécifié
-Wthread-safety
, ajoutez le drapeau supplémentaire-Wthread-safety-negative
Sur toute fonction qui acquiert un verrou, utilisez le motif
RCPPUTILS_TSA_REQUIRES(!mutex)
Comment exécuter l’analyse
La ferme de construction ROS CI exécute un travail nocturne avec `` libcxx``, qui fera apparaître tous les problèmes dans la pile principale ROS 2 en étant marqué « Instable » lorsque l’analyse de la sécurité des threads déclenche des avertissements
Pour les exécutions locales, vous disposez des options suivantes, toutes équivalentes
Utilisez le mixin colcon clang-libcxx
colcon build --mixin clang-libcxx
Vous ne pouvez l’utiliser que si vous avez configuré des mixins pour votre installation colcon
Passer le compilateur à CMake
colcon build --cmake-args -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ -DCMAKE_CXX_FLAGS='-stdlib=libc++ -D_LIBCPP_ENABLE_THREAD_SAFETY_ANNOTATIONS' -DFORCE_BUILD_VENDOR_PKG=ON --no-warn-unused-cli
Remplacer le compilateur système
CC=clang CXX=clang++ colcon build --cmake-args -DCMAKE_CXX_FLAGS='-stdlib=libc++ -D_LIBCPP_ENABLE_THREAD_SAFETY_ANNOTATIONS' -DFORCE_BUILD_VENDOR_PKG=ON --no-warn-unused-cli
Contexte résultant :
Les blocages potentiels et les conditions de concurrence seront signalés au moment de la compilation, lors de l’utilisation de Clang et
libcxx
Analyse dynamique (data races & deadlocks)
Contexte:
Vous développez/déboguez votre code de production C++ multithread.
Vous utilisez pthreads ou threading C++11 + llvm libc++ (dans le cas de ThreadSanitizer).
Vous n’utilisez pas la liaison statique Libc/libstdc++ (dans le cas de ThreadSanitizer).
Vous ne créez pas d’exécutables non indépendants de la position (dans le cas de ThreadSanitizer).
Problème:
Les courses aux données et les blocages peuvent entraîner des bogues critiques.
Les courses de données et les blocages ne peuvent pas être détectés à l’aide de l’analyse statique (raison : limitation de l’analyse statique).
Les courses de données et les blocages ne doivent pas apparaître pendant le débogage/test de développement (raison : généralement, tous les chemins de contrôle possibles via le code de production ne sont pas exercés).
Solution:
Utilisez un outil d’analyse dynamique qui se concentre sur la recherche de courses de données et de blocages (ici clang ThreadSanitizer).
Mise en œuvre:
Compilez et liez le code de production avec clang en utilisant l’option
-fsanitize=thread
(cela instrumente le code de production).Dans le cas où un code de production différent doit être exécuté pendant l’analyse, envisagez une compilation conditionnelle, par ex. ThreadSanitizers _has_feature(thread_sanitizer).
Si certains codes ne doivent pas être instrumentés, envisagez ThreadSanitizers _/*attribute*/_((no_sanitize(« thread »))).
Dans le cas où certains fichiers ne doivent pas être instrumentés, envisagez l’exclusion au niveau du fichier ou de la fonction ThreadSanitizers blacklisting, plus spécifique : `ThreadSanitizers Sanitizer Special Case List < https://clang.llvm.org/docs/SanitizerSpecialCaseList.html>`__ ou avec ThreadSanitizers no_sanitize(« thread ») et utilisez l’option
--fsanitize-blacklist
.
Contexte résultant :
Plus de chances de trouver des courses de données et des blocages dans le code de production avant de le déployer.
Le résultat de l’analyse peut manquer de fiabilité, outil en phase bêta (dans le cas de ThreadSanitizer).
Surcoût dû à l’instrumentation du code de production (maintien de branches distinctes pour le code de production instrumenté/non instrumenté, etc.).
Le code instrumenté nécessite plus de mémoire par thread (dans le cas de ThreadSanitizer).
Le code instrumenté mappe beaucoup d’espace d’adressage virtuel (dans le cas de ThreadSanitizer).