Question

Je voudrais empêcher ou gérer une exception StackOverflowException que je reçois d'un appel à la méthode XslCompiledTransform.Transform dans un éditeur Xsl J'écris. Le problème semble être que l'utilisateur peut écrire un script Xsl qui est infiniment récursif et qui explose lors de l'appel de la méthode Transform . (En d’autres termes, le problème n’est pas simplement 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 tout exploser?

Était-ce utile?

La solution

De Microsoft:

  

À partir du .NET Framework   version 2.0, une exception StackOverflowException   objet ne peut pas être attrapé par un try-catch   bloquer et le processus correspondant est   résilié par défaut. Par conséquent,   les utilisateurs sont invités à écrire leur code   détecter et empêcher une pile   débordement. Par exemple, si votre   l'application dépend de la récursivité, de l'utilisation   un compteur ou une condition d'état à   terminer la boucle récursive.

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

Vous pouvez faire plusieurs choses.

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

Vous pouvez utiliser la classe Process pour charger l'assemblage qui appliquera la transformation à un processus distinct et alerter l'utilisateur de l'échec en cas de décès, sans détruire votre application principale.

EDIT: Je viens de tester, voici comment faire:

MainProcess:

// 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);
        }
    }
}

Autres conseils

  

REMARQUE La question de la prime de @WilliamJockusch et la question initiale 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. Si vous recherchez un cas particulier avec XslTransform, consultez la réponse acceptée.

Les débordements de pile se produisent parce que les données de la pile dépassent une certaine limite (en octets). Vous trouverez les détails du fonctionnement de cette détection ici .

  

Je me demande s’il existe un moyen général de détecter les exceptions 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 d'un bout à l'autre jusqu'à ce que je le voie. Je me fiche de la façon dont il est bidouillé.

Comme je l’ai mentionné dans le lien, détecter un débordement de pile à partir d’une analyse de code statique nécessiterait de résoudre le problème d’arrêt qui est undecidable . Maintenant que nous avons établi qu'il n'y a pas de solution miracle , je peux vous montrer quelques astuces qui, selon moi, permettent de 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 variations.

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

Le problème ici est que vous disposez d’un environnement de test (limité) et souhaitez détecter un débordement de pile dans un environnement de production (développé).

Au lieu de détecter le responsable de la sécurité 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 taille de la pile ou la profondeur de récursivité maximale.

En gros, j'essaie de forcer un SO en rendant la profondeur de la pile aussi petite que possible. S'il ne déborde pas, je peux toujours le rendre plus grand (= dans ce cas: plus sûr) pour l'environnement de production. Au moment où vous obtenez un débordement de pile, vous pouvez décider manuellement si c'est un "valide" ou non.

Pour ce faire, transmettez 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 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 lui attribuer une valeur supérieure jusqu’à ce que vous obteniez un SO qui ait du sens.

Création d'exceptions avant votre SO

L'exception StackOverflowException n'est pas capturable. Cela signifie que vous ne pouvez pas faire grand chose quand c'est arrivé. Ainsi, si vous pensez que quelque chose va mal dans votre code, vous pouvez créer votre propre exception dans certains cas. La seule chose dont vous avez besoin pour cela est la profondeur actuelle de la pile; il n'y a pas besoin d'un 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 utilisez des composants tiers qui utilisent un mécanisme de rappel. La seule chose nécessaire est que vous puissiez intercepter certains appels dans la trace de la pile.

Détection dans un thread séparé

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

Vous pouvez essayer de détecter un responsable de la sécurité dans un thread séparé .. mais cela ne vous servira probablement à rien. Un débordement de pile peut se produire rapidement , avant même que vous ne changiez de contexte. Cela signifie que ce mécanisme n'est pas fiable du tout ... Je ne recommanderais pas de l'utiliser . C'était amusant de construire cependant, 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 ceci dans le débogueur et amusez-vous.

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

Une autre interprétation de votre question est la suivante: "Où se trouvent les éléments de code susceptibles de provoquer une exception de débordement de pile?". Évidemment, la réponse à cette question est: tout code avec récursivité. Pour chaque élément de code, vous pouvez ensuite effectuer une analyse manuelle.

Il est également possible de déterminer cela à l'aide d'une analyse de code statique. Pour ce faire, vous devez décompiler toutes les méthodes et 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 contienne une récursion ne constitue en aucun cas une garantie qu'un débordement de pile se produira - c'est simplement la condition préalable la plus probable pour votre exception de débordement de pile. En bref, cela signifie que ce code déterminera les éléments de code où un dépassement de pile peut se produire, ce qui devrait considérablement réduire le code.

Pourtant, d'autres approches

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

  1. Gestion du débordement de la pile en hébergeant le processus CLR et en le gérant. Notez que vous ne pouvez toujours pas "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 déjà implémenté :-); 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-le pour déterminer les débordements de pile. Par exemple, vous pouvez implémenter des vérifications qui donnent un signal si vous rencontrez la même méthode X fois dans votre arborescence des appels. Il existe un projet ici qui vous donnera une longueur d'avance.

Je suggérerais de créer un wrapper autour de l'objet XmlWriter, afin qu'il compte le nombre d'appels à WriteStartElement / WriteEndElement et que, si vous limitez le nombre de balises à un certain nombre (par exemple, 100), vous pourrez générer une exception différente, par exemple - InvalidOperation.

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));
    }
}

Cette réponse concerne @WilliamJockusch.

  

Je me demande s'il existe un moyen général de localiser   StackOverflowExceptions. En d'autres termes, supposons que j'ai infini   récursion quelque part dans mon code, mais je ne sais pas où. je veux   traquez-le par un moyen qui est plus facile que de parcourir le code   partout jusqu'à ce que je vois ce qui se passe. Je me fous de savoir comment   il est. Par exemple, ce serait bien d’avoir un module que je pourrais   activer, peut-être même à partir d'un autre thread, qui a interrogé la pile   profondeur et se plaint si elle atteint un niveau que je considère "trop ??élevé". Pour   Par exemple, je pourrais définir "trop ??élevé". à 600 images, sachant que si le   pile étaient trop profonds, ça doit être un problème. Est quelque chose comme ça   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 aurait un peu   la preuve du dépassement serait assez bonne, et elle ne le ferait probablement pas   exploser la sortie trop mal. La clé est que cela ne peut pas impliquer   écrire un chèque partout où le débordement se produit. Parce que l'ensemble   Le problème est que je ne sais pas où c'est. De préférence la solution   ne devrait pas dépendre de ce à quoi ressemble mon environnement de développement; c'est à dire,   cela ne doit pas supposer que j’utilise C # via un ensemble d’outils spécifique (par exemple,   VS).

On dirait que vous êtes enthousiaste à l'idée de techniques de débogage pour intercepter ce StackOverflow, alors j'ai pensé en partager quelques-uns pour que vous puissiez l'essayer.

1. Mémoire mémoire.

Pro : les vidages de mémoire sont un moyen sûr de résoudre le problème d'un débordement de pile. Un MVP C # & amp; J'ai travaillé ensemble pour résoudre un problème et il a ensuite écrit un blog à ce sujet. ici .

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

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

Les inconvénients : les vidages de mémoire sont très volumineux et vous devez attacher le processus AdPlus / procdump au processus.

2. Programmation orientée aspect.

Pro : il s'agit probablement du moyen le plus simple d'implémenter du code qui vérifie la taille de la pile d'appels à partir de toute méthode sans écrire de code dans chaque méthode de votre application. Il existe de nombreux AOP Frameworks qui vous permet d’intercepter avant et après les appels.

Vous indiquera les méthodes à l'origine du débordement de pile.

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

Les inconvénients : ils auront un impact sur les performances: les points d'ancrage sont intégrés à l'IL pour chaque méthode et vous ne pouvez pas vraiment "désactiver" " dehors.

Cela dépend un peu de votre ensemble d’outils d’environnement de développement.

3. Consignation de l'activité de l'utilisateur.

Il y a une semaine, j'essayais de traquer plusieurs problèmes difficiles à reproduire. J'ai posté ce QA Enregistrement de l'activité utilisateur, télémétrie (et variables) dans les gestionnaires d'exceptions globales) . La conclusion à laquelle je suis arrivé était un très simple utilisateur-actions-logger 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é (par exemple, vous abonner à des événements).

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

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

Les fichiers journaux sont relativement petits et mettent l'accent sur les actions à effectuer pour reproduire le problème.

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

Les inconvénients : n'est pas adapté à un service Windows et je suis certain qu'il existe de meilleurs outils comme celui-ci pour les applications Web .

Ne pas nécessairement vous indiquer les méthodes qui provoquent le débordement de pile.

Vous devez parcourir les journaux en reproduisant manuellement les problèmes plutôt qu’un vidage de la mémoire où vous pouvez l’obtenir et le déboguer immédiatement.

& nbsp;

Peut-être pourriez-vous essayer toutes les techniques que j'ai mentionnées ci-dessus et certaines que @atlaste a postées et nous dire lesquelles vous avez trouvées étaient les plus faciles / les plus rapides / les plus sales / les plus acceptables pour fonctionner dans un environnement PROD / etc.

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

Si votre application dépend du code de partie 3d (dans les scripts Xsl), vous devez d'abord décider si vous souhaitez vous défendre ou non contre les bogues. Si vous voulez vraiment défendre, alors je pense que vous devriez exécuter votre logique qui est sujette à des erreurs externes dans AppDomains séparés. Catching StackOverflowException n'est pas bon.

Vérifiez également cette question .

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

J'avais l'habitude d'avoir une boucle presque infinie comme celle-ci:

    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 épuisée comme suit:

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 aidera quelqu'un.

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 ça marche.

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");

        }

    }      
}

@WilliamJockusch, si j'ai bien compris votre préoccupation, 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 problème Problème d’arrêt . Pour le résoudre, vous aurez besoin d'un algorithme super-récursif (comme Prédicats d'essai et d'erreur par exemple) ou une machine capable de calcul hypercalentatif (un exemple est expliqué dans le section suivante - disponible en tant qu'aperçu - de ce livre ).

D'un point de vue pratique, vous devez savoir:

  • Combien de mémoire de pile il vous reste à l'heure donnée
  • De combien de mémoire de pile votre méthode récursive aura-t-elle besoin à un moment donné pour la sortie spécifique?

N'oubliez pas qu'avec les machines actuelles, ces données sont extrêmement mutables du fait du multitâche et que je n'ai pas entendu parler d'un logiciel capable de le faire.

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

À première vue, mis à part le démarrage d'un autre processus, il ne semble pas y avoir de moyen de gérer une StackOverflowException . Avant que quiconque d'autre ne pose la question, 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);
                }
            }
        }
    }
}

Si vous finissez par utiliser la solution de processus séparé, cependant, je vous recommanderais d'utiliser Process.Exited et Process.StandardOutput et de gérer les erreurs vous-même, pour donner vos utilisateurs une meilleure expérience.

Vous pouvez lire cette propriété tous les quelques appels, Environment.StackTrace , et si le chemin de pile a dépassé un seuil spécifique que vous avez défini, vous pouvez retourner la fonction.

Vous devriez également essayer de remplacer certaines fonctions récursives par des boucles.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top