Previna AV com Assert

Uma das razões mais comuns para acontecer Access Violations (AV) nos programas é quanto se tenta acessar um método ou uma propriedade de um objeto que ainda não foi alocado ou que já foi desalocado da memória.

Por exemplo:

procedure AV;
var
  List: TStringList;
begin
  // AV porque List é um objeto não alocado
  List.Add('teste');
 
  // ok, porque List foi previamente alocado
  List := TStringList.Create;
  List.Add('novamente');
 
  // AV, porque o objeto foi desalocado e o valor
  // da variável List é um ponteiro inválido
  List.Free;
  List.Add('outra vez');
 
  // AV da mesma forma que antes, apenas a
  // variável aponta para nil ($0000:0000)
  List := nil;
  List.Add('e finalmente');
end;

Essas são receitas certas pra cometer violações de acesso de leitura de memória. Normalmente, em blocos de código pequenos esse tipo de coisa não ocorre, porque temos mais visibilidade e controle sobre o tempo de vida das variáveis. O problema fica maior quando o objeto é um parâmetro recebido de outra parte qualquer do código. Quem garante que o valor passado como parâmetro é válido?

Só pra ficar claro:

procedure ShowText(List: TStringList);
begin
  ShowMessage(List.Text); // AV? List é válido?
end;
 
procedure TestAV;
var
  Strings: TStringList;
begin
  ShowText(Strings);
end;

Aqui TestAV chama ShowText passando como parâmetro um valor de objeto inválido, porque nunca foi alocado. Seja o valor igual a nil ou uma valor aleatório qualquer (dependendo do contexto, a variável pode conter “lixo” de memória), quando ShowText usar a propriedade Text do objeto vai produzir um AV.

O que eu uso para prevenir esses casos é um Assert básico.

O código acima com Assert ficaria assim:

procedure ShowText(List: TStringList);
begin
  // Exceção (EAssert) caso List seja inválido
  Assert(List <> nil);
 
  // A partir daqui é seguro usar List
  ShowMessage(List.Text);
end;

Assert tem como parâmetro um valor condicional (Boolean). Se falhar (False), dispara uma exceção EAssert. A vantagem é que, ao contrário de uma exceção de violação de acesso, a IDE vai te mostrar certinho onde ocorreu a violação, isto é, vai parar o debugger naquela linha do Assert. Sem uma verificação desse tipo, seria muito mais difícil identificar em todo o seu projeto o ponto que deu problema.

Se você quiser, pode ainda usar usar o segundo parâmetro opcional de Assert para dar uma mensagem ao usuário. Por exemplo: “Assert(List <> nil, ‘A função ShowText não recebeu um objeto TStringList válido’)”.

Outra vantagem do Assert é que depois que você estiver confiante da correção do código (quando esse dia chegar :) ), você poderá desativar um checkbox nas opções de compilação do Delphi para
simplesmente omitir todo o código de verificação de “Assert” globalmente no projeto (lembre-se de dar um Build depois que mudar essa opção).

Dá muito trabalho? “Better safe, than sorry.”

Que atire a primeira pedra quem nunca fez um “Access Violation”. ;-)

Serialização de Variants

Estou trabalhando em um projeto que faz uso extensivo de variáveis do tipo Variant que precisam ser armazenadas em objetos TStream. Os valores podem ser de qualquer tipo: inteiros, strings, datas e até arrays. Os streams são então levados de um lugar para outro em arquivos ou diretamente pela rede para serem lidos por outro processo.

É claro que eu não fui o primeiro a ter essa necessidade, então antes de começar a quebrar a cabeça fui buscar no Google as soluções que já tinham sido criadas. Primeiro encontrei um comentário de Deborah Pate (TeamB) no newsgroup Borland “oleautomation”. Foi um bom começo, mas ainda faltava muito.

Mais tarde encontrei os posts de Nikolay Pavlov - nem acredito que encontrei um parente de Pavlov e ainda um que programa em Delphi! Nikolay estava bem mais próximo do que eu precisava, inclusive com o tratamento recursivo de arrays.

Como o código é livre, copiei, fiz diversas alterações e melhoramentos e estou publicando aqui a minha versão para quem se interessar.

Notas 07/08/08

Oi pessoal. Estamos de volta! Passei uns 2 meses sem escrever enquanto arrumava minha vida, me acomodando às muitas mudanças recentes. Se alguém interpretou minha ausência por abandono ou diminuição do meu interesse pelo Delphi, se enganou. Continuo investindo em novas tecnologias dentro e fora do mundo Delphi. Aliás, volto com várias idéias e experiências novas para compartilhar.

***

A compra da CodeGear pela Embarcadero foi comercial e juridicamente finalizada a poucas semanas. Aqui no Brasil que eu saiba continua a mesma empresa e houve poucas mudanças na direção, embora agora se reportem a outra companhia lá fora.

***

Andreano Lanusse apresentou um preview do Tiburon em português no dia 1 de agosto. A apresentação foi interessante e deixou uma boa impressão. O mais importante foi perceber que a CodeGear está mesmo investindo em melhorias no Delphi e em particular no Delphi Win32, ao contrário do que os pessimistas de plantão vinham dizendo. No blog do Andreano tem alguns artigos em português sobre as novidades do Delphi 2008 2009 (codinome Tiburón).

***

“Anonymous methods” é um dos features mais polêmicos. No mínimo, é idéia interessante com um nome infeliz. Como apontou um comentário de Caleb, “anonymous methods” são de fato “closures“. O recurso de “closure” é comum em algumas linguagens modernas, mas ainda assim tem aplicabilidade questionável.

***

É engraçado para nós brasileiros a grande ênfase que se está dando a conversão completa de toda as libraries nativas Delphi (RTL, VCL, DBX, etc.) para Unicode. Eu lembro de ter assistido uma palestra em uma Borcon em que um representante da Borland americana foi pego de surpresa em público ao descobrir durante a apresentação que ninguém no Brasil tinha problemas com acentuação e ninguém estava ansioso por Unicode.

***

Unicode é bom para a globalização do Delphi. Por isso acaba sendo bom para todos nós, mesmo não tendo necessidade direta de usar Unicode (eu pelo menos não tenho pretensão de escrever programas em mandarin).

***

Não entendi ainda bem como funciona, mas fiquei admirado com a nova diretiva {$MethodInfo ON}, que permite inclusive o novo DataSnap evocar métodos remotos sem necessidade da parafernália Microsoft COM, interfaces, etc.

Atualização: Leonel comentou que a diretiva já existia em versões anteriores.

***

Aliás, por que diabos a CodeGear foi a essa altura do campeonato investir em melhorias na tecnologia COM? Há quem diga que estão “maqueando” correções de bugs como melhorias…

****

Delphi TStringBuilder é legal. Já existe a muitos anos em bibliotecas gratuitas como JCL, mas é legal.

***

Eu quero meu Delphi Win32 com generics!!!