web-dev-qa-db-fra.com

Comment empêcher et / ou gérer une StackOverflowException?

Je souhaite empêcher ou gérer un StackOverflowException que j'obtiens d'un appel au XslCompiledTransform.Transform méthode dans un Xsl Editor Je suis en train d'écrire. Le problème semble être que l'utilisateur peut écrire un Xsl script qui est infiniment récursif, et qui explose juste lors de l'appel à la méthode Transform. (C'est-à-dire que le problème n'est pas seulement l'erreur de programmation typique, qui est généralement la cause d'une telle exception.)

Existe-t-il un moyen de détecter et/ou de limiter le nombre de récursions autorisées? Ou d'autres idées pour empêcher ce code de simplement exploser sur moi?

68
JohnnyM

De Microsoft:

À partir de .NET Framework version 2.0, un objet StackOverflowException ne peut pas être intercepté par un bloc try-catch et le processus correspondant est interrompu par défaut. Par conséquent, il est conseillé aux utilisateurs d'écrire leur code pour détecter et empêcher un débordement de pile. Par exemple, si votre application dépend de la récursivité, utilisez un compteur ou une condition d'état pour terminer la boucle récursive.

Je suppose que l'exception se produit dans une méthode interne .NET, et non dans votre code.

Vous pouvez faire quelques choses.

  • Écrivez du code qui vérifie la récursion infinie dans xsl et avertit l'utilisateur avant d'appliquer une transformation (Ugh).
  • Chargez le code XslTransform dans un processus distinct (Hacky, mais moins de travail).

Vous pouvez utiliser la classe Process pour charger l'assembly qui appliquera la transformation dans un processus distinct et avertir l'utilisateur de l'échec s'il meurt, sans tuer votre application principale.

EDIT: Je viens de tester, voici comment le faire:

Processus principal:

// This is just an example, obviously you'll want to pass args to this.
Process p1 = new Process();
p1.StartInfo.FileName = "ApplyTransform.exe";
p1.StartInfo.UseShellExecute = false;
p1.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;

p1.Start();
p1.WaitForExit();

if (p1.ExitCode == 1)    
   Console.WriteLine("StackOverflow was thrown");

Processus ApplyTransform:

class Program
{
    static void Main(string[] args)
    {
        AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(CurrentDomain_UnhandledException);
        throw new StackOverflowException();
    }

    // We trap this, we can't save the process, 
    // but we can prevent the "ILLEGAL OPERATION" window 
    static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
    {
        if (e.IsTerminating)
        {
            Environment.Exit(1);
        }
    }
}
59
FlySwat

[~ # ~] note [~ # ~] La question dans la prime de @WilliamJockusch et la question d'origine sont différentes.

Cette réponse concerne StackOverflow dans le cas général des bibliothèques tierces et ce que vous pouvez/ne pouvez pas faire avec elles. Si vous recherchez le cas spécial avec XslTransform, consultez la réponse acceptée.


Les débordements de pile se produisent car les données sur la pile dépassent une certaine limite (en octets). Les détails du fonctionnement de cette détection peuvent être trouvés ici .

Je me demande s'il existe un moyen général de retrouver les StackOverflowExceptions. En d'autres termes, supposons que j'ai une récursion infinie quelque part dans mon code, mais je ne sais pas où. Je veux le retrouver par un moyen plus facile que de parcourir le code partout jusqu'à ce que je le voie. Je me fiche du hack.

Comme je l'ai mentionné dans le lien, la détection d'un débordement de pile à partir de l'analyse de code statique nécessiterait de résoudre le problème d'arrêt qui est indécidable. Maintenant que nous avons établi que il n'y a pas de solution miracle, je peux vous montrer quelques astuces qui, je pense, aident à dépister le problème.

Je pense que cette question peut être interprétée de différentes manières, et comme je m'ennuie un peu :-), je vais la décomposer en différentes variantes.

Détection d'un débordement de pile dans un environnement de test

Fondamentalement, le problème ici est que vous disposez d'un environnement de test (limité) et que vous souhaitez détecter un débordement de pile dans un environnement de production (étendu).

Au lieu de détecter le SO lui-même, je résous ce problème en exploitant le fait que la profondeur de la pile peut être définie. Le débogueur vous donnera toutes les informations dont vous avez besoin. La plupart des langues vous permettent de spécifier la pile taille ou la profondeur de récursivité max.

Fondamentalement, j'essaie de forcer un SO en rendant la profondeur de pile aussi petite que possible. Si elle ne déborde pas, je peux toujours l'agrandir (= dans ce cas: plus sûr) pour la production Dès que vous obtenez un débordement de pile, vous pouvez décider manuellement s'il est "valide" ou non.

Pour ce faire, passez la taille de la pile (dans notre cas: une petite valeur) à un paramètre Thread et voyez ce qui se passe. La taille de pile par défaut dans .NET est de 1 Mo, nous allons utiliser une valeur beaucoup plus petite:

class StackOverflowDetector
{
    static int Recur()
    {
        int variable = 1;
        return variable + Recur();
    }

    static void Start()
    {
        int depth = 1 + Recur();
    }

    static void Main(string[] args)
    {
        Thread t = new Thread(Start, 1);
        t.Start();
        t.Join();
        Console.WriteLine();
        Console.ReadLine();
    }
}

Remarque: nous allons également utiliser ce code ci-dessous.

Une fois qu'il déborde, vous pouvez le définir sur une valeur plus élevée jusqu'à ce que vous obteniez un SO qui a du sens.

Création d'exceptions avant vous SO

StackOverflowException n'est pas capturable. Cela signifie que vous ne pouvez pas faire grand-chose une fois que cela s'est produit. Donc, si vous pensez que quelque chose va mal dans votre code, vous pouvez faire votre propre exception dans certains cas. La seule chose dont vous avez besoin pour cela est la profondeur de pile actuelle; il n'y a pas besoin de compteur, vous pouvez utiliser les vraies valeurs de .NET:

class StackOverflowDetector
{
    static void CheckStackDepth()
    {
        if (new StackTrace().FrameCount > 10) // some arbitrary limit
        {
            throw new StackOverflowException("Bad thread.");
        }
    }

    static int Recur()
    {
        CheckStackDepth();
        int variable = 1;
        return variable + Recur();
    }

    static void Main(string[] args)
    {
        try
        {
            int depth = 1 + Recur();
        }
        catch (ThreadAbortException e)
        {
            Console.WriteLine("We've been a {0}", e.ExceptionState);
        }
        Console.WriteLine();
        Console.ReadLine();
    }
}

Notez que cette approche fonctionne également si vous traitez avec des composants tiers qui utilisent un mécanisme de rappel. La seule chose requise est que vous pouvez intercepter les appels certains dans la trace de la pile.

Détection dans un thread séparé

Vous l'avez explicitement suggéré, alors voici celui-ci.

Vous pouvez essayer de détecter un SO dans un thread séparé .. mais cela ne vous fera probablement rien de bien. Un débordement de pile peut se produire rapide, même avant vous obtenez un changement de contexte. Cela signifie que ce mécanisme n'est pas fiable du tout ... Je ne recommanderais pas réellement de l'utiliser . C'était amusant à construire, alors voici le code :-)

class StackOverflowDetector
{
    static int Recur()
    {
        Thread.Sleep(1); // simulate that we're actually doing something :-)
        int variable = 1;
        return variable + Recur();
    }

    static void Start()
    {
        try
        {
            int depth = 1 + Recur();
        }
        catch (ThreadAbortException e)
        {
            Console.WriteLine("We've been a {0}", e.ExceptionState);
        }
    }

    static void Main(string[] args)
    {
        // Prepare the execution thread
        Thread t = new Thread(Start);
        t.Priority = ThreadPriority.Lowest;

        // Create the watch thread
        Thread watcher = new Thread(Watcher);
        watcher.Priority = ThreadPriority.Highest;
        watcher.Start(t);

        // Start the execution thread
        t.Start();
        t.Join();

        watcher.Abort();
        Console.WriteLine();
        Console.ReadLine();
    }

    private static void Watcher(object o)
    {
        Thread towatch = (Thread)o;

        while (true)
        {
            if (towatch.ThreadState == System.Threading.ThreadState.Running)
            {
                towatch.Suspend();
                var frames = new System.Diagnostics.StackTrace(towatch, false);
                if (frames.FrameCount > 20)
                {
                    towatch.Resume();
                    towatch.Abort("Bad bad thread!");
                }
                else
                {
                    towatch.Resume();
                }
            }
        }
    }
}

Exécutez cela dans le débogueur et amusez-vous de ce qui se passe.

Utilisation des caractéristiques d'un débordement de pile

Une autre interprétation de votre question est: "Où sont les morceaux de code qui pourraient potentiellement provoquer une exception de dépassement de pile?". Évidemment, la réponse est: tout le code avec récursivité. Pour chaque morceau de code, vous pouvez ensuite effectuer une analyse manuelle.

Il est également possible de le déterminer à l'aide d'une analyse de code statique. Ce que vous devez faire pour cela est de décompiler toutes les méthodes et de déterminer si elles contiennent une récursion infinie. Voici un code qui fait cela pour vous:

// A simple decompiler that extracts all method tokens (that is: call, callvirt, newobj in IL)
internal class Decompiler
{
    private Decompiler() { }

    static Decompiler()
    {
        singleByteOpcodes = new OpCode[0x100];
        multiByteOpcodes = new OpCode[0x100];
        FieldInfo[] infoArray1 = typeof(OpCodes).GetFields();
        for (int num1 = 0; num1 < infoArray1.Length; num1++)
        {
            FieldInfo info1 = infoArray1[num1];
            if (info1.FieldType == typeof(OpCode))
            {
                OpCode code1 = (OpCode)info1.GetValue(null);
                ushort num2 = (ushort)code1.Value;
                if (num2 < 0x100)
                {
                    singleByteOpcodes[(int)num2] = code1;
                }
                else
                {
                    if ((num2 & 0xff00) != 0xfe00)
                    {
                        throw new Exception("Invalid opcode: " + num2.ToString());
                    }
                    multiByteOpcodes[num2 & 0xff] = code1;
                }
            }
        }
    }

    private static OpCode[] singleByteOpcodes;
    private static OpCode[] multiByteOpcodes;

    public static MethodBase[] Decompile(MethodBase mi, byte[] ildata)
    {
        HashSet<MethodBase> result = new HashSet<MethodBase>();

        Module module = mi.Module;

        int position = 0;
        while (position < ildata.Length)
        {
            OpCode code = OpCodes.Nop;

            ushort b = ildata[position++];
            if (b != 0xfe)
            {
                code = singleByteOpcodes[b];
            }
            else
            {
                b = ildata[position++];
                code = multiByteOpcodes[b];
                b |= (ushort)(0xfe00);
            }

            switch (code.OperandType)
            {
                case OperandType.InlineNone:
                    break;
                case OperandType.ShortInlineBrTarget:
                case OperandType.ShortInlineI:
                case OperandType.ShortInlineVar:
                    position += 1;
                    break;
                case OperandType.InlineVar:
                    position += 2;
                    break;
                case OperandType.InlineBrTarget:
                case OperandType.InlineField:
                case OperandType.InlineI:
                case OperandType.InlineSig:
                case OperandType.InlineString:
                case OperandType.InlineTok:
                case OperandType.InlineType:
                case OperandType.ShortInlineR:
                    position += 4;
                    break;
                case OperandType.InlineR:
                case OperandType.InlineI8:
                    position += 8;
                    break;
                case OperandType.InlineSwitch:
                    int count = BitConverter.ToInt32(ildata, position);
                    position += count * 4 + 4;
                    break;

                case OperandType.InlineMethod:
                    int methodId = BitConverter.ToInt32(ildata, position);
                    position += 4;
                    try
                    {
                        if (mi is ConstructorInfo)
                        {
                            result.Add((MethodBase)module.ResolveMember(methodId, mi.DeclaringType.GetGenericArguments(), Type.EmptyTypes));
                        }
                        else
                        {
                            result.Add((MethodBase)module.ResolveMember(methodId, mi.DeclaringType.GetGenericArguments(), mi.GetGenericArguments()));
                        }
                    }
                    catch { } 
                    break;


                default:
                    throw new Exception("Unknown instruction operand; cannot continue. Operand type: " + code.OperandType);
            }
        }
        return result.ToArray();
    }
}

class StackOverflowDetector
{
    // This method will be found:
    static int Recur()
    {
        CheckStackDepth();
        int variable = 1;
        return variable + Recur();
    }

    static void Main(string[] args)
    {
        RecursionDetector();
        Console.WriteLine();
        Console.ReadLine();
    }

    static void RecursionDetector()
    {
        // First decompile all methods in the Assembly:
        Dictionary<MethodBase, MethodBase[]> calling = new Dictionary<MethodBase, MethodBase[]>();
        var Assembly = typeof(StackOverflowDetector).Assembly;

        foreach (var type in Assembly.GetTypes())
        {
            foreach (var member in type.GetMembers(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance).OfType<MethodBase>())
            {
                var body = member.GetMethodBody();
                if (body!=null)
                {
                    var bytes = body.GetILAsByteArray();
                    if (bytes != null)
                    {
                        // Store all the calls of this method:
                        var calls = Decompiler.Decompile(member, bytes);
                        calling[member] = calls;
                    }
                }
            }
        }

        // Check every method:
        foreach (var method in calling.Keys)
        {
            // If method A -> ... -> method A, we have a possible infinite recursion
            CheckRecursion(method, calling, new HashSet<MethodBase>());
        }
    }

Maintenant, le fait qu'un cycle de méthode contient une récursivité n'est en aucun cas une garantie qu'un débordement de pile se produira - c'est juste la condition la plus probable pour votre exception de débordement de pile. En bref, cela signifie que ce code déterminera les morceaux de code où un débordement de pile peut se produira, ce qui devrait réduire considérablement la plupart du code.

Encore d'autres approches

Vous pouvez essayer d'autres approches que je n'ai pas décrites ici.

  1. Gérer le débordement de pile en hébergeant le processus CLR et en le gérant. Notez que vous ne pouvez toujours pas l'attraper.
  2. Changer tout le code IL, construire une autre DLL, ajouter des contrôles sur la récursivité. Oui, c'est tout à fait possible (je l'ai implémenté dans le passé :-); c'est juste difficile et implique beaucoup de code pour bien faire les choses.
  3. Utilisez l'API de profilage .NET pour capturer tous les appels de méthode et utilisez-la pour comprendre les débordements de pile. Par exemple, vous pouvez implémenter des vérifications que si vous rencontrez la même méthode X fois dans votre arborescence d'appels, vous donnez un signal. Il y a un projet ici qui vous donnera une longueur d'avance.
22
atlaste

Je suggérerais de créer un wrapper autour de l'objet XmlWriter, de sorte qu'il compterait le nombre d'appels à WriteStartElement/WriteEndElement, et si vous limitez le nombre de balises à un certain nombre (fe 100), vous seriez en mesure de lever une exception différente, par exemple - Opération invalide.

Cela devrait résoudre le problème dans la majorité des cas

public class LimitedDepthXmlWriter : XmlWriter
{
    private readonly XmlWriter _innerWriter;
    private readonly int _maxDepth;
    private int _depth;

    public LimitedDepthXmlWriter(XmlWriter innerWriter): this(innerWriter, 100)
    {
    }

    public LimitedDepthXmlWriter(XmlWriter innerWriter, int maxDepth)
    {
        _maxDepth = maxDepth;
        _innerWriter = innerWriter;
    }

    public override void Close()
    {
        _innerWriter.Close();
    }

    public override void Flush()
    {
        _innerWriter.Flush();
    }

    public override string LookupPrefix(string ns)
    {
        return _innerWriter.LookupPrefix(ns);
    }

    public override void WriteBase64(byte[] buffer, int index, int count)
    {
        _innerWriter.WriteBase64(buffer, index, count);
    }

    public override void WriteCData(string text)
    {
        _innerWriter.WriteCData(text);
    }

    public override void WriteCharEntity(char ch)
    {
        _innerWriter.WriteCharEntity(ch);
    }

    public override void WriteChars(char[] buffer, int index, int count)
    {
        _innerWriter.WriteChars(buffer, index, count);
    }

    public override void WriteComment(string text)
    {
        _innerWriter.WriteComment(text);
    }

    public override void WriteDocType(string name, string pubid, string sysid, string subset)
    {
        _innerWriter.WriteDocType(name, pubid, sysid, subset);
    }

    public override void WriteEndAttribute()
    {
        _innerWriter.WriteEndAttribute();
    }

    public override void WriteEndDocument()
    {
        _innerWriter.WriteEndDocument();
    }

    public override void WriteEndElement()
    {
        _depth--;

        _innerWriter.WriteEndElement();
    }

    public override void WriteEntityRef(string name)
    {
        _innerWriter.WriteEntityRef(name);
    }

    public override void WriteFullEndElement()
    {
        _innerWriter.WriteFullEndElement();
    }

    public override void WriteProcessingInstruction(string name, string text)
    {
        _innerWriter.WriteProcessingInstruction(name, text);
    }

    public override void WriteRaw(string data)
    {
        _innerWriter.WriteRaw(data);
    }

    public override void WriteRaw(char[] buffer, int index, int count)
    {
        _innerWriter.WriteRaw(buffer, index, count);
    }

    public override void WriteStartAttribute(string prefix, string localName, string ns)
    {
        _innerWriter.WriteStartAttribute(prefix, localName, ns);
    }

    public override void WriteStartDocument(bool standalone)
    {
        _innerWriter.WriteStartDocument(standalone);
    }

    public override void WriteStartDocument()
    {
        _innerWriter.WriteStartDocument();
    }

    public override void WriteStartElement(string prefix, string localName, string ns)
    {
        if (_depth++ > _maxDepth) ThrowException();

        _innerWriter.WriteStartElement(prefix, localName, ns);
    }

    public override WriteState WriteState
    {
        get { return _innerWriter.WriteState; }
    }

    public override void WriteString(string text)
    {
        _innerWriter.WriteString(text);
    }

    public override void WriteSurrogateCharEntity(char lowChar, char highChar)
    {
        _innerWriter.WriteSurrogateCharEntity(lowChar, highChar);
    }

    public override void WriteWhitespace(string ws)
    {
        _innerWriter.WriteWhitespace(ws);
    }

    private void ThrowException()
    {
        throw new InvalidOperationException(string.Format("Result xml has more than {0} nested tags. It is possible that xslt transformation contains an endless recursive call.", _maxDepth));
    }
}
8
Dmitry Dzygin

Cette réponse est pour @WilliamJockusch.

Je me demande s'il existe un moyen général de retrouver les StackOverflowExceptions. En d'autres termes, supposons que j'ai une récursion infinie quelque part dans mon code, mais je ne sais pas où. Je veux le retrouver par un moyen plus facile que de parcourir le code partout jusqu'à ce que je le voie. Je me fiche du hack. Par exemple, ce serait génial d'avoir un module que je pourrais activer, peut-être même à partir d'un autre thread, qui interrogerait la profondeur de la pile et se plaindrait s'il atteignait un niveau que je considérais "trop ​​élevé". Par exemple, je pourrais définir "trop ​​haut" à 600 images, pensant que si la pile était trop profonde, cela devait être un problème. Est-ce que quelque chose comme ça est possible. Un autre exemple serait d'enregistrer chaque 1000e appel de méthode dans mon code à la sortie de débogage. Les chances que cela obtienne des preuves du débordement seraient plutôt bonnes et cela ne ferait probablement pas trop exploser la sortie. La clé est qu'il ne peut pas impliquer d'écrire un chèque partout où le débordement se produit. Parce que tout le problème est que je ne sais pas où c'est. De préférence, la solution ne devrait pas dépendre de l'apparence de mon environnement de développement; c'est-à-dire, il ne faut pas supposer que j'utilise C # via un ensemble d'outils spécifique (par exemple VS).

Il semble que vous souhaitiez entendre quelques techniques de débogage pour attraper ce StackOverflow, alors j'ai pensé que je partagerais quelques-unes pour vous d'essayer.

1. Dumps de mémoire.

Pro : les vidages de mémoire sont un moyen sûr de déterminer la cause d'un débordement de pile. Un MVP C # et moi avons travaillé ensemble pour dépanner un SO et il a ensuite blogué à ce sujet ici .

Cette méthode est le moyen le plus rapide de rechercher le problème.

Cette méthode ne vous obligera pas à reproduire les problèmes en suivant les étapes vues dans les journaux.

Contre : les vidages mémoire sont très importants et vous devez attacher AdPlus/procdump au processus.

2. Programmation orientée aspect.

Pro's : C'est probablement le moyen le plus simple pour vous d'implémenter du code qui vérifie la taille de la pile d'appels à partir de n'importe quelle méthode sans écrire de code dans chaque méthode de votre application. Il y a un tas de AOP Frameworks qui vous permettent d'intercepter avant et après les appels.

Vous indiquera les méthodes qui provoquent le débordement de la pile.

Vous permet de vérifier la StackTrace().FrameCount à l'entrée et à la sortie de toutes les méthodes de votre application.

Con : Cela aura un impact sur les performances - les crochets sont intégrés dans l'IL pour chaque méthode et vous ne pouvez pas vraiment "désactiver".

Cela dépend quelque peu de votre ensemble d'outils d'environnement de développement.

3. Journalisation de l'activité des utilisateurs.

Il y a une semaine, j'essayais de traquer plusieurs problèmes difficiles à reproduire. J'ai posté ce QA Journalisation des activités utilisateur, télémétrie (et variables dans les gestionnaires d'exceptions globales) . La conclusion à laquelle je suis arrivé était un enregistreur d'actions utilisateur très simple pour voir comment reproduire les problèmes dans un débogueur lorsqu'une exception non gérée se produit.

Pro : Vous pouvez l'activer ou le désactiver à volonté (c'est-à-dire en vous abonnant à des événements).

Le suivi des actions de l'utilisateur ne nécessite pas d'intercepter toutes les méthodes.

Vous pouvez également compter le nombre d'événements auxquels les méthodes sont souscrites beaucoup plus simplement qu'avec AOP.

Les fichiers journaux sont relativement petits et se concentrent sur les actions que vous devez effectuer pour reproduire le problème.

Il peut vous aider à comprendre comment les utilisateurs utilisent votre application.

Con : ne convient pas à un service Windows et je suis sûr qu'il existe de meilleurs outils comme celui-ci pour les applications Web =.

nécessairement ne vous indique pas les méthodes qui provoquent le débordement de la pile.

Vous oblige à parcourir les journaux reproduisant manuellement les problèmes plutôt qu'un vidage de la mémoire où vous pouvez les obtenir et les déboguer immédiatement.


Vous pourriez peut-être essayer toutes les techniques que je mentionne ci-dessus et certaines que @atlaste a publiées et nous dire laquelle vous avez trouvé la plus facile/rapide/sale/la plus acceptable à exécuter dans un environnement PROD/etc.

Quoi qu'il en soit, bonne chance pour retrouver ce SO.

4
Jeremy Thompson

Si votre application dépend du code tiers (dans les scripts Xsl), vous devez d'abord décider si vous voulez vous défendre contre les bogues ou non. Si vous voulez vraiment vous défendre, je pense que vous devriez exécuter votre logique sujette à des erreurs externes dans des domaines d'application séparés. La capture de StackOverflowException n'est pas bonne.

Vérifiez également ceci question .

3
Shrike

J'ai eu un stackoverflow aujourd'hui et j'ai lu certains de vos messages et j'ai décidé d'aider le garbage collector.

J'avais l'habitude d'avoir une boucle presque infinie comme ceci:

    class Foo
    {
        public Foo()
        {
            Go();
        }

        public void Go()
        {
            for (float i = float.MinValue; i < float.MaxValue; i+= 0.000000000000001f)
            {
                byte[] b = new byte[1]; // Causes stackoverflow
            }
        }
    }

Au lieu de cela, laissez la ressource hors de portée comme ceci:

class Foo
{
    public Foo()
    {
        GoHelper();
    }

    public void GoHelper()
    {
        for (float i = float.MinValue; i < float.MaxValue; i+= 0.000000000000001f)
        {
            Go();
        }
    }

    public void Go()
    {
        byte[] b = new byte[1]; // Will get cleaned by GC
    }   // right now
}

Cela a fonctionné pour moi, j'espère que cela aide quelqu'un.

3
Fixation

Avec .NET 4.0, vous pouvez ajouter l'attribut HandleProcessCorruptedStateExceptions de System.Runtime.ExceptionServices à la méthode contenant le bloc try/catch. Cela a vraiment fonctionné! Peut-être pas recommandé mais fonctionne.

using System;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Runtime.ExceptionServices;

namespace ExceptionCatching
{
    public class Test
    {
        public void StackOverflow()
        {
            StackOverflow();
        }

        public void CustomException()
        {
            throw new Exception();
        }

        public unsafe void AccessViolation()
        {
            byte b = *(byte*)(8762765876);
        }
    }

    class Program
    {
        [HandleProcessCorruptedStateExceptions]
        static void Main(string[] args)
        {
            Test test = new Test();
            try {
                //test.StackOverflow();
                test.AccessViolation();
                //test.CustomException();
            }
            catch
            {
                Console.WriteLine("Caught.");
            }

            Console.WriteLine("End of program");

        }

    }      
}
2
jdehaan

@WilliamJockusch, si j'ai bien compris votre inquiétude, il n'est pas possible (d'un point de vue mathématique) d'identifier toujours une récursion infinie car cela signifierait résoudre le Halting problem . Pour le résoudre, vous auriez besoin d'un algorithme super-récursif (comme prédicats d'essai et d'erreur par exemple) ou d'une machine capable hypercalcul (un exemple est expliqué dans la section suivante - disponible en aperçu - de ce livre ).

D'un point de vue pratique, il faudrait savoir:

  • Combien de mémoire de pile vous reste à un moment donné
  • De combien de mémoire de pile votre méthode récursive aura besoin à un moment donné pour la sortie spécifique.

Gardez à l'esprit que, avec les machines actuelles, ces données sont extrêmement modifiables en raison du multitâche et je n'ai pas entendu parler d'un logiciel qui fait la tâche.

Faites-moi savoir si quelque chose n'est pas clair.

1
Gentian Kasa

À première vue, à part démarrer un autre processus, il ne semble pas y avoir de moyen de gérer un StackOverflowException. Avant que quelqu'un d'autre ne le demande, j'ai essayé d'utiliser AppDomain, mais cela n'a pas fonctionné:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading;

namespace StackOverflowExceptionAppDomainTest
{
    class Program
    {
        static void recrusiveAlgorithm()
        {
            recrusiveAlgorithm();
        }
        static void Main(string[] args)
        {
            if(args.Length>0&&args[0]=="--child")
            {
                recrusiveAlgorithm();
            }
            else
            {
                var domain = AppDomain.CreateDomain("Child domain to test StackOverflowException in.");
                domain.ExecuteAssembly(Assembly.GetEntryAssembly().CodeBase, new[] { "--child" });
                domain.UnhandledException += (object sender, UnhandledExceptionEventArgs e) =>
                {
                    Console.WriteLine("Detected unhandled exception: " + e.ExceptionObject.ToString());
                };
                while (true)
                {
                    Console.WriteLine("*");
                    Thread.Sleep(1000);
                }
            }
        }
    }
}

Cependant, si vous finissez par utiliser la solution de processus séparé, je vous recommande d'utiliser Process.Exited et Process.StandardOutput et gérez les erreurs vous-même, pour offrir une meilleure expérience à vos utilisateurs.

0
Nick Mertin