52 votes

Pourquoi les exceptions .NET ne sont-elles pas capturées par le bloc try/catch ?

Je travaille sur un projet utilisant le ANTLR pour C#. J'ai construit une grammaire pour analyser du texte et cela fonctionne bien. Cependant, lorsque le parseur rencontre un token illégal ou inattendu, il lève l'une des nombreuses exceptions. Le problème est que dans certains cas (pas tous), mon bloc try/catch ne l'attrape pas et arrête l'exécution en tant qu'exception non gérée.

Le problème pour moi est que je ne peux pas reproduire ce problème ailleurs que dans mon code complet. La pile d'appels montre que l'exception se produit bien dans mon bloc try/catch(Exception). La seule chose à laquelle je peux penser est qu'il y a quelques appels d'assemblage ANTLR qui se produisent entre mon code et le code qui lance l'exception et cette bibliothèque n'a pas de débogage activé, donc je ne peux pas passer à travers. Je me demande si les assemblages non débuggeables empêchent le bouillonnement des exceptions ? La pile d'appels ressemble à ceci ; les appels d'assemblages externes sont dans Antlr.Runtime :

    Expl.Itinerary.dll!TimeDefLexer.mTokens() Line 1213 C#
    Antlr3.Runtime.dll!Antlr.Runtime.Lexer.NextToken() + 0xfc bytes 
    Antlr3.Runtime.dll!Antlr.Runtime.CommonTokenStream.FillBuffer() + 0x22c bytes   
    Antlr3.Runtime.dll!Antlr.Runtime.CommonTokenStream.LT(int k = 1) + 0x68 bytes
    Expl.Itinerary.dll!TimeDefParser.prog() Line 109 + 0x17 bytes   C#
    Expl.Itinerary.dll!Expl.Itinerary.TDLParser.Parse(string Text = "", Expl.Itinerary.IItinerary Itinerary = {Expl.Itinerary.MemoryItinerary}) Line 17 + 0xa bytes C#

L'extrait de code de l'appel le plus bas dans Parse() ressemble à ceci :

     try {
        // Execution stopped at parser.prog()
        TimeDefParser.prog_return prog_ret = parser.prog();
        return prog_ret == null ? null : prog_ret.value;
     }
     catch (Exception ex) {
        throw new ParserException(ex.Message, ex);
     }

Pour moi, une clause catch (Exception) aurait dû capturer toute exception quelle qu'elle soit. Y a-t-il une raison pour laquelle ce ne serait pas le cas ?

Mise à jour : J'ai parcouru l'assemblage externe avec Reflector et je n'ai trouvé aucune trace de filetage. L'assemblage semble être une simple classe utilitaire d'exécution pour le code généré par ANTLR. L'exception levée provient de la méthode TimeDefLexer.mTokens() et son type est NoViableAltException, qui dérive de RecognitionException -> Exception. Cette exception est levée lorsque le lexeur ne peut pas comprendre le token suivant dans le flux ; en d'autres termes, une entrée invalide. Cette exception est SUPPOSÉE à se produire, mais elle aurait dû être attrapée par mon bloc try/catch.

De plus, le rejet de l'exception ParserException n'est pas vraiment pertinent dans cette situation. Il s'agit d'une couche d'abstraction qui prend toute exception pendant l'analyse et la convertit en ma propre ParserException. Le problème de gestion des exceptions que je rencontre n'atteint jamais cette ligne de code. En fait, j'ai commenté la partie "throw new ParserException" et j'ai toujours obtenu le même résultat.

Une dernière chose, j'ai modifié le bloc try/catch original en question pour attraper plutôt NoViableAltException, éliminant ainsi toute confusion d'héritage. J'ai toujours obtenu le même résultat.

Quelqu'un a suggéré que parfois VS est trop actif pour attraper les exceptions manipulées en mode débogage, mais ce problème se produit également en mode release.

Mec, je suis toujours en panne ! Je ne l'avais pas encore mentionné, mais j'utilise VS 2008 et tout mon code est en 3.5. L'assemblage externe est 2.0. De plus, une partie de mon code sous-classe une classe dans l'assemblage 2.0. Un décalage de version pourrait-il causer ce problème ?

Mise à jour 2 : J'ai pu éliminer le conflit de version de .NET en portant les parties pertinentes de mon code .NET 3.5 vers un projet .NET 2.0 et en reproduisant le même scénario. J'ai pu reproduire la même exception non gérée lors d'une exécution cohérente en .NET 2.0.

J'ai appris qu'ANTLR a récemment publié la version 3.1. J'ai donc mis à jour la version 3.0.1 et j'ai réessayé. Il s'avère que le code généré est un peu remanié, mais la même exception non gérée se produit dans mes cas de test.

Mise à jour 3 : J'ai répliqué ce scénario dans une projet VS 2008 simplifié . N'hésitez pas à télécharger et à examiner le projet par vous-même. J'ai appliqué toutes les excellentes suggestions, mais je n'ai pas encore réussi à surmonter cet obstacle.

Si vous trouvez une solution de contournement, veuillez partager vos conclusions. Merci encore !


Merci, mais VS 2008 s'arrête automatiquement sur les exceptions non gérées. De plus, je n'ai pas de dialogue Debug->Exceptions. L'exception NoViableAltException qui est levée est entièrement prévue et conçue pour être attrapée par le code utilisateur. Comme elle n'est pas détectée comme prévu, l'exécution du programme s'arrête de manière inattendue en raison d'une exception non gérée.

L'exception déclenchée est dérivée de Exception et il n'y a pas de multithreading avec ANTLR.

30voto

Steve Steiner Points 4044

Je crois que je comprends le problème. L'exception est détectée, le problème est la confusion sur le comportement du débogueur et les différences dans les paramètres du débogueur entre chaque personne qui essaie de le reproduire.

Dans le 3ème cas de votre repro, je crois que vous obtenez le message suivant : "NoViableAltException was unhandled by user code" et un callstack qui ressemble à ceci :

         \[External Code\]  
    >   TestAntlr-3.1.exe!TimeDefLexer.mTokens() Line 852 + 0xe bytes   C#
        \[External Code\]   
        TestAntlr-3.1.exe!TimeDefParser.prog() Line 141 + 0x14 bytes    C#
        TestAntlr-3.1.exe!TestAntlr\_3.\_1.Program.ParseTest(string Text = "foobar;") Line 49 + 0x9 bytes   C#
        TestAntlr-3.1.exe!TestAntlr\_3.\_1.Program.Main(string\[\] args = {string\[0x00000000\]}) Line 30 + 0xb bytes   C#
        \[External Code\]   

Si vous faites un clic droit dans la fenêtre du callstack et que vous activez l'option "show external code", vous voyez ceci :

        Antlr3.Runtime.dll!Antlr.Runtime.DFA.NoViableAlt(int s = 0x00000000, Antlr.Runtime.IIntStream input = {Antlr.Runtime.ANTLRStringStream}) + 0x80 bytes   
        Antlr3.Runtime.dll!Antlr.Runtime.DFA.Predict(Antlr.Runtime.IIntStream input = {Antlr.Runtime.ANTLRStringStream}) + 0x21e bytes  
    >   TestAntlr-3.1.exe!TimeDefLexer.mTokens() Line 852 + 0xe bytes   C#
        Antlr3.Runtime.dll!Antlr.Runtime.Lexer.NextToken() + 0xc4 bytes 
        Antlr3.Runtime.dll!Antlr.Runtime.CommonTokenStream.FillBuffer() + 0x147 bytes   
        Antlr3.Runtime.dll!Antlr.Runtime.CommonTokenStream.LT(int k = 0x00000001) + 0x2d bytes  
        TestAntlr-3.1.exe!TimeDefParser.prog() Line 141 + 0x14 bytes    C#
        TestAntlr-3.1.exe!TestAntlr\_3.\_1.Program.ParseTest(string Text = "foobar;") Line 49 + 0x9 bytes   C#
        TestAntlr-3.1.exe!TestAntlr\_3.\_1.Program.Main(string\[\] args = {string\[0x00000000\]}) Line 30 + 0xb bytes   C#
        \[Native to Managed Transition\]    
        \[Managed to Native Transition\]    
        mscorlib.dll!System.AppDomain.ExecuteAssembly(string assemblyFile, System.Security.Policy.Evidence assemblySecurity, string\[\] args) + 0x39 bytes  
        Microsoft.VisualStudio.HostingProcess.Utilities.dll!Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly() + 0x2b bytes  
        mscorlib.dll!System.Threading.ThreadHelper.ThreadStart\_Context(object state) + 0x3b bytes  
        mscorlib.dll!System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, object state) + 0x81 bytes    
        mscorlib.dll!System.Threading.ThreadHelper.ThreadStart() + 0x40 bytes

Le message du débogueur vous indique qu'une exception provenant de l'extérieur de votre code (de NoViableAlt) traverse le code que vous possédez dans TestAntlr-3.1.exe!TimeDefLexer.mTokens() sans être traitée.

La formulation prête à confusion, mais cela ne signifie pas que l'exception n'est pas attrapée. Le débogueur vous fait savoir que le code que vous possédez mTokens()" doit être robuste pour éviter que cette exception ne soit levée par lui.

Des choses à jouer avec pour voir ce que cela donne pour ceux qui n'ont pas reproduit le problème :

  • Allez dans Outils/Options/Débogage et désactivez "Activer seulement mon code (Managed only)". ou l'option.
  • Allez dans Debugger/Exceptions et désactivez "User-unhandled" pour les exceptions du Common-Language Runtime. Common-Language Runtime Exceptions.

9voto

ljs Points 16511

Indépendamment du fait que l'assemblage ait été compilé en tant que release build, l'exception doit certainement remonter à l'appelant, il n'y a aucune raison pour que le fait que l'assemblage n'ait pas été compilé en mode debug ait un effet sur cela.

Je suis d'accord avec Daniel qui suggère que l'exception se produit peut-être sur un thread séparé - essayez d'accrocher l'événement d'exception de thread dans Application.ThreadException. Cet événement devrait être déclenché lorsqu'une exception de fil non gérée se produit. Vous pourriez adapter votre code comme suit

using System.Threading;

...

void Application_ThreadException(object sender, ThreadExceptionEventArgs e) {
  throw new ParserException(e.Exception.Message, e.Exception);
}    

 ...

 var exceptionHandler = 
    new ThreadExceptionEventHandler(Application_ThreadException);
 Application.ThreadException += exceptionHandler;
 try {
    // Execution stopped at parser.prog()
    TimeDefParser.prog_return prog_ret = parser.prog();
    return prog_ret == null ? null : prog_ret.value;
 }
 catch (Exception ex) {
    throw new ParserException(ex.Message, ex);
 }
 finally {
    Application.ThreadException -= exceptionHandler;
 }

5voto

tbreffni Points 3031

Utilisez-vous .Net 1.0 ou 1.1 ? Si c'est le cas, catch(Exception ex) n'attrape pas les exceptions du code non géré. Vous devez utiliser catch {} à la place. Voir cet article pour plus de détails :

http://www.netfxharmonics.com/2005/10/net-20-trycatch-and-trycatchexception/

3voto

Daniel Auger Points 8459

Est-il possible que l'exception soit levée dans un autre thread ? Il est évident que votre code d'appel est mono-thread, mais il est possible que la bibliothèque que vous utilisez effectue des opérations multithread sous couvert.

3voto

Luke Girvin Points 8270

Se pourrait-il que vous ayez affaire à une exception qui ne dérive pas de System.Exception ? Si c'est le cas, consultez cet article de MSDN :

http://msdn.microsoft.com/en-us/library/ms404228(VS.80).aspx

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X