Notre équipe fournit un support client rapide et efficace. Les demandes des utilisateurs sont traitées uniquement par les programmeurs car nos clients sont eux-mêmes programmeurs et posent souvent des questions délicates. Aujourd'hui, je vais vous parler d'une demande récente concernant un faux positif qui m'a même forcé à mener une petite enquête pour résoudre le problème.
Nous travaillons dur pour réduire au minimum le nombre de faux positifs générés par PVS-Studio. Malheureusement, les analyseurs statiques sont souvent incapables de distinguer le code correct d'un bogue car ils n'ont tout simplement pas assez d'informations. Les faux positifs sont donc inévitables. Cependant, ce n'est pas un problème car vous pouvez facilement personnaliser l'analyseur de sorte que
9 avertissements
sur 10 pointeront vers de véritables bogues.
Bien que les faux positifs ne semblent pas être un gros problème, nous ne cessons de les combattre en améliorant nos diagnostics. Certains faux positifs flagrants sont détectés par notre équipe; d'autres sont signalés par nos clients et les utilisateurs de la version gratuite.
Un de nos clients nous a récemment envoyé un e-mail lisant quelque chose comme ceci:
Pour une raison quelconque, l'analyseur dit qu'un certain pointeur est toujours nul, alors qu'il ne l'est pas. De plus, son comportement sur un projet de test est étrange et instable: parfois il émet un avertissement, parfois non. Voici un exemple synthétique reproduisant ce faux positif:#include <windows.h> #include <aclapi.h> #include <tchar.h> int main() { PACL pDACL = NULL; PSECURITY_DESCRIPTOR pSD = NULL; ::GetNamedSecurityInfo(_T("ObjectName"), SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, NULL, NULL, &pDACL, NULL, &pSD); auto test = pDACL == NULL; // V547 Expression 'pDACL == 0' is always true. return 0; }
Il n'est pas difficile de deviner comment nos utilisateurs voient de tels faux positifs. La fonction
GetNamedSecurityInfo modifie évidemment la valeur de la variable
pDACL . Qu'est-ce qui a empêché les développeurs de créer un gestionnaire pour des cas simples comme celui-ci? Et pourquoi l'avertissement n'est-il pas émis à chaque session? C'est peut-être un bug dans l'analyseur lui-même, disons, une variable non initialisée?
Hélas ... Soutenir les utilisateurs d'un analyseur de code statique n'est pas une tâche facile, mais c'était mon choix de le faire. Alors, j'ai retroussé mes manches et je me suis mis à enquêter sur le problème.
J'ai commencé par vérifier la description de la fonction
GetNamedSecurityInfo et m'assurer que son appel impliquait en effet de modifier la valeur de la variable
pDACL . Voici la description du 6ème argument:
ppdacl
Un pointeur vers une variable qui reçoit un pointeur vers la DACL dans le descripteur de sécurité renvoyé ou NULL si le descripteur de sécurité n'a pas de DACL. Le pointeur renvoyé n'est valide que si vous définissez l'indicateur DACL_SECURITY_INFORMATION. En outre, ce paramètre peut être NULL si vous n'avez pas besoin de la DACL.
|
Je sais que PVS-Studio devrait évidemment être capable de gérer un code aussi simple sans générer de faux avertissement. À ce moment-là, mon intuition me disait déjà que l'affaire n'était pas anodine et que cela allait prendre un certain temps à résoudre.
Mes craintes ont été confirmées lorsque je n'ai pas reproduit le faux positif avec notre version alpha actuelle de l'analyseur ou la version installée sur l'ordinateur de l'utilisateur. Peu importe ce que j'ai fait, l'analyseur est resté silencieux.
J'ai demandé au client de m'envoyer le
fichier i prétraité généré pour l'exemple de programme. Il l'a fait et j'ai poursuivi mon enquête.
L'analyseur a produit immédiatement le faux positif sur ce fichier. D'une part, c'était bien que j'aie enfin réussi à le reproduire. D'un autre côté, j'avais un sentiment qui pourrait être mieux illustré par cette image:
Pourquoi ce sentiment? Vous voyez, je sais parfaitement comment fonctionnent à la fois l'analyseur et le diagnostic
V547 . Il n'y a tout simplement aucun moyen qu'ils puissent générer un faux positif comme ça, jamais!
OK, faisons du thé et continuons.
L'appel à la fonction
GetNamedSecurityInfo se développe dans le code suivant:
::GetNamedSecurityInfoW(L"ObjectName", SE_FILE_OBJECT, (0x00000004L), 0, 0, &pDACL, 0, &pSD);
Ce code est identique à la fois dans le fichier i prétraité sur mon ordinateur et dans le fichier envoyé par l'utilisateur.
Hmm ... OK, regardons la déclaration de cette fonction. Voici ce que j'ai dans mon dossier:
__declspec(dllimport) DWORD __stdcall GetNamedSecurityInfoW( LPCWSTR pObjectName, SE_OBJECT_TYPE ObjectType, SECURITY_INFORMATION SecurityInfo, PSID * ppsidOwner, PSID * ppsidGroup, PACL * ppDacl, PACL * ppSacl, PSECURITY_DESCRIPTOR * ppSecurityDescriptor );
Tout est logique et clair. Rien d'inhabituel.
Ensuite, je jette un œil au fichier de l'utilisateur et ...
Ce que j'y vois n'appartient pas à notre réalité:
__declspec(dllimport) DWORD __stdcall GetNamedSecurityInfoW( LPCWSTR pObjectName, SE_OBJECT_TYPE ObjectType, SECURITY_INFORMATION SecurityInfo, const PSID * ppsidOwner, const PSID * ppsidGroup, const PACL * ppDacl, const PACL * ppSacl, PSECURITY_DESCRIPTOR * ppSecurityDescriptor );
Notez que le paramètre formel
pp Dacl est marqué comme
const .
Wat? WTF? Wat? WTF?Quel est ce
const !? Que fait-il ici!?
Eh bien, au moins je sais avec certitude que l'analyseur est innocent et je peux défendre son honneur.
L'argument est un pointeur sur un objet constant. Il s'avère que, du point de vue de l'analyseur, la fonction
GetNamedSecurityInfoW ne peut pas modifier l'objet référencé par le pointeur. Par conséquent, dans le code suivant:
PACL pDACL = NULL; PSECURITY_DESCRIPTOR pSD = NULL; ::GetNamedSecurityInfo(_T("ObjectName"), SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, NULL, NULL, &pDACL, NULL, &pSD); auto test = pDACL == NULL;
la variable
pDACL ne peut pas changer, ce que l'analyseur nous avertit à juste titre (l'expression 'pDACL == 0' est toujours vraie.).
OK, maintenant nous savons ce qui déclenche l'avertissement. Ce que nous ne savons toujours pas, c'est d'où vient ce mot-clé
const . Ça ne peut tout simplement pas être là!
Eh bien, j'ai une supposition, et cela est confirmé par ce que je trouve sur Internet. Il s'avère qu'il existe une ancienne version du fichier aclapi.h avec une description de fonction incorrecte. J'ai également rencontré quelques liens intéressants:
Il était donc une fois une description de la fonction dans le fichier aclapi.h (6.0.6002.18005-Windows 6.0):
WINADVAPI DWORD WINAPI GetNamedSecurityInfoW( __in LPWSTR pObjectName, __in SE_OBJECT_TYPE ObjectType, __in SECURITY_INFORMATION SecurityInfo, __out_opt PSID * ppsidOwner, __out_opt PSID * ppsidGroup, __out_opt PACL * ppDacl, __out_opt PACL * ppSacl, __out_opt PSECURITY_DESCRIPTOR * ppSecurityDescriptor );
Ensuite, quelqu'un a changé le type du paramètre
pObjectName mais a gâché les types de pointeurs en cours de route en ajoutant le mot clé
const . Et le fichier aclapi.h (6.1.7601.23418-Windows 7.0) s'est terminé comme suit:
WINADVAPI DWORD WINAPI GetNamedSecurityInfoW( __in LPCWSTR pObjectName, __in SE_OBJECT_TYPE ObjectType, __in SECURITY_INFORMATION SecurityInfo, __out_opt const PSID * ppsidOwner, __out_opt const PSID * ppsidGroup, __out_opt const PACL * ppDacl, __out_opt const PACL * ppSacl, __out PSECURITY_DESCRIPTOR * ppSecurityDescriptor );
Maintenant, il était clair que notre utilisateur avait travaillé avec cette version très incorrecte de aclapi.h, qu'il a ensuite confirmée dans son e-mail. Je n'ai pas pu reproduire le bogue car j'utilisais une version plus récente.
Voici à quoi ressemble la description de la fonction fixe dans le dernier fichier aclapi.h (6.3.9600.17415-Windows_8.1).
WINADVAPI DWORD WINAPI GetNamedSecurityInfoW( _In_ LPCWSTR pObjectName, _In_ SE_OBJECT_TYPE ObjectType, _In_ SECURITY_INFORMATION SecurityInfo, _Out_opt_ PSID * ppsidOwner, _Out_opt_ PSID * ppsidGroup, _Out_opt_ PACL * ppDacl, _Out_opt_ PACL * ppSacl, _Out_ PSECURITY_DESCRIPTOR * ppSecurityDescriptor );
Le type de l'argument
pObjectName est toujours le même, mais les
const supplémentaires ont disparu. Tout va bien à nouveau, mais il y a encore des en-têtes cassés en usage quelque part.
J'explique tout cela au client, et il est heureux de voir le problème résolu. De plus, il a découvert pourquoi le faux positif ne se produisait pas régulièrement:
Je me souviens maintenant d'avoir expérimenté des ensembles d'outils sur ce projet de test il y a quelque temps. La configuration de débogage a été définie par défaut sur Platform Toolset pour Visual Studio 2017 - «Visual Studio 2017 (v141)», tandis que la configuration de version a été définie sur «Visual Studio 2015 - Windows XP (v140_xp)». Hier, je basculais simplement entre les configurations, et l'avertissement apparaîtrait et disparaîtrait en conséquence.C'est tout. L'enquête est terminée. Nous discutons du problème avec le client et décidons de ne pas ajouter de kludge à l'analyseur pour lui permettre de gérer ce bogue de fichier d'en-tête. La chose la plus importante est que nous avons résolu le problème. "Affaire classée", comme on dit.
ConclusionPVS-Studio est un produit logiciel complexe, qui rassemble de grandes quantités d'informations à partir du code des programmes et les utilise dans diverses
techniques d'analyse . Dans ce cas particulier, il s'est avéré trop intelligent, aboutissant à un faux positif en raison d'une description de fonction incorrecte.
Devenez nos clients et vous êtes assuré d'obtenir un soutien professionnel rapide de moi et de mes coéquipiers.