Nossa equipe fornece suporte rápido e eficaz ao cliente. As solicitações dos usuários são tratadas apenas pelos programadores, já que nossos clientes são eles mesmos e geralmente fazem perguntas complicadas. Hoje vou falar sobre um pedido recente referente a um falso positivo que até me forçou a realizar uma pequena investigação para resolver o problema.
Trabalhamos duro para reduzir ao mínimo o número de falsos positivos gerados pelo PVS-Studio. Infelizmente, os analisadores estáticos geralmente não conseguem distinguir o código correto de um bug, porque eles simplesmente não têm informações suficientes. Os falsos positivos são, portanto, inevitáveis. No entanto, não é um problema, pois você pode personalizar facilmente o analisador, de modo que
9 em cada 10 avisos apontem para erros genuínos.
Embora os falsos positivos possam não parecer muito, nunca paramos de combatê-los melhorando nossos diagnósticos. Alguns falsos positivos flagrantes são capturados por nossa equipe; outros são relatados por nossos clientes e usuários de versão gratuita.
Um de nossos clientes recentemente nos enviou um e-mail com a seguinte redação:
Por alguma razão, o analisador diz que um determinado ponteiro é sempre nulo, enquanto não é. Além disso, seu comportamento em um projeto de teste é estranho e instável: às vezes emite um aviso e às vezes não. Aqui está um exemplo sintético que reproduz esse falso positivo:#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; }
Não é difícil adivinhar como nossos usuários veem falsos positivos assim. A função
GetNamedSecurityInfo obviamente modifica o valor da variável
pDACL . O que impediu os desenvolvedores de criar um manipulador para casos simples como esse? E por que o aviso não é emitido em todas as sessões? Talvez seja um erro no próprio analisador, digamos, uma variável não inicializada?
Infelizmente ... Oferecer suporte a usuários de um analisador de código estático não é uma tarefa fácil, mas foi minha própria escolha. Então, arregacei as mangas e comecei a investigar o problema.
Comecei verificando a descrição da função
GetNamedSecurityInfo e certificando-me de que sua chamada realmente implicava modificar o valor da variável
pDACL . Aqui está a descrição do sexto argumento:
ppdacl
Um ponteiro para uma variável que recebe um ponteiro para a DACL no descritor de segurança retornado ou NULL se o descritor de segurança não tiver DACL. O ponteiro retornado é válido apenas se você definir o sinalizador DACL_SECURITY_INFORMATION. Além disso, esse parâmetro pode ser NULL se você não precisar da DACL.
|
Eu sei que o PVS-Studio obviamente deve ser capaz de lidar com um código tão simples sem gerar um aviso falso. Nesse ponto, minha intuição já estava me dizendo que o caso não era trivial e que levaria um bom tempo para resolver.
Minhas suspeitas foram confirmadas quando não consegui reproduzir o falso positivo com a versão alfa atual do analisador ou a versão instalada no computador do usuário. Não importa o que eu fiz, o analisador ficou em silêncio.
Pedi ao cliente que me enviasse o
arquivo i pré-processado gerado para o programa de exemplo. Ele fez isso, e eu continuei minha investigação.
O analisador produziu o falso positivo nesse arquivo imediatamente. Por um lado, foi bom finalmente ter conseguido reproduzi-lo. Por outro lado, tive um sentimento que poderia ser melhor ilustrado por esta figura:
Por que esse sentimento? Veja bem, eu sei perfeitamente bem como o analisador e o diagnóstico
V547 funcionam. Simplesmente não há como eles gerarem um falso positivo assim!
OK, vamos fazer um chá e continuar.
A chamada para a função
GetNamedSecurityInfo se expande no seguinte código:
::GetNamedSecurityInfoW(L"ObjectName", SE_FILE_OBJECT, (0x00000004L), 0, 0, &pDACL, 0, &pSD);
Esse código parece o mesmo no arquivo i pré-processado no meu computador e no arquivo enviado pelo usuário.
Hmm ... OK, vejamos a declaração desta função. Aqui está o que eu tenho no meu arquivo:
__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 );
Tudo é lógico e claro. Nada incomum.
Então eu espio o arquivo do usuário e ...
O que vejo lá não pertence à nossa realidade:
__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 );
Observe que o parâmetro formal
pp Dacl está marcado como
const .
Wat? WTF? Wat? WTF?O que é essa
const !? O que está fazendo aqui!?
Bem, pelo menos tenho certeza de que o analisador é inocente e posso defender sua honra.
O argumento é um ponteiro para um objeto constante. Acontece que, do ponto de vista do analisador, a função
GetNamedSecurityInfoW não pode modificar o objeto referido pelo ponteiro. Portanto, no seguinte código:
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;
a variável
pDACL não pode mudar, sobre a qual o analisador nos adverte corretamente (a expressão 'pDACL == 0' sempre é verdadeira.).
OK, agora sabemos o que aciona o aviso. O que ainda não sabemos é de onde veio a palavra-chave
const . Simplesmente não pode estar lá!
Bem, tenho um palpite, e é confirmado pelo que encontro na Internet. Acontece que há uma versão antiga do arquivo aclapi.h com uma descrição incorreta da função. Também encontrei alguns links interessantes:
Portanto, era uma vez uma descrição de função no arquivo 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 );
Alguém alterou o tipo do parâmetro
pObjectName, mas
alterou os tipos de ponteiros ao longo do caminho, adicionando a palavra-chave
const . E o arquivo aclapi.h (6.1.7601.23418-Windows 7.0) terminou da seguinte maneira:
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 );
Agora ficou claro que nosso usuário estava trabalhando com essa versão muito incorreta do aclapi.h, que ele confirmou em seu email. Não pude reproduzir o bug porque estava usando uma versão mais recente.
É assim que a descrição da função fixa se parece no arquivo aclapi.h mais recente (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 );
O tipo do argumento
pObjectName ainda é o mesmo, mas as
constantes extras se foram. Tudo está bom novamente, mas ainda existem cabeçalhos quebrados em uso em algum lugar por aí.
Eu explico tudo isso ao cliente, e ele fica feliz em ver o problema resolvido. Além disso, ele descobriu por que o falso positivo não ocorreu regularmente:
Agora me lembro de experimentar conjuntos de ferramentas neste projeto de teste há algum tempo. A configuração de depuração foi definida como Platform Toolset por padrão para o Visual Studio 2017 - "Visual Studio 2017 (v141)", enquanto a configuração do Release foi definida como "Visual Studio 2015 - Windows XP (v140_xp)". Eu estava simplesmente alternando entre as configurações ontem e o aviso apareceria e desapareceria de acordo.Só isso. A investigação terminou. Discutimos o problema com o cliente e decidimos não adicionar nenhum kludge ao analisador para poder lidar com esse bug no arquivo de cabeçalho. O mais importante é que descobrimos o problema. "Caso encerrado", como se costuma dizer.
ConclusãoO PVS-Studio é um produto de software complexo, que reúne grandes quantidades de informações do código dos programas e as utiliza em várias
técnicas de análise . Nesse caso em particular, ele se mostrou muito inteligente, terminando com um falso positivo devido a uma descrição incorreta da função.
Torne-se nossos clientes e você receberá um suporte profissional imediato de mim e de meus colegas de equipe.