3 de julio de 2008

Recursividad en un ASSERT

Después de tanto tiempo sin escribir sobre código erróneo, hoy voy a presentar un pequeño trozo de código que puede fallar (y falló). A ver si sois capaces de encontrar el error.

#define M_ASSERT( exp ) AssertScreen( exp, #exp )

Para empezar tenemos esta definición de ASSERT. Simplemente al autor del código no le gusta el ASSERT de la librería estándar de C, así que crea su propia versión. No es nada raro.

void AssertScreen( int expression, const char * msg ){
if( expression != 0 ){
return;
}

void * fontData = LoadFile( "debugfont.fnt" );
LogScreen::Setup( fontData );
LogScreen::PrintString( msg );

do{}while( 1 );
}

Veamos que hace el assert propio. Abre una ventana con el mensaje de error e interrumpe la ejecución(o más bien la para entrando en un bucle infinito).

void * LoadFile( const char * name ){
File file;

if( file.Open( name ) ){
M_ASSERT( false && "File Not Found" );
return 0;
}

void * data = file.Load();

file.Close();

return data;
}

Y esta es la función en la que vemos el problema, la función encargada de abrir y leer un fichero.

El error / Mi solución
Bueno, el error creo que se ve rápido. Y más si se tiene en cuenta la pista del título de la entrada Recursividad en un ASSERT. Pues ese es el error, es posible que falle un ASSERT dentro del propio ASSERT. Es más si falla, empezara a fallar recursivamente hasta agotar los recursos, ya que nunca llega a parar la ejecución (no llega nunca al bucle infinito).

El fallo se produce porque ASSERT usa la función LoadFile para cargar la fuente del mensaje. Y LoadFile llama a ASSERT si no encuentra un fichero.

La mejor manera de solucionarlo, creo yo, es quitar el assert del LoadFile. Más aún cuando LoadFile devuelve 0 en caso de no encontrar el fichero. Es de lo más común que no se encuentre un fichero determinado y no por eso se debe llamar a un ASSERT.

Otra posible solución es usar el ASSERT de la librería estándar. Creed me, el ASSERT no hacia nada del otro mundo. Es más, no hacia nada adicional al ASSERT de la librería estándar, solo poner una fuente de letra diferente. Y a veces esto fallaba.

No llega al nivel de ASSERT propio que vi una vez, que permitía ignorar un error, ignorar todos los errores del mismo ASSERT, o terminar la ejecución del programa. Eso si que era motivo para hacerse una función propia.

La moraleja de hoy, no os compliquéis la vida.