J'ai du code C++ hérité que je suis supposé supprimer du code inutilisé. Le problème est que la base de code est volumineuse.
Comment puis-je savoir quel code n'est jamais appelé/jamais utilisé?
Il existe deux variétés de code inutilisé:
Pour le premier type, un bon compilateur peut aider:
-Wunused
_ (GCC, Clang ) devrait avertir des variables inutilisées, l'analyseur non utilisé de Clang a même été incrémenté pour avertir des variables qui ne sont jamais lues (même si elles sont utilisées).-Wunreachable-code
_ (ancien GCC, supprimé en 201 ) devrait avertir des blocs locaux qui ne sont jamais accédés (cela se produit avec des retours précoces ou des conditions toujours considérées comme vraies)catch
, car le compilateur ne peut généralement pas prouver qu’aucune exception ne sera levée.Pour le second type, c'est beaucoup plus difficile. Statiquement, il nécessite une analyse complète du programme. Même si l'optimisation du temps de liaison peut réellement supprimer le code mort, le programme a été tellement transformé au moment de son exécution qu'il est presque impossible de transmettre des informations significatives à l'utilisateur.
Il y a donc deux approches:
gcov
. Notez que des indicateurs spécifiques doivent être passés lors de la compilation pour fonctionner correctement) Vous exécutez l’outil de couverture de code avec un bon ensemble d’entrées variées (vos tests unitaires ou vos tests de non-régression), le code mort est nécessairement compris dans le code non atteint ... et vous pouvez donc commencer à partir de cet endroit.Si le sujet vous intéresse énormément et que vous avez le temps et l'envie de créer vous-même un outil, je vous conseillerais d'utiliser les bibliothèques Clang pour créer un tel outil.
Comme Clang analysera le code pour vous et effectuera la résolution de la surcharge, vous n’aurez pas à vous préoccuper des règles du langage C++ et vous pourrez vous concentrer sur le problème à résoudre.
Cependant, ce type de technique ne peut pas identifier les remplacements virtuels inutilisés, car ils pourraient être appelés par un code tiers que vous ne pouvez pas raisonner.
Dans le cas de fonctions entières inutilisées (et de variables globales inutilisées), GCC peut réellement effectuer la majeure partie du travail, à condition que vous utilisiez GCC et GNU ld.
Lors de la compilation de la source, utilisez -ffunction-sections
et -fdata-sections
, puis en reliant utiliser -Wl,--gc-sections,--print-gc-sections
. L'éditeur de liens va maintenant lister toutes les fonctions qui pourraient être supprimées car elles n'ont jamais été appelées et tous les globals qui n'ont jamais été référencés.
(Bien sûr, vous pouvez aussi sauter le --print-gc-sections
partie et laissez l’éditeur de liens supprimer les fonctions en mode silencieux, mais conservez-les dans le source.)
Note: cela ne trouvera que des fonctions complètes non utilisées, cela ne fera rien au sujet du code mort dans les fonctions. Les fonctions appelées depuis le code mort dans les fonctions en direct seront également conservées.
Certaines fonctionnalités spécifiques à C++ poseront également des problèmes, notamment:
Dans les deux cas, tout ce qui est utilisé par une fonction virtuelle ou un constructeur de variable globale doit également être conservé.
Une mise en garde supplémentaire est que si vous construisez une bibliothèque partagée, les paramètres par défaut de GCC exporteront chaque fonction de la bibliothèque partagée, ce qui entraînera son exportation. "utilisé" en ce qui concerne l'éditeur de liens. Pour résoudre ce problème, vous devez définir le paramètre par défaut de masquage de symboles au lieu d’exporter (en utilisant par exemple -fvisibility=hidden
), puis sélectionnez explicitement les fonctions exportées que vous devez exporter.
Eh bien, si vous utilisez g ++, vous pouvez utiliser cet indicateur -Wunused
Selon la documentation:
Avertir chaque fois qu'une variable est utilisée en dehors de sa déclaration, chaque fois qu'une fonction est déclarée statique mais non définie, chaque fois qu'une étiquette est déclarée mais non utilisée et chaque fois qu'une instruction calcule un résultat explicitement non utilisé.
http://docs.freebsd.org/info/gcc/gcc.info.Warning_Options.html
Edit : Voici un autre indicateur utile -Wunreachable-code
Selon la documentation:
Cette option est destinée à avertir lorsque le compilateur détecte qu'au moins une ligne entière de code source ne sera jamais exécutée, car certaines conditions ne sont jamais satisfaites ou parce qu'elles suivent une procédure qui ne retourne jamais.
Mise à jour : J'ai trouvé un sujet similaire Détection de code mort dans un projet C/C++ hérité
Je pense que vous recherchez un outil couverture de code . Un outil de couverture de code analysera votre code en cours d’exécution et vous indiquera quelles lignes de code ont été exécutées et combien de fois, ainsi que celles qui ne l’ont pas été.
Vous pouvez essayer de donner une chance à cet outil de couverture de code open source: TestCocoon - outil de couverture de code pour C/C++ et C #.
La vraie réponse est la suivante: Vous ne pouvez jamais vraiment savoir avec certitude.
Au moins, dans les cas non triviaux, vous ne pouvez pas être sûr d'avoir tout compris. Considérez ce qui suit à partir de article de Wikipedia sur le code inaccessible :
double x = sqrt(2);
if (x > 5)
{
doStuff();
}
Comme Wikipedia le note correctement, un compilateur intelligent peut être capable d’attraper quelque chose comme ça. Mais considérons une modification:
int y;
cin >> y;
double x = sqrt((double)y);
if (x != 0 && x < 1)
{
doStuff();
}
Est-ce que le compilateur va attraper ça? Peut être. Mais pour cela, il faudra faire plus que lancer sqrt
contre une valeur scalaire constante. Il va falloir comprendre que (double)y
sera toujours un entier (facile), puis comprendra la plage mathématique de sqrt
pour l'ensemble des entiers (durs). Un compilateur très sophistiqué pourrait peut-être faire cela pour la fonction sqrt
ou pour chaque fonction de math.h, ou pour toute fonction à entrée fixe dont le domaine peut être représenté en dehors. Cela devient très, très complexe et la complexité est pratiquement illimitée. Vous pouvez continuer à ajouter des couches de sophistication à votre compilateur, mais il y aura toujours un moyen d'insérer du code inaccessible pour un ensemble d'entrées donné.
Et puis il y a les ensembles d'entrées qui ne sont jamais entrés. Des entrées qui n'auraient aucun sens dans la vie réelle, ou qui seraient bloquées par la logique de validation ailleurs. Le compilateur n'a aucun moyen de connaître ces informations.
Le résultat final est que, même si les outils logiciels mentionnés par d’autres sont extrêmement utiles, vous ne saurez jamais si vous avez tout saisi, à moins que vous ne lisiez le code manuellement par la suite. Même alors, vous ne serez jamais certain de n'avoir rien manqué.
La seule vraie solution, à mon humble avis, est d’être aussi vigilant que possible, d’utiliser l’automatisation à votre disposition, de procéder à une refactorisation dans la mesure du possible, et de rechercher en permanence des moyens d’améliorer votre code. Bien sûr, c’est une bonne idée de le faire quand même.
Je ne l'ai pas utilisé moi-même, mais cppcheck , prétend retrouver des fonctions inutilisées. Cela ne résoudra probablement pas le problème, mais ce pourrait être un début.
Vous pouvez essayer d'utiliser PC-lint/FlexeLint de Gimple Software . Il prétend à
trouver des macros, typedef's, classes, membres, déclarations, etc. inutilisés dans tout le projet
Je l'ai utilisé pour l'analyse statique et l'ai trouvé très bien, mais je dois admettre que je ne l'ai pas utilisé pour rechercher spécifiquement du code mort.
Marquez autant de fonctions et de variables publiques que privées ou protégées sans causer d'erreur de compilation. Essayez de refactoriser le code. En rendant les fonctions privées et, dans une certaine mesure, protégées, vous réduisez votre zone de recherche car les fonctions privées ne peuvent être appelées que de la même classe (sauf en cas de macro stupide ou d’autres astuces pour contourner la restriction d’accès. Si tel était le cas, je vous le recommanderais. trouver un nouvel emploi). Il est beaucoup plus facile de déterminer que vous n'avez pas besoin d'une fonction privée, car seule la classe sur laquelle vous travaillez actuellement peut appeler cette fonction. Cette méthode est plus facile si votre base de code a de petites classes et est couplée de manière lâche. Si votre base de code n'a pas de petites classes ou a un couplage très étroit, je suggère de les nettoyer en premier.
La prochaine étape consistera à marquer toutes les fonctions publiques restantes et à créer un graphe d’appel pour déterminer la relation entre les classes. À partir de cet arbre, essayez de déterminer quelle partie de la branche peut être coupée.
L'avantage de cette méthode est que vous pouvez le faire sur une base par module, il est donc facile de continuer à passer votre testeur sans avoir beaucoup de temps sans avoir une longue période de temps lorsque le code est cassé.
Mon approche normale pour trouver des choses inutilisées est
watch "make 2>&1"
a tendance à faire le tour sous Unix.C'est un processus un peu long, mais il donne de bons résultats.
Je n'ai vraiment utilisé aucun outil qui fasse une telle chose ... Mais pour autant que j'ai pu le constater dans toutes les réponses, personne n'a jamais dit que ce problème n'était pas calculable.
Qu'est-ce que je veux dire par là? Que ce problème ne puisse être résolu par aucun algorithme jamais installé sur un ordinateur. Ce théorème (qu'un tel algorithme n'existe pas) est un corollaire du problème de Halting de Turing.
Tous les outils que vous utiliserez ne sont pas des algorithmes mais des méthodes heuristiques (c.-à-d. Pas des algorithmes exacts). Ils ne vous donneront pas exactement tout le code qui n'est pas utilisé.
Si vous êtes sous Linux, vous voudrez peut-être examiner callgrind
, un outil d'analyse de programme C/C++ faisant partie de la suite valgrind
, qui contient également des outils de contrôle des fuites de mémoire et autres. erreurs de mémoire (que vous devriez également utiliser). Il analyse une instance en cours de votre programme et produit des données sur son graphe d'appels et sur les coûts de performance des noeuds du graphe d'appels. Il est généralement utilisé pour l'analyse des performances, mais il génère également un graphe d'appels pour vos applications, afin que vous puissiez voir quelles fonctions sont appelées, ainsi que leurs appelants.
Ceci est évidemment complémentaire aux méthodes statiques mentionnées ailleurs dans la page, et ne sera utile que pour éliminer les classes, méthodes et fonctions totalement inutilisées - cela n'aidera pas à retrouver le code mort à l'intérieur des méthodes qui sont réellement appelées.
Une solution consiste à utiliser un débogueur et la fonctionnalité du compilateur consistant à éliminer le code machine non utilisé lors de la compilation.
Une fois que le code machine a été éliminé, le débogueur ne vous laissera pas mettre de pause sur la ligne correspondante du code source. Donc, vous mettez des points d'arrêt partout et démarrez le programme et inspectez les points d'arrêt - ceux qui sont dans l'état "aucun code chargé pour cette source" correspondent au code éliminé - soit ce code n'est jamais appelé, soit il a été mis en ligne et vous devez effectuer un minimum analyse pour trouver lequel de ces deux est arrivé.
Au moins, c'est comme ça que ça marche dans Visual Studio et je suppose que d'autres outils peuvent également le faire.
C'est beaucoup de travail, mais je suppose plus rapide que d'analyser manuellement tout le code.
Cela dépend de la plate-forme que vous utilisez pour créer votre application.
Par exemple, si vous utilisez Visual Studio, vous pouvez utiliser un outil tel que .NET ANTS Profiler , capable d'analyser et de profiler votre code. De cette façon, vous devriez rapidement savoir quelle partie de votre code est réellement utilisée. Eclipse a également des plugins équivalents.
Sinon, si vous avez besoin de savoir quelle fonction de votre application est réellement utilisée par votre utilisateur final et si vous pouvez libérer votre application facilement, vous pouvez utiliser un fichier journal pour un audit.
Pour chaque fonction principale, vous pouvez suivre son utilisation et, au bout de quelques jours/semaines, récupérez simplement ce fichier journal et consultez-le.
CppDepend est un outil commercial capable de détecter les types, méthodes et champs inutilisés, et bien plus encore. Il est disponible pour Windows et Linux (mais n’a actuellement aucune prise en charge 64 bits) et est fourni avec un essai de 2 semaines.
Clause de non-responsabilité: je n'y travaille pas, mais je possède une licence pour cet outil (ainsi que NDepend , qui est une alternative plus puissante pour le code .NET).
Pour ceux qui sont curieux, voici un exemple de règle intégrée (personnalisable) pour la détection de méthodes mortes, écrite en CQLinq :
// <Name>Potentially dead Methods</Name>
warnif count > 0
// Filter procedure for methods that should'nt be considered as dead
let canMethodBeConsideredAsDeadProc = new Func<IMethod, bool>(
m => !m.IsPublic && // Public methods might be used by client applications of your Projects.
!m.IsEntryPoint && // Main() method is not used by-design.
!m.IsClassConstructor &&
!m.IsVirtual && // Only check for non virtual method that are not seen as used in IL.
!(m.IsConstructor && // Don't take account of protected ctor that might be call by a derived ctors.
m.IsProtected) &&
!m.IsGeneratedByCompiler
)
// Get methods unused
let methodsUnused =
from m in JustMyCode.Methods where
m.NbMethodsCallingMe == 0 &&
canMethodBeConsideredAsDeadProc(m)
select m
// Dead methods = methods used only by unused methods (recursive)
let deadMethodsMetric = methodsUnused.FillIterative(
methods => // Unique loop, just to let a chance to build the hashset.
from o in new[] { new object() }
// Use a hashet to make Intersect calls much faster!
let hashset = methods.ToHashSet()
from m in codeBase.Application.Methods.UsedByAny(methods).Except(methods)
where canMethodBeConsideredAsDeadProc(m) &&
// Select methods called only by methods already considered as dead
hashset.Intersect(m.MethodsCallingMe).Count() == m.NbMethodsCallingMe
select m)
from m in JustMyCode.Methods.Intersect(deadMethodsMetric.DefinitionDomain)
select new { m, m.MethodsCallingMe, depth = deadMethodsMetric[m] }
J’ai demandé à un ami de me poser cette question aujourd’hui, et j’ai jeté un coup d’œil aux développements prometteurs de Clang, par exemple ASTMatcher s et le Static Analyzer qui pourrait avoir une visibilité suffisante lors de la compilation lors de la compilation pour déterminer les sections de code mort, mais j'ai trouvé ceci:
https://blog.flameeyes.eu/2008/01/today-how-to-identify-unused-exported-functions-and-variables
C'est à peu près une description complète de la façon d'utiliser quelques drapeaux GCC apparemment conçus dans le but d'identifier des symboles non référencés!
Je ne pense pas que cela puisse être fait automatiquement.
Même avec les outils de couverture de code, vous devez fournir suffisamment de données d'entrée à exécuter.
Peut être un outil d'analyse statique très complexe et coûteux, tel que de Coverity ou compilateur LLVM pourrait être une aide.
Mais je ne suis pas sûr et je préférerais un examen manuel du code.
[~ # ~] mis à jour [~ # ~]
Bien .. ne supprimant que les variables inutilisées, les fonctions inutilisées n’est pas difficile cependant.
[~ # ~] mis à jour [~ # ~]
Après avoir lu d'autres réponses et commentaires, je suis plus convaincu que cela ne peut pas être fait.
Vous devez connaître le code pour avoir une mesure significative de la couverture de code et si vous savez que beaucoup de modifications manuelles seront plus rapides que de préparer/exécuter/réviser les résultats de couverture.
Le problème général de savoir si une fonction sera appelée est NP-Complete. De manière générale, vous ne pouvez pas savoir à l'avance si une fonction sera appelée car vous ne saurez pas si une machine de Turing s'arrêtera un jour. Vous pouvez obtenir si un chemin (statique) va de main () à la fonction que vous avez écrite, mais cela ne garantit pas qu'il sera appelé.