Bug no retorno do PathIsDirectory

Caloni, 2008-09-10 computer blog

Estava eu outro dia programando aquele código esperto "para ontem" quando me deparei com uma situação no mínimo inusitada. Ao testar se um caminho recebido era de fato um diretório me foi retornado pela API um valor diferente de TRUE. E diferente de FALSE!

De acordo com a documentação, o retorno deveria ser TRUE caso o caminho enviado à função fosse de fato um diretório. Caso contrário, o retorno deveria ser FALSE.

Note que existem apenas dois valores possíveis para essa função. Porém, o valor retornado não é 1, o equivalente ao define TRUE, mas sim 0x10 (16 em hexadecimal). O simples exemplo abaixo deve conseguir reproduzir a situação (Windows XP Service Pack 3):

   Setting environment for using Microsoft Visual Studio 2008 x86 tools.
   C:\Tests>copy con IsPathDir.cpp
   #include <shlwapi.h>
   #include <windows.h>
   #include <stdio.h>
   #pragma comment(lib, "shlwapi.lib")
   int main()
   {
           BOOL isDir = PathIsDirectory("C:\\Tests"); // obs.: diretorio TEM que existir
           printf("Resultado: %d.\n", isDir);
   }^Z
           1 arquivo(s) copiado(s).
   C:\Tests>cl IsPathDir.cpp
   Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 15.00.21022.08 for 80x86
   Copyright (C) Microsoft Corporation.  All rights reserved.
   IsPathDir.cpp
   Microsoft (R) Incremental Linker Version 9.00.21022.08
   Copyright (C) Microsoft Corporation.  All rights reserved.
   /out:IsPathDir.exe
   IsPathDir.obj
   C:\Tests>IsPathDir.exe
   Resultado: 16.

Isso quer dizer apenas que o código abaixo vai funcionar,

   if( PathIsDirectory(path) ) // legal: qualquer coisa diferente de zero

o código abaixo vai funcionar

   if( ! PathIsDirectory(path) ) // legal: se der zero (FALSE), OK

e o código abaixo não vai funcionar:

   if( PathIsDirectory(path) == TRUE ) // vixi: TRUE nem sempre é o resultado

E, pior, o código abaixo também não vai funcionar!

   if( PathIsDirectory(path) != TRUE ) // aff... é bom rever os seus conceitos

Pesquisando um pouco descobri uma boa discussão sobre o tema, e inclusive que outras pessoas descobriram o interessante detalhe que para pastas normais o retorno é 0x10, mas para compartilhamentos o retorno é 0x1.

O bug atrás dos documentos

O problema ocorre por causa da maneira que a função determina se o caminho é um diretório ou não. Uma simples vistoria sobre a função nos revela o detalhe crucial:

   C:\Tests>cl /Zi IsPathDir.cpp
   Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 15.00.21022.08 for 80x86
   Copyright (C) Microsoft Corporation.  All rights reserved.
   IsPathDir.cpp
   Microsoft (R) Incremental Linker Version 9.00.21022.08
   Copyright (C) Microsoft Corporation.  All rights reserved.
   /out:IsPathDir.exe
   /debug
   IsPathDir.obj
   C:\Tests>cdb IsPathDir.exe
   Microsoft (R) Windows Debugger Version 6.8.0004.0 X86
   Copyright (c) Microsoft Corporation. All rights reserved.
   CommandLine: IsPathDir.exe
   Symbol search path is: SRV*c:\symbols*http://msdl.microsoft.com/download/symbols
   ...
   ntdll!DbgBreakPoint:
   7c901230 cc              int     3
   0:000> g shlwapi!PathIsDirectoryA
   eax=009836e0 ebx=7ffde000 ecx=00000001 edx=00422828 esi=0006f4cc edi=7c911970
   eip=77ee7538 esp=0012ff6c ebp=0012ff78 iopl=0         nv up ei pl zr na pe nc
   cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
   SHLWAPI!PathIsDirectoryA:
   77ee7538 8bff            mov     edi,edi
   0:000> p
   push    ebp
   mov     ebp,esp
   sub     esp,20Ch
   mov     eax,dword ptr [SHLWAPI!__security_cookie
   push    esi
   mov     esi,dword ptr [ebp+8] ss:0023:0012ff70=0041dc5c
   test    esi,esi
   mov     dword ptr [ebp-4],eax ss:0023:0012ff64=fffffffe
   je      SHLWAPI!PathIsDirectoryA+0xb2 (77ee75ea) [br=0]
   push    esi
   call    SHLWAPI!PathIsUNCServerA (77ec35b9)
   test    eax,eax
   jne     SHLWAPI!PathIsDirectoryA+0xb2 (77ee75ea) [br=0]
   push    esi
   call    SHLWAPI!PathIsUNCServerShareA (77ee737d)
   test    eax,eax
   je      SHLWAPI!PathIsDirectoryA+0xc1 (77ee75f9) [br=1]
   push    esi
   call    dword ptr [SHLWAPI!_imp__GetFileAttributesA (77ea11d4)]
   cmp     eax,0FFFFFFFFh
   ...

Ou seja, para pastas locais a função simplesmente usa a conhecidíssima GetFileAttributes, que retorna o flag 0x10 setado caso se trate de uma pasta, de acordo com a documentação: "The attributes can be one or more of the following values:"

  • FILE_ATTRIBUTE_ARCHIVE 32 (0x20) A file or directory that is an archive file or directory.
  • FILE_ATTRIBUTE_COMPRESSED 2048 (0x800) A file or directory that is compressed.
  • FILE_ATTRIBUTE_DIRECTORY 16 (0x10) The handle that identifies a directory.

Aqui termina nossa dúvida sobre o pequenino bug na documentação. E isso nos lembra também que é sempre bom comparar as coisas da melhor maneira possível. E essa melhor maneira em se tratando de ifs é supor apenas dois valores binário: ou é zero ou é não-zero.

[todo_programador_e_um_filosofo_em_potencial] [os_processosfantasma]